GithubHelp home page GithubHelp logo

shubham-kumar-2000 / protocol-registry Goto Github PK

View Code? Open in Web Editor NEW
64.0 2.0 14.0 379 KB

This module allows you to set custom protocol handler for your nodejs app.

License: MIT License

Shell 0.40% JavaScript 87.35% EJS 3.16% Objective-C 2.96% AppleScript 4.63% TypeScript 1.50%
nodejs protocol registry cross-platform npm-module macos windows linux hacktoberfest

protocol-registry's Introduction

Protocol-registry

Registers protocols like:- yourapp:// or myapp:// etc. to open your nodejs app from different browsers.

This is meant to be used in command-line tools and scripts, not in the browser.


Coding

Why?

  • Actively maintained.
  • Handles Cross Platform.
  • Supports WSL paths to Windows apps.
  • Handles multi-line commands.
  • Works on electron.

Install

$ npm install protocol-registry

Usage

const path = require("path");

const ProtocolRegistry = require("protocol-registry");

console.log("Registering...");
// Registers the Protocol
ProtocolRegistry.register({
  protocol: "testproto", // sets protocol for your command , testproto://**
  command: `node ${path.join(__dirname, "./tester.js")} $_URL_`, // this will be executed with a extra argument %url from which it was initiated
  override: true, // Use this with caution as it will destroy all previous Registrations on this protocol
  terminal: true, // Use this to run your command inside a terminal
  script: false,
  scriptName: 'my-custom-script-name' // Custom script name.
}).then(async () => {
  console.log("Successfully registered");
});
Note : Refrain from using query to get data from the url, click here to view some alternatives.
Alternative 1

Instead you can use routing params to get the data from the url, described in the example below:

Original Way : testproto://test?a=b&b=c

Must use : testproto://test/-a/b/-b/c
As it is more CLI friendly.
Example :

const url = "testProto://-l/Hindi";
const { ArgumentParser } = require("argparse");
const { version } = require("./package.json");

const protocols = ["testProto", "test"];
const defaultProtocol = "testProto";
const parser = new ArgumentParser({
  description: "Example",
});

parser.add_argument("-v", "--version", { action: "version", version });
parser.add_argument("mode", {
  type: String,
  choices: protocols,
  nargs: "?",
  default: defaultProtocol,
});
parser.add_argument("-l", "--lang", {
  help: "Choose the language.",
  choices: ["Hindi", "English"],
});
const data = parser.parse_args(url.split(/:\/\/|[\/]/g));
console.log(JSON.stringify(data, null, 4));
// {
//    "mode": "testProto",
//    "lang": "Hindi"
// }
Alternative 2

Use can use base64 encryption to transmit data and decode it there.

const encode = (str) => Buffer.from(str).toString('base64');
const decode = (str) => Buffer.from(str, 'base64').toString();

const protocol = 'testproto://';
const encoded = encode(JSON.stringify({ mode: 'testProto', lang: 'Hindi' }));
const url = `${protocol}${encoded}`;
console.log(url);
// testproto://eyJtb2RlIjoidGVzdFByb3RvIiwibGFuZyI6IkhpbmRpIn0=

const data = url.split('://')[1];
const decoded = JSON.parse(decode(data));
console.log(decoded);
// { mode: 'testProto', lang: 'Hindi' }

On Electron :

On electron, you can use the protocol-registry to open your app through custom protocols.

Note : Electron's built-in app.setAsDefaultProtocolClient is recommended to be used in production but as it has some issues in development you can use ProtocolRegistry.register instead while developing.

const dev = require("electron-is-dev");
const ProtocolRegistry = require("protocol-registry")

if (dev) {
  ProtocolRegistry
    .register({
      protocol: "testproto",
      command: `"${process.execPath}" "${path.resolve(
        process.argv[1]
      )}" $_URL_`,
      override: true,
      script: true,
      terminal: dev,
    })
    .then(() => console.log("Successfully registered"))
    .catch(console.error);
} else {
  if (!app.isDefaultProtocolClient('testproto')) {
    app.setAsDefaultProtocolClient('testproto');
  }
}

API

At present it supports :

register(options, cb(err))

Options are mentioned in the above example, more details below. If a valid callback is provided then it returns cb(err) Otherwise it returns a promise.

Example

const path = require("path");

const ProtocolRegistry = require("protocol-registry");

// Registers the Protocol
ProtocolRegistry.register({
  protocol: "testproto",
  command: `node ${path.join(__dirname, "./tester.js")} $_URL_`,
  terminal: true,
})
  .then(() => {
    // do something
  })
  .catch((e) => {
    // do something
  });
// Above will run tester.js when testproto://** is called as
// node .../tester.js testproto://**
// you can further parse the url to run in different modes
// As override is not passed true it will throw an error is protocol already exists

ProtocolRegistry.register(
  {
    protocol: "testproto",
    command: `node ${path.join(__dirname, "./tester.js")} $_URL_`,
    terminal: true,
  },
  (err) => {
    if (err) {
      // do something
    }
  }
);
// Example with callback

ProtocolRegistry.register(
  {
    protocol: "testproto",
    command: `node ${path.join(__dirname, "./tester.js")} $_URL_`,
    terminal: false, // Terminal is set to false
  },
  (err) => {
    if (err) {
      // do something
    }
  }
);
// The above code will run your command in background
// You wont be able to see any logs
// But if your program launches any UI / webpage / file will be visible

const commands = `cd path/to/destination
ls
node ${path.join(__dirname, "./tester.js")} $_URL_
`;

ProtocolRegistry.register(
  {
    protocol: "testproto",
    command: commands,
    terminal: true, // Terminal is set to false
    script: true, // This will save your commands in a script file and execute it when the protocol is hit.
    scriptName: 'my-custom-script-name' // This is the name of the script file that will be created if script option is set true.
  },
  (err) => {
    if (err) {
      // do something
    }
  }
);
// the above code will save your commands to a script file
// and execute it when ever required
// use this for multiline commands

checkifExists(protocol)

Checks if the provided protocol already exists or not. Returns a Promise which resolves in true or false.

Example

const path = require("path");

const ProtocolRegistry = require("protocol-registry");

// Registers the Protocol
ProtocolRegistry.checkifExists("testproto")
  .then((res) => {
    console.log(res); // true or false
    // do something
  })
  .catch((e) => {
    // do something
  });
// Above snippet will check it already some app uses the given protocol or not

options

Register function accept the below mentioned option

name types default details
protocol String (required) NA Only alphabets allowed. Your command will be executed when any url starting with this protocol is opened i.e. "myapp://test","testproto://abcd?mode=dev", etc. And please make sure that the protocol is unique to your application.
command String (required) NA This command will be executed when the protocol is called. $_URL_ mentioned anywhere in your command will be replaced by the url by which it is initiated.
override Boolean false If this is not true, then you will get an error that protocol is already being used. So, first check if the protocol exist or not then take action accordingly (Refrain from using it).
terminal Boolean false If this is set true, then first a terminal is opened and then your command is executed inside it.otherwise your command is executed in background and no logs appear but if your program launches any UI / webpage / file, it will be visible.
script Boolean false If this is set true, then your command is saved in a script and that script is executed. This option is recommended if you are using multi-line commands or your command uses any kind of quotes.
scriptName String ${protocol} This is the name of the script file that will be created if script option is set true.

Supported platforms

  • Windows - OS - Supported
  • linux - OS - Supported
  • MacOS - OS - Supported with some anomalies mentioned below.

MacOS Anomalies

terminal: false

In MacOS if you don't launch the terminal it will run your command without logging in.

Thus you need to use absolute address of each command in your command string.

Example

Suppose you want to run :

$ node /path/to/index.js

Then first you need to find the path of node using the command below in terminal :

$ type node
> node is /usr/local/bin/node

Then replace the address of node in original command. So your final command will be :

$ /usr/local/bin/node /path/to/index.js

To check if your program is running in MacOS you can use the code below:

if (process.platform === "darwin") {
  // running in MacOS do some thing
}

To run shell commands such as "type node" using nodeJS please check the ShellJS documentation

Contributors:

Credits goes to these people: ✨

Visitor's Count Visitor Count

protocol-registry's People

Contributors

adi-11 avatar dependabot[bot] avatar godbleak avatar mayank1307 avatar san-saha avatar shubham-kumar-2000 avatar simran2337 avatar snyk-bot avatar trimceski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

protocol-registry's Issues

Handling non-zero exit codes from `xdg-mime` incorrectly interprets absence of protocol as an error on KDE

Describe the bug:

When using the package to register custom protocols on a system running KDE, an error is thrown if the protocol isn't already registered. This behaviour stems from the xdg-mime utility's defapp_kde function treating a query for a non-existent protocol as a failure, thus returning an exit code of 4 ("The action failed"). This is then interpreted by protocol-registry as an error condition, even though this behaviour is expected and appropriate when checking for a non-existent protocol.

To Reproduce:

  1. Install and use the protocol-registry package in a Node.js application on a KDE-based system.
  2. Try to register a custom protocol that isn't already registered on the system.
  3. protocol-registry will throw an error, indicating that the xdg-mime query default x-scheme-handler/{protocol} command failed.

Expected behaviour:

protocol-registry should handle the exit code 4 from xdg-mime as an indication that the protocol does not exist, rather than as an error. No error should be thrown when attempting to register a new protocol that does not already have a handler.

MAC documentation is absent.

Prerequisites:

Please answer the following questions for yourself before submitting an issue. YOU MAY DELETE THE PREREQUISITES SECTION.

  • I checked to make sure that this issue has not already been filed.

Expected Behavior:

Repo must contain documentation for registering a protocol in mac.

Current Behavior:

Doesn't contain any documentation regarding registering a protocol in mac.

Solution:

Add the documentation in mac directory inside assets.

URL Schema

Hey,

Given the URL: testproto://test/example?var1=hello&var2=world
And parsing the arguments passed to my JS, the below happens...

image

I am looking for clarification as to examples of correctly formatted URLs.
Trying to figure out how I would go about parsing these into commands, and sub command and pass variables into my application via URL query.

Can you clarify the URL schema so I know what my options are please?

Absolute Path of Command is Required on MacOS when Terminal Mode is Off

Describe the bug:

On MacOS systems, (as mentioned in the bottom of the README) an absolute path is required to run commands when terminal mode is off.

To Reproduce:

Steps to reproduce the behavior:

  1. Set the command to use a non built-in command, such as "node ${path.join(__dirname, './tester.js')} $_URL_" (See how it uses node?)
  2. Set terminal to false in the configuration.
  3. Register the protocol.
  4. Go to the protocol in a browser.
  5. It will error and say that the command does not exist.

Expected behavior:

It should run the command like a user is doing so.


Bugs in MacOS

Describe the bug:

There are some bugs in the Mac OS directory.

To Reproduce:

Steps to reproduce the behavior:

  1. Run driver.js

Expected behavior:

To run successfully in macOS.

Problem with spaces in path to script.sh

Describe the bug:

I'm on linux and the script I run for registering a protocol is on a path with a space in it. I get the following error:

Error: chmod: cannot access '/home/vuenc/Some': No such file or directory
chmod: cannot access 'Folder/node_modules/protocol-registry/temp/script.sh': No such file or directory

    at Object.register (/home/vuenc/Some Folder/node_modules/protocol-registry/src/linux/index.js:93:52)

The problem is that in src/linux/index.js in line 92, const chmod = await shell.exec('chmod +x ' + scriptFilePath);, the path is passed to the shell without escaping spaces.

To Reproduce:

  1. Create a directory "Some Folder" in your home directory and run npm install protocol-registry in it
  2. Create a file registerMyProtocol.js with the contents:
const ProtocolRegistry = require("protocol-registry");

ProtocolRegistry.register({
    protocol: "testprotocol", // sets protocol for your command , testproto://**
    command: "echo $_URL_", // this will be executed with a extra argument %url from which it was initiated
    override: true, // Use this with caution as it will destroy all previous Registrations on this protocol
    terminal: false, // Use this to run your command inside a terminal
    script: false,
})
.catch((e) => {console.log(e)})
  1. Run node registerMyProtocol.js

Remove Shelljs

Describe the bug:

Shelljs prevents proper functioning in electron.

To Reproduce:

Steps to reproduce the behavior:

  1. Go to a electron project.
  2. Import protocol-registry

Note:

  • For this issue we will be removing shelljs as a dependency.

Issue when setting another protocol than the demo

On OSX when adding a new protocol (after setting previously another with your code example)

const ProtocolRegistry = require("protocol-registry");

console.log("Registering...");
// Registers the Protocol
ProtocolRegistry.register({
  protocol: "toto", // sets protocol for your command , testproto://**
  command: `/usr/local/bin/node ${path.join(__dirname, "./tester.js")} $_URL_`, // this will be executed with a extra argument %url from which it was initiated
  override: true, // Use this with caution as it will destroy all previous Registrations on this protocol
  terminal: true, // Use this to run your command inside a terminal
  script: false,
  scriptName: 'my-custom-script-name' // Custom script name.
}).then(async () => {
  console.log("Successfully registered");
});

produce

Registering...
/Users/quentinlamamy/dev/cafeine/customProtocol/node_modules/protocol-registry/src/macos/index.js:144
            throw new Error(scriptResult.stderr);
                  ^

Error: .: replacing existing signature
.: replacing existing signature

    at Object.register (/Users/quentinlamamy/dev/cafeine/customProtocol/node_modules/protocol-registry/src/macos/index.js:144:19)

Node.js v20.0.0

CheckIfExist function related support app

Prerequisites:

Please answer the following questions for yourself before submitting an issue. YOU MAY DELETE THE PREREQUISITES SECTION.

  • I checked to make sure that this issue has not already been filed.

Expected Behavior:

There should a xCode app to check if the default app exists for a given protocol.

Current Behavior:

No such app found.

Solution:

To make a xCode app to full-fill the requirements.

Configurable `temp` directory (or support for `pkg`)

Feature request:

I'd like a way to configure the temp directory, because right now it's in node_modules.

Is your feature request related to a problem? Please describe. (optional):

I'm trying to use pkg to create an executable file of my script, and we're getting the following error because pkg's snapshot filesystem doesn't seem to work well with it.

image

Describe the solution you'd like:

Adding a path to the options object would be good.

Describe alternatives you've considered:

Describe how this feature will be useful to our users:

Additional context:


Note:

  • If you want to work on an issue, you should check if it has already been assigned to anyone.

Documentation Issue in readme.md

Describe the bug:

The Documentation had some grammatical errors and some understanding issues through the language of the documentation which caused some problems in understanding the working of the npm module.

To Reproduce:

Steps to reproduce the behavior:

  1. Go to 'readme.md'

Expected behavior:

It was expected to have a clear idea of the working npm module which failed at some places.

Falsy terminal option registers invalid command on Windows

Describe the bug:

When on Windows with Node 14 LTS, calling the register function with the terminal option set to false or undefined results in an invalid command being registered – the problem is in the following line:

(terminal && 'cmd /k ') + command

which produces commands that start with "false" or "undefined". Replacing it with

(terminal ? 'cmd /k ' : '') + command

makes the command work.

To Reproduce:

Step through the usage example with a debugger, pass false for the terminal option:

ProtocolRegistry.register({
    protocol: 'testproto', // sets protocol for your command , testproto://**
    command: `node ${path.join(__dirname, './tester.js')} $_URL_`, // this will be executed with a extra argument %url from which it was initiated
    override: true, // Use this with caution as it will destroy all previous Registrations on this protocol
    terminal: false, // Use this to run your command inside a terminal
    script: false
}).then(async () => {
    console.log('Successfully registered');
});

Expected behavior:

Passing false for the terminal option should register a correct command.

Let us name the wrapper file.

Feature request:

Instead of automatically naming it {protocol}Wrapper.(bat/sh), let us choose a name for it.

Is your feature request related to a problem? Please describe. (optional): No

Describe alternatives you've considered:

I've considered just forking your package to make this change myself, but my application automatically downloads packages from NPM when a new one is required or one goes missing, which would cause issues.

Describe how this feature will be useful to our users:

It wouldn't look bad when you use the feature.


Note:

  • If you want to work on an issue, you should check if it has already been assigned to anyone.

Use `os.tmpDir()` and allow numbers on protocol

Hi! 👋

Firstly, thanks for your work on this project! 🙂

Today I used patch-package to patch [email protected] for the project I'm working on.

Essentially what I need is to allow numbers on protocol and use default OS temp dir instead of the node_moules one. This is much more reliable based on my experience.

Here is the diff that solved my problem:

diff --git a/node_modules/protocol-registry/src/linux/index.js b/node_modules/protocol-registry/src/linux/index.js
index e47f611..20fa07f 100644
--- a/node_modules/protocol-registry/src/linux/index.js
+++ b/node_modules/protocol-registry/src/linux/index.js
@@ -6,6 +6,7 @@ const { preProcessCommands } = require('../utils/processCommand');
 const constants = require('../config/constants');
 const validator = require('../utils/validator');
 const { registerSchema } = require('../validation/common');
+const { tmpdir } = require('os');
 
 /**
  * Checks if the given protocal already exist on not
@@ -72,10 +73,10 @@ const register = async (options, cb) => {
         }
 
         const desktopFileName = `${protocol}.desktop`;
-        const desktopFilePath = join(__dirname, '../../temp', desktopFileName);
+        const desktopFilePath = join(tmpdir(), desktopFileName);
         const desktopTemplate = join(__dirname, './templates', 'desktop.ejs');
         const scriptTemplate = join(__dirname, './templates', 'script.ejs');
-        const scriptFilePath = join(__dirname, '../../temp', 'script.sh');
+        const scriptFilePath = join(tmpdir(), 'script.sh');
 
         command = await preProcessCommands(
             protocol,
diff --git a/node_modules/protocol-registry/src/macos/index.js b/node_modules/protocol-registry/src/macos/index.js
index 1b368c3..3c07345 100644
--- a/node_modules/protocol-registry/src/macos/index.js
+++ b/node_modules/protocol-registry/src/macos/index.js
@@ -77,19 +77,18 @@ const register = async (options, cb) => {
         const plistMutator = join(__dirname, 'plistMutator.js');
 
         const appTemplate = join(__dirname, './templates', 'app.ejs');
-        const appSource = join(__dirname, '../../temp', `app-${protocol}.txt`);
+        const appSource = join(tmpdir(), `app-${protocol}.txt`);
         const appPath = join(homedir, `APP-${protocol}.app`);
 
         const urlAppTemplate = join(__dirname, './templates', 'url-app.ejs');
         const urlAppSource = join(
-            __dirname,
-            '../../temp',
+            tmpdir(),
             `URL-${protocol}.txt`
         );
         const urlAppPath = join(homedir, `URL-${protocol}.app`);
 
         const scriptTemplate = join(__dirname, './templates', 'script.ejs');
-        const scriptFilePath = join(__dirname, '../../temp', 'script.sh');
+        const scriptFilePath = join(__dirname, tmpdir(), 'script.sh');
 
         const appSourceContent = await new Promise((resolve, reject) => {
             ejs.renderFile(
diff --git a/node_modules/protocol-registry/src/validation/common.js b/node_modules/protocol-registry/src/validation/common.js
index f1ab1cc..3a827de 100644
--- a/node_modules/protocol-registry/src/validation/common.js
+++ b/node_modules/protocol-registry/src/validation/common.js
@@ -2,7 +2,7 @@ const Joi = require('joi');
 
 exports.registerSchema = Joi.object({
     protocol: Joi.string()
-        .regex(/^[a-zA-Z]+$/)
+        .regex(/^[a-zA-Z0-9]+$/)
         .required(),
     command: Joi.string().required(),
     override: Joi.boolean(),

This issue body was partially generated by patch-package.

Reducing complexity for checkIfExist in macOS

  • I checked to make sure that this issue has not already been filed.

Expected Behavior:

To check if default app exists or not using appleScript.

Current Behavior:

It's been checked using a binary app of Objective-C.

Solution:

Reduce the module size by using Apple Script.

Deregister URL handler

Option to deregister a URL protocol, for people who are creating tools and need to be able to uninstall the app for their clients (including the registry entry). The override is good for development testing different configurations, but I don't see a documented way to remove the URL protocol handler other than deleting from registry.

Deno implementation

Hey, can you give me some tips on converting this for use with Deno? Is this planned for the future?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.