uri-handler-registry.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. const url = require('url');
  2. const { Emitter, Disposable } = require('event-kit');
  3. // Private: Associates listener functions with URIs from outside the application.
  4. //
  5. // The global URI handler registry maps URIs to listener functions. URIs are mapped
  6. // based on the hostname of the URI; the format is atom://package/command?args.
  7. // The "core" package name is reserved for URIs handled by Pulsar Core (it is not possible
  8. // to register a package with the name "core").
  9. //
  10. // Because URI handling can be triggered from outside the application (e.g. from
  11. // the user's browser), package authors should take great care to ensure that malicious
  12. // activities cannot be performed by an attacker. A good rule to follow is that
  13. // **URI handlers should not take action on behalf of the user**. For example, clicking
  14. // a link to open a pane item that prompts the user to install a package is okay;
  15. // automatically installing the package right away is not.
  16. //
  17. // Packages can register their desire to handle URIs via a special key in their
  18. // `package.json` called "uriHandler". The value of this key should be an object
  19. // that contains, at minimum, a key named "method". This is the name of the method
  20. // on your package object that Pulsar will call when it receives a URI your package
  21. // is responsible for handling. It will pass the parsed URI as the first argument (by using
  22. // [Node's `url.parse(uri, true)`](https://nodejs.org/docs/latest/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost))
  23. // and the raw URI string as the second argument.
  24. //
  25. // By default, Pulsar will defer activation of your package until a URI it needs to handle
  26. // is triggered. If you need your package to activate right away, you can add
  27. // `"deferActivation": false` to your "uriHandler" configuration object. When activation
  28. // is deferred, once Pulsar receives a request for a URI in your package's namespace, it will
  29. // activate your package and then call `methodName` on it as before.
  30. //
  31. // If your package specifies a deprecated `urlMain` property, you cannot register URI handlers
  32. // via the `uriHandler` key.
  33. //
  34. // ## Example
  35. //
  36. // Here is a sample package that will be activated and have its `handleURI` method called
  37. // when a URI beginning with `atom://my-package` is triggered:
  38. //
  39. // `package.json`:
  40. //
  41. // ```javascript
  42. // {
  43. // "name": "my-package",
  44. // "main": "./lib/my-package.js",
  45. // "uriHandler": {
  46. // "method": "handleURI"
  47. // }
  48. // }
  49. // ```
  50. //
  51. // `lib/my-package.js`
  52. //
  53. // ```javascript
  54. // module.exports = {
  55. // activate: function() {
  56. // // code to activate your package
  57. // }
  58. //
  59. // handleURI(parsedUri, rawUri) {
  60. // // parse and handle uri
  61. // }
  62. // }
  63. // ```
  64. module.exports = class URIHandlerRegistry {
  65. constructor(maxHistoryLength = 50) {
  66. this.registrations = new Map();
  67. this.history = [];
  68. this.maxHistoryLength = maxHistoryLength;
  69. this._id = 0;
  70. this.emitter = new Emitter();
  71. }
  72. registerHostHandler(host, callback) {
  73. if (typeof callback !== 'function') {
  74. throw new Error(
  75. 'Cannot register a URI host handler with a non-function callback'
  76. );
  77. }
  78. if (this.registrations.has(host)) {
  79. throw new Error(
  80. `There is already a URI host handler for the host ${host}`
  81. );
  82. } else {
  83. this.registrations.set(host, callback);
  84. }
  85. return new Disposable(() => {
  86. this.registrations.delete(host);
  87. });
  88. }
  89. async handleURI(uri) {
  90. const parsed = url.parse(uri, true);
  91. const { protocol, slashes, auth, port, host } = parsed;
  92. if (protocol !== 'atom:' || slashes !== true || auth || port) {
  93. throw new Error(
  94. `URIHandlerRegistry#handleURI asked to handle an invalid URI: ${uri}`
  95. );
  96. }
  97. const registration = this.registrations.get(host);
  98. const historyEntry = { id: ++this._id, uri: uri, handled: false, host };
  99. try {
  100. if (registration) {
  101. historyEntry.handled = true;
  102. await registration(parsed, uri);
  103. }
  104. } finally {
  105. this.history.unshift(historyEntry);
  106. if (this.history.length > this.maxHistoryLength) {
  107. this.history.length = this.maxHistoryLength;
  108. }
  109. this.emitter.emit('history-change');
  110. }
  111. }
  112. getRecentlyHandledURIs() {
  113. return this.history;
  114. }
  115. onHistoryChange(cb) {
  116. return this.emitter.on('history-change', cb);
  117. }
  118. destroy() {
  119. this.emitter.dispose();
  120. this.registrations = new Map();
  121. this.history = [];
  122. this._id = 0;
  123. }
  124. };