Browse Source

docs: documentation of NetworkService-based protocol module (#18952)

* docs: NetworkService-based protocol module

* docs: separate ProtocolRequest

* docs: separate ProtocolResponse

* docs: fix lint warning

* docs: fix electron.d.ts

* fix: print deprecation warnings for protocol module

* docs: fix links

* Apply suggestions from code review

Co-Authored-By: Felix Rieseberg <[email protected]>

* Apply suggestions from code review

Co-Authored-By: Samuel Attard <[email protected]>

* Do not publish NetworkService changes draft

* Apply suggestions from code review

Co-Authored-By: Samuel Attard <[email protected]>

* docs: filePath must be absolute
Cheng Zhao 5 years ago
parent
commit
0a9438dbba

+ 61 - 0
docs/api/breaking-changes-ns.md

@@ -0,0 +1,61 @@
+# Breaking changes (NetworkService) (Draft)
+
+This document describes changes to Electron APIs after migrating network code
+to NetworkService API.
+
+We don't currently have an estimate of when we will enable `NetworkService` by
+default in Electron, but as Chromium is already removing non-`NetworkService`
+code, we might switch before Electron 10.
+
+The content of this document should be moved to `breaking-changes.md` once we have
+determined when to enable `NetworkService` in Electron.
+
+## Planned Breaking API Changes
+
+### `protocol.unregisterProtocol`
+### `protocol.uninterceptProtocol`
+
+The APIs are now synchronous and the optional callback is no longer needed.
+
+```javascript
+// Deprecated
+protocol.unregisterProtocol(scheme, () => { /* ... */ })
+// Replace with
+protocol.unregisterProtocol(scheme)
+```
+
+### `protocol.registerFileProtocol`
+### `protocol.registerBufferProtocol`
+### `protocol.registerStringProtocol`
+### `protocol.registerHttpProtocol`
+### `protocol.registerStreamProtocol`
+### `protocol.interceptFileProtocol`
+### `protocol.interceptStringProtocol`
+### `protocol.interceptBufferProtocol`
+### `protocol.interceptHttpProtocol`
+### `protocol.interceptStreamProtocol`
+
+The APIs are now synchronous and the optional callback is no longer needed.
+
+```javascript
+// Deprecated
+protocol.registerFileProtocol(scheme, handler, () => { /* ... */ })
+// Replace with
+protocol.registerFileProtocol(scheme, handler)
+```
+
+The registered or intercepted protocol does not have effect on current page
+until navigation happens.
+
+### `protocol.isProtocolHandled`
+
+This API is deprecated and users should use `protocol.isProtocolRegistered`
+and `protocol.isProtocolIntercepted` instead.
+
+```javascript
+// Deprecated
+protocol.isProtocolHandled(scheme).then(() => { /* ... */ })
+// Replace with
+const isRegistered = protocol.isProtocolRegistered(scheme)
+const isIntercepted = protocol.isProtocolIntercepted(scheme)
+```

+ 309 - 0
docs/api/protocol-ns.md

@@ -0,0 +1,309 @@
+# protocol (NetworkService) (Draft)
+
+This document describes the new protocol APIs based on the [NetworkService](https://www.chromium.org/servicification).
+
+We don't currently have an estimate of when we will enable the `NetworkService` by
+default in Electron, but as Chromium is already removing non-`NetworkService`
+code, we will probably switch before Electron 10.
+
+The content of this document should be moved to `protocol.md` after we have
+enabled the `NetworkService` by default in Electron.
+
+> Register a custom protocol and intercept existing protocol requests.
+
+Process: [Main](../glossary.md#main-process)
+
+An example of implementing a protocol that has the same effect as the
+`file://` protocol:
+
+```javascript
+const { app, protocol } = require('electron')
+const path = require('path')
+
+app.on('ready', () => {
+  protocol.registerFileProtocol('atom', (request, callback) => {
+    const url = request.url.substr(7)
+    callback({ path: path.normalize(`${__dirname}/${url}`) })
+  })
+})
+```
+
+**Note:** All methods unless specified can only be used after the `ready` event
+of the `app` module gets emitted.
+
+## Using `protocol` with a custom `partition` or `session`
+
+A protocol is registered to a specific Electron [`session`](./session.md)
+object. If you don't specify a session, then your `protocol` will be applied to
+the default session that Electron uses. However, if you define a `partition` or
+`session` on your `browserWindow`'s `webPreferences`, then that window will use
+a different session and your custom protocol will not work if you just use
+`electron.protocol.XXX`.
+
+To have your custom protocol work in combination with a custom session, you need
+to register it to that session explicitly.
+
+```javascript
+const { session, app, protocol } = require('electron')
+const path = require('path')
+
+app.on('ready', () => {
+  const partition = 'persist:example'
+  const ses = session.fromPartition(partition)
+
+  ses.protocol.registerFileProtocol('atom', (request, callback) => {
+    const url = request.url.substr(7)
+    callback({ path: path.normalize(`${__dirname}/${url}`) })
+  })
+
+  mainWindow = new BrowserWindow({ webPreferences: { partition } })
+})
+```
+
+## Methods
+
+The `protocol` module has the following methods:
+
+### `protocol.registerSchemesAsPrivileged(customSchemes)`
+
+* `customSchemes` [CustomScheme[]](structures/custom-scheme.md)
+
+**Note:** This method can only be used before the `ready` event of the `app`
+module gets emitted and can be called only once.
+
+Registers the `scheme` as standard, secure, bypasses content security policy for
+resources, allows registering ServiceWorker and supports fetch API. Specify a
+privilege with the value of `true` to enable the capability.
+
+An example of registering a privileged scheme, that bypasses Content Security
+Policy:
+
+```javascript
+const { protocol } = require('electron')
+protocol.registerSchemesAsPrivileged([
+  { scheme: 'foo', privileges: { bypassCSP: true } }
+])
+```
+
+A standard scheme adheres to what RFC 3986 calls [generic URI
+syntax](https://tools.ietf.org/html/rfc3986#section-3). For example `http` and
+`https` are standard schemes, while `file` is not.
+
+Registering a scheme as standard allows relative and absolute resources to
+be resolved correctly when served. Otherwise the scheme will behave like the
+`file` protocol, but without the ability to resolve relative URLs.
+
+For example when you load following page with custom protocol without
+registering it as standard scheme, the image will not be loaded because
+non-standard schemes can not recognize relative URLs:
+
+```html
+<body>
+  <img src='test.png'>
+</body>
+```
+
+Registering a scheme as standard will allow access to files through the
+[FileSystem API][file-system-api]. Otherwise the renderer will throw a security
+error for the scheme.
+
+By default web storage apis (localStorage, sessionStorage, webSQL, indexedDB,
+cookies) are disabled for non standard schemes. So in general if you want to
+register a custom protocol to replace the `http` protocol, you have to register
+it as a standard scheme.
+
+### `protocol.registerFileProtocol(scheme, handler)`
+
+* `scheme` String
+* `handler` Function
+  * `request` ProtocolRequest
+  * `callback` Function
+    * `response` (String | [ProtocolResponse](structures/protocol-response.md))
+
+Registers a protocol of `scheme` that will send a file as the response. The
+`handler` will be called with `request` and `callback` where `request` is
+an incoming request for the `scheme`.
+
+To handle the `request`, the `callback` should be called with either the file's
+path or an object that has a `path` property, e.g. `callback(filePath)` or
+`callback({ path: filePath })`. The `filePath` must be an absolute path.
+
+By default the `scheme` is treated like `http:`, which is parsed differently
+from protocols that follow the "generic URI syntax" like `file:`.
+
+### `protocol.registerBufferProtocol(scheme, handler)`
+
+* `scheme` String
+* `handler` Function
+  * `request` ProtocolRequest
+  * `callback` Function
+    * `response` (Buffer | [ProtocolResponse](structures/protocol-response.md))
+
+Registers a protocol of `scheme` that will send a `Buffer` as a response.
+
+The usage is the same with `registerFileProtocol`, except that the `callback`
+should be called with either a `Buffer` object or an object that has the `data`
+property.
+
+Example:
+
+```javascript
+protocol.registerBufferProtocol('atom', (request, callback) => {
+  callback({ mimeType: 'text/html', data: Buffer.from('<h5>Response</h5>') })
+})
+```
+
+### `protocol.registerStringProtocol(scheme, handler)`
+
+* `scheme` String
+* `handler` Function
+  * `request` ProtocolRequest
+  * `callback` Function
+    * `response` (String | [ProtocolResponse](structures/protocol-response.md))
+
+Registers a protocol of `scheme` that will send a `String` as a response.
+
+The usage is the same with `registerFileProtocol`, except that the `callback`
+should be called with either a `String` or an object that has the `data`
+property.
+
+### `protocol.registerHttpProtocol(scheme, handler)`
+
+* `scheme` String
+* `handler` Function
+  * `request` ProtocolRequest
+  * `callback` Function
+    * `response` ProtocolResponse
+
+Registers a protocol of `scheme` that will send an HTTP request as a response.
+
+The usage is the same with `registerFileProtocol`, except that the `callback`
+should be called with an object that has the `url` property.
+
+### `protocol.registerStreamProtocol(scheme, handler)`
+
+* `scheme` String
+* `handler` Function
+  * `request` ProtocolRequest
+  * `callback` Function
+    * `response` (ReadableStream | [ProtocolResponse](structures/protocol-response.md))
+
+Registers a protocol of `scheme` that will send a stream as a response.
+
+The usage is the same with `registerFileProtocol`, except that the
+`callback` should be called with either a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable) object or an object that
+has the `data` property.
+
+Example:
+
+```javascript
+const { protocol } = require('electron')
+const { PassThrough } = require('stream')
+
+function createStream (text) {
+  const rv = new PassThrough() // PassThrough is also a Readable stream
+  rv.push(text)
+  rv.push(null)
+  return rv
+}
+
+protocol.registerStreamProtocol('atom', (request, callback) => {
+  callback({
+    statusCode: 200,
+    headers: {
+      'content-type': 'text/html'
+    },
+    data: createStream('<h5>Response</h5>')
+  })
+})
+```
+
+It is possible to pass any object that implements the readable stream API (emits
+`data`/`end`/`error` events). For example, here's how a file could be returned:
+
+```javascript
+protocol.registerStreamProtocol('atom', (request, callback) => {
+  callback(fs.createReadStream('index.html'))
+})
+```
+
+### `protocol.unregisterProtocol(scheme)`
+
+* `scheme` String
+
+Unregisters the custom protocol of `scheme`.
+
+### `protocol.isProtocolRegistered(scheme)`
+
+* `scheme` String
+
+Returns `Boolean` - Whether `scheme` is already registered.
+
+### `protocol.interceptFileProtocol(scheme, handler)`
+
+* `scheme` String
+* `handler` Function
+  * `request` ProtocolRequest
+  * `callback` Function
+    * `response` (String | [ProtocolResponse](structures/protocol-response.md))
+
+Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
+which sends a file as a response.
+
+### `protocol.interceptStringProtocol(scheme, handler)`
+
+* `scheme` String
+* `handler` Function
+  * `request` ProtocolRequest
+  * `callback` Function
+    * `response` (String | [ProtocolResponse](structures/protocol-response.md))
+
+Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
+which sends a `String` as a response.
+
+### `protocol.interceptBufferProtocol(scheme, handler)`
+
+* `scheme` String
+* `handler` Function
+  * `request` ProtocolRequest
+  * `callback` Function
+    * `response` (Buffer | [ProtocolResponse](structures/protocol-response.md))
+
+Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
+which sends a `Buffer` as a response.
+
+### `protocol.interceptHttpProtocol(scheme, handler)`
+
+* `scheme` String
+* `handler` Function
+  * `request` ProtocolRequest
+  * `callback` Function
+    * `response` ProtocolResponse
+
+Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
+which sends a new HTTP request as a response.
+
+### `protocol.interceptStreamProtocol(scheme, handler)`
+
+* `scheme` String
+* `handler` Function
+  * `request` ProtocolRequest
+  * `callback` Function
+    * `response` (ReadableStream | [ProtocolResponse](structures/protocol-response.md))
+
+Same as `protocol.registerStreamProtocol`, except that it replaces an existing
+protocol handler.
+
+### `protocol.uninterceptProtocol(scheme)`
+
+* `scheme` String
+
+Remove the interceptor installed for `scheme` and restore its original handler.
+
+### `protocol.isProtocolIntercepted(scheme)`
+
+* `scheme` String
+
+Returns `Boolean` - Whether `scheme` is already intercepted.
+
+[file-system-api]: https://developer.mozilla.org/en-US/docs/Web/API/LocalFileSystem

+ 6 - 0
docs/api/structures/protocol-request.md

@@ -0,0 +1,6 @@
+# ProtocolRequest Object
+
+* `url` String
+* `referrer` String
+* `method` String
+* `uploadData` [UploadData[]](upload-data.md) (optional)

+ 36 - 0
docs/api/structures/protocol-response.md

@@ -0,0 +1,36 @@
+# ProtocolResponse Object
+
+* `error` Integer (optional) - When assigned, the `request` will fail with the
+  `error` number . For the available error numbers you can use, please see the
+  [net error list][net-error].
+* `statusCode` Number (optional) - The HTTP response code, default is 200.
+* `charset` String (optional) - The charset of response body, default is
+  `"utf-8"`.
+* `mimeType` String (optional) - The MIME type of response body, default is
+  `"text/html"`. Setting `mimeType` would implicitly set the `content-type`
+  header in response, but if `content-type` is already set in `headers`, the
+  `mimeType` would be ignored.
+* `headers` Record<string, string | string[]> (optional) - An object containing the response headers. The
+  keys must be String, and values must be either String or Array of String.
+* `data` (Buffer | String | ReadableStream) (optional) - The response body. When
+  returning stream as response, this is a Node.js readable stream representing
+  the response body. When returning `Buffer` as response, this is a `Buffer`.
+  When returning `String` as response, this is a `String`. This is ignored for
+  other types of responses.
+* `path` String (optional) - Path to the file which would be sent as response
+  body. This is only used for file responses.
+* `url` String (optional) - Download the `url` and pipe the result as response
+  body. This is only used for URL responses.
+* `referrer` String (optional) - The `referrer` URL. This is only used for file
+  and URL responses.
+* `method` String (optional) - The HTTP `method`. This is only used for file
+  and URL responses.
+* `session` Session (optional) - The session used for requesting URL, by default
+  the HTTP request will reuse the current session. Setting `session` to `null`
+  would use a random independent session. This is only used for URL responses.
+* `uploadData` Object (optional) - The data used as upload data. This is only
+  used for URL responses when `method` is `"POST"`.
+  * `contentType` String - MIME type of the content.
+  * `data` String - Content to be sent.
+
+[net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h

+ 4 - 0
filenames.auto.gni

@@ -4,6 +4,7 @@ auto_filenames = {
     "docs/api/accelerator.md",
     "docs/api/app.md",
     "docs/api/auto-updater.md",
+    "docs/api/breaking-changes-ns.md",
     "docs/api/breaking-changes.md",
     "docs/api/browser-view.md",
     "docs/api/browser-window-proxy.md",
@@ -39,6 +40,7 @@ auto_filenames = {
     "docs/api/power-monitor.md",
     "docs/api/power-save-blocker.md",
     "docs/api/process.md",
+    "docs/api/protocol-ns.md",
     "docs/api/protocol.md",
     "docs/api/remote.md",
     "docs/api/sandbox-option.md",
@@ -92,6 +94,8 @@ auto_filenames = {
     "docs/api/structures/process-memory-info.md",
     "docs/api/structures/process-metric.md",
     "docs/api/structures/product.md",
+    "docs/api/structures/protocol-request.md",
+    "docs/api/structures/protocol-response.md",
     "docs/api/structures/rectangle.md",
     "docs/api/structures/referrer.md",
     "docs/api/structures/remove-client-certificate.md",

+ 2 - 2
package.json

@@ -4,7 +4,7 @@
   "repository": "https://github.com/electron/electron",
   "description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
   "devDependencies": {
-    "@electron/docs-parser": "^0.2.2",
+    "@electron/docs-parser": "^0.3.0",
     "@electron/typescript-definitions": "^8.3.1",
     "@octokit/rest": "^16.3.2",
     "@types/chai": "^4.1.7",
@@ -127,4 +127,4 @@
       "git add filenames.auto.gni"
     ]
   }
-}
+}

+ 15 - 2
shell/browser/api/atom_api_protocol_ns.cc

@@ -9,6 +9,7 @@
 
 #include "base/stl_util.h"
 #include "shell/browser/atom_browser_context.h"
+#include "shell/common/deprecate_util.h"
 #include "shell/common/native_mate_converters/net_converter.h"
 #include "shell/common/native_mate_converters/once_callback.h"
 #include "shell/common/promise_util.h"
@@ -106,8 +107,15 @@ bool ProtocolNS::IsProtocolIntercepted(const std::string& scheme) {
   return base::ContainsKey(intercept_handlers_, scheme);
 }
 
-v8::Local<v8::Promise> ProtocolNS::IsProtocolHandled(
-    const std::string& scheme) {
+v8::Local<v8::Promise> ProtocolNS::IsProtocolHandled(const std::string& scheme,
+                                                     mate::Arguments* args) {
+  node::Environment* env = node::Environment::GetCurrent(args->isolate());
+  EmitDeprecationWarning(
+      env,
+      "The protocol.isProtocolHandled API is deprecated, use "
+      "protocol.isProtocolRegistered or protocol.isProtocolIntercepted "
+      "instead.",
+      "ProtocolDeprecateIsProtocolHandled");
   util::Promise promise(isolate());
   promise.Resolve(IsProtocolRegistered(scheme) ||
                   IsProtocolIntercepted(scheme) ||
@@ -126,6 +134,11 @@ void ProtocolNS::HandleOptionalCallback(mate::Arguments* args,
                                         ProtocolError error) {
   CompletionCallback callback;
   if (args->GetNext(&callback)) {
+    node::Environment* env = node::Environment::GetCurrent(args->isolate());
+    EmitDeprecationWarning(
+        env,
+        "The callback argument of protocol module APIs is no longer needed.",
+        "ProtocolDeprecateCallback");
     if (error == ProtocolError::OK)
       callback.Run(v8::Null(args->isolate()));
     else

+ 2 - 1
shell/browser/api/atom_api_protocol_ns.h

@@ -65,7 +65,8 @@ class ProtocolNS : public mate::TrackableObject<ProtocolNS> {
   bool IsProtocolIntercepted(const std::string& scheme);
 
   // Old async version of IsProtocolRegistered.
-  v8::Local<v8::Promise> IsProtocolHandled(const std::string& scheme);
+  v8::Local<v8::Promise> IsProtocolHandled(const std::string& scheme,
+                                           mate::Arguments* args);
 
   // Helper for converting old registration APIs to new RegisterProtocol API.
   template <ProtocolType type>

+ 16 - 1
yarn.lock

@@ -22,7 +22,7 @@
   dependencies:
     regenerator-runtime "^0.12.0"
 
-"@electron/docs-parser@^0.2.1", "@electron/docs-parser@^0.2.2":
+"@electron/docs-parser@^0.2.1":
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/@electron/docs-parser/-/docs-parser-0.2.2.tgz#7c9acd6cc10559c86a27bb0653ec13df10955f02"
   integrity sha512-FKXktu5i6cHL+AkvWv34j2lpBXNpqfHN7YwhswcBqRFXsj24phpih/sY2NKx6OrFP9R3ReJeg681/luAf/3k8Q==
@@ -37,6 +37,21 @@
     ora "^3.4.0"
     pretty-ms "^5.0.0"
 
+"@electron/docs-parser@^0.3.0":
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/@electron/docs-parser/-/docs-parser-0.3.0.tgz#cf8c33ed9cebffe7f3463a1e2d60ccf457b52ec6"
+  integrity sha512-/q2et0q6eMDItzv1ZCAH5ZJZY8AFGFkK1+wfAJfdarMDJOVs29pH8b0HjTXo2k+kLGlbC2TROZfuCHRgx+l/EQ==
+  dependencies:
+    "@types/markdown-it" "^0.0.7"
+    chai "^4.2.0"
+    chalk "^2.4.2"
+    fs-extra "^7.0.1"
+    lodash.camelcase "^4.3.0"
+    markdown-it "^8.4.2"
+    minimist "^1.2.0"
+    ora "^3.4.0"
+    pretty-ms "^5.0.0"
+
 "@electron/typescript-definitions@^8.3.1":
   version "8.3.4"
   resolved "https://registry.yarnpkg.com/@electron/typescript-definitions/-/typescript-definitions-8.3.4.tgz#2345e4058e66677792f1bc11662b13e6ccc9a17e"