Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lens:// protocol handling with a routing mechanism #1949

Merged
merged 40 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8430b67
Add lens:// protocol handling with a routing mechanism
Nokel81 Jan 11, 2021
e7ee7fe
Auto-clean extension handlers on deactivation
Nokel81 Jan 19, 2021
2544ba9
Convert the extension API to use an array for registering handlers
Nokel81 Jan 20, 2021
6c4c2b7
Fix unit tests and update docs
Nokel81 Jan 21, 2021
4a98d97
reintroduce renderer routing
Nokel81 Jan 22, 2021
bfc335a
fix unit tests
Nokel81 Jan 25, 2021
2ee1d3d
fix integration tests and markdown
Nokel81 Jan 25, 2021
f970028
change internal hostname to app, revert spacing
Nokel81 Jan 27, 2021
0062395
resolve pr comment
Nokel81 Jan 27, 2021
f8b3605
fix unit and integration tests
Nokel81 Jan 28, 2021
117c833
testing handling on linux
Nokel81 Jan 29, 2021
bce0779
fix package.json
Nokel81 Feb 1, 2021
aa5909b
get protocol handling working for linux
Feb 1, 2021
fed90f3
fix lint
Feb 1, 2021
624b7b5
change open to be a dev dep
Nokel81 Feb 2, 2021
401908f
stop getting logs on test timeout
Nokel81 Feb 2, 2021
7ea0a7f
fix integration tests locally
Nokel81 Feb 2, 2021
8e9a0cc
add waiting for renderer and extensions to load before broadcasting h…
Nokel81 Feb 2, 2021
d3f8ebc
revert bad rebase
Nokel81 Feb 2, 2021
437c75b
killall during teardown
Nokel81 Feb 2, 2021
f2df7c7
improve docs, export types for extensions, skip integration tests
Nokel81 Feb 3, 2021
2bbdd16
add install docs for linux
Nokel81 Feb 3, 2021
3153431
fix unit tests
Nokel81 Feb 3, 2021
76a8a47
improve docs
Nokel81 Feb 4, 2021
4ad4e47
Merge branch 'master' of github.com:lensapp/lens into feature/protoco…
Nokel81 Feb 4, 2021
eb4c98e
remove killall from integration tests
Nokel81 Feb 4, 2021
1c8aa2b
fix bad merge
Nokel81 Feb 4, 2021
c8faded
create dir if necessary (for testing)
Nokel81 Feb 4, 2021
bcbb335
remove linux based protocol handling setup for testing
Nokel81 Feb 4, 2021
96e3c52
Merge branch 'master' of github.com:lensapp/lens into feature/protoco…
Nokel81 Feb 4, 2021
42928a8
Merge branch 'master' of github.com:lensapp/lens into feature/protoco…
Nokel81 Feb 9, 2021
69ce254
remove unneccessary shorter timeouts on before* handles in integratio…
Feb 9, 2021
b48b38c
move remove config to make targets
Feb 9, 2021
74fc8b4
fix lint
Feb 9, 2021
7395d0c
fail tests in before and after handles
Nokel81 Feb 9, 2021
311178f
switch to event emitting renderer being ready
Nokel81 Feb 9, 2021
270d0f7
Add logging and fix renderer:loaded send to main
Nokel81 Feb 10, 2021
65274f0
Merge branch 'master' of github.com:lensapp/lens into feature/protoco…
Nokel81 Feb 18, 2021
c80965e
revert integration test code
Nokel81 Feb 18, 2021
5ee659d
ignore removeing old config
Nokel81 Feb 18, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ else
DETECTED_OS := $(shell uname)
endif

binaries/client:
binaries/client: node_modules
yarn download-bins

node_modules: yarn.lock
Expand Down Expand Up @@ -37,17 +37,24 @@ test: binaries/client
yarn test

.PHONY: integration-linux
integration-linux: build-extension-types build-extensions
integration-linux: binaries/client build-extension-types build-extensions
# ifdef XDF_CONFIG_HOME
# rm -rf ${XDG_CONFIG_HOME}/.config/Lens
# else
# rm -rf ${HOME}/.config/Lens
# endif
yarn build:linux
yarn integration

.PHONY: integration-mac
integration-mac: build-extension-types build-extensions
integration-mac: binaries/client build-extension-types build-extensions
# rm ${HOME}/Library/Application\ Support/Lens
yarn build:mac
yarn integration

.PHONY: integration-win
integration-win: build-extension-types build-extensions
integration-win: binaries/client build-extension-types build-extensions
# rm %APPDATA%/Lens
yarn build:win
yarn integration

Expand Down
3 changes: 2 additions & 1 deletion docs/extensions/guides/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ Each guide or code sample includes the following:
| [Components](components.md) | |
| [KubeObjectListLayout](kube-object-list-layout.md) | |
| [Working with mobx](working-with-mobx.md) | |
| [Protocol Handlers](protocol-handlers.md) | |

## Samples

| Sample | APIs |
| ----- | ----- |
[helloworld](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
[hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
[minikube](https://github.com/lensapp/lens-extension-samples/tree/master/minikube-sample) | LensMainExtension <br> Store.clusterStore <br> Store.workspaceStore |
[styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
[styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
Expand Down
Binary file added docs/extensions/guides/images/routing-diag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
83 changes: 83 additions & 0 deletions docs/extensions/guides/protocol-handlers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Lens Protocol Handlers

Lens has a file association with the `lens://` protocol.
This means that Lens can be opened by external programs by providing a link that has `lens` as its protocol.
Lens provides a routing mechanism that extensions can use to register custom handlers.

## Registering A Protocol Handler

The field `protocolHandlers` exists both on [`LensMainExtension`](extensions/api/classes/lensmainextension/#protocolhandlers) and on [`LensRendererExtension`](extensions/api/classes/lensrendererextension/#protocolhandlers).
This field will be iterated through every time a `lens://` request gets sent to the application.
The `pathSchema` argument must comply with the [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) package's `compileToRegex` function.

Once you have registered a handler it will be called when a user opens a link on their computer.
Handlers will be run in both `main` and `renderer` in parallel with no synchronization between the two processes.
Furthermore, both `main` and `renderer` are routed separately.
In other words, which handler is selected in either process is independent from the list of possible handlers in the other.

Example of registering a handler:

```typescript
import { LensMainExtension, Interface } from "@k8slens/extensions";

function rootHandler(params: Iterface.ProtocolRouteParams) {
console.log("routed to ExampleExtension", params);
}

export default class ExampleExtensionMain extends LensMainExtension {
protocolHandlers = [
pathSchema: "/",
handler: rootHandler,
]
}
```

For testing the routing of URIs the `open` (on macOS) or `xdg-open` (on most linux) CLI utilities can be used.
For the above handler, the following URI would be always routed to it:

```
open lens://extension/example-extension/
```

## Deregistering A Protocol Handler

All that is needed to deregister a handler is to remove it from the array of handlers.

## Routing Algorithm

The routing mechanism for extensions is quite straight forward.
For example consider an extension `example-extension` which is published by the `@mirantis` org.
If it were to register a handler with `"/display/:type"` as its corresponding link then we would match the following URI like this:

![Lens Protocol Link Resolution](images/routing-diag.png)

Once matched, the handler would be called with the following argument (note both `"search"` and `"pathname"` will always be defined):

```json
{
"search": {
"text": "Hello"
},
"pathname": {
"type": "notification"
}
}
```

As the diagram above shows, the search (or query) params are not considered as part of the handler resolution.
If the URI had instead been `lens://extension/@mirantis/example-extension/display/notification/green` then a third (and optional) field will have the rest of the path.
The `tail` field would be filled with `"/green"`.
If multiple `pathSchema`'s match a given URI then the most specific handler will be called.

For example consider the following `pathSchema`'s:

1. `"/"`
1. `"/display"`
1. `"/display/:type"`
1. `"/show/:id"`

The URI sub-path `"/display"` would be routed to #2 since it is an exact match.
On the other hand, the subpath `"/display/notification"` would be routed to #3.

The URI is routed to the most specific matching `pathSchema`.
This way the `"/"` (root) `pathSchema` acts as a sort of catch all or default route if no other route matches.
23 changes: 22 additions & 1 deletion docs/getting-started/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,28 @@ Review the [System Requirements](../supporting/requirements.md) to check if your

See the [Download Lens](https://github.com/lensapp/lens/releases) page for a complete list of available installation options.

After installing Lens manually (not using a package manager file such as `.deb` or `.rpm`) the following will need to be done to allow protocol handling.
This assumes that your linux distribution uses `xdg-open` and the `xdg-*` suite of programs for determining which application can handle custom URIs.

1. Create a file called `lens.desktop` in either `~/.local/share/applications/` or `/usr/share/applications` (if you have permissions and are installing Lens for all users).
1. That file should have the following contents, with `<path/to/executable>` being the absolute path to where you have installed the unpacked `Lens` executable:
```
[Desktop Entry]
Name=Lens
Exec=<path/to/executable> %U
Terminal=false
Type=Application
Icon=lens
StartupWMClass=Lens
Comment=Lens - The Kubernetes IDE
MimeType=x-scheme-handler/lens;
Categories=Network;
```
1. Then run the following command:
```
xdg-settings set default-url-scheme-handler lens lens.desktop
```
1. If that succeeds (exits with code `0`) then your Lens install should be set up to handle `lens://` URIs.

### Snap

Expand All @@ -52,4 +74,3 @@ To stay current with the Lens features, you can review the [release notes](https

- [Add clusters](../clusters/adding-clusters.md)
- [Watch introductory videos](./introductory-videos.md)

17 changes: 14 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"build:linux": "yarn run compile && electron-builder --linux --dir -c.productName=Lens",
"build:mac": "yarn run compile && electron-builder --mac --dir -c.productName=Lens",
"build:win": "yarn run compile && electron-builder --win --dir -c.productName=Lens",
"test": "jest --env=jsdom src $@",
"test": "scripts/test.sh",
"integration": "jest --runInBand integration",
"dist": "yarn run compile && electron-builder --publish onTag",
"dist:win": "yarn run compile && electron-builder --publish onTag --x64 --ia32",
Expand Down Expand Up @@ -170,7 +170,14 @@
"repo": "lens",
"owner": "lensapp"
}
]
],
"protocols": {
"name": "Lens Protocol Handler",
"schemes": [
"lens"
],
"role": "Viewer"
}
},
"lens": {
"extensions": [
Expand All @@ -187,6 +194,7 @@
"@hapi/call": "^8.0.0",
"@hapi/subtext": "^7.0.3",
"@kubernetes/client-node": "^0.12.0",
"abort-controller": "^3.0.0",
"array-move": "^3.0.0",
"await-lock": "^2.1.0",
"byline": "^5.0.0",
Expand All @@ -213,6 +221,7 @@
"mobx-observable-history": "^1.0.3",
"mobx-react": "^6.2.2",
"mock-fs": "^4.12.0",
"moment": "^2.26.0",
"node-pty": "^0.9.0",
"npm": "^6.14.8",
"openid-client": "^3.15.2",
Expand All @@ -232,6 +241,7 @@
"tar": "^6.0.5",
"tcp-port-used": "^1.0.1",
"tempy": "^0.5.0",
"url-parse": "^1.4.7",
"uuid": "^8.3.2",
"win-ca": "^3.2.0",
"winston": "^3.2.1",
Expand Down Expand Up @@ -289,6 +299,7 @@
"@types/tempy": "^0.3.0",
"@types/terser-webpack-plugin": "^3.0.0",
"@types/universal-analytics": "^0.4.4",
"@types/url-parse": "^1.4.3",
"@types/uuid": "^8.3.0",
"@types/webdriverio": "^4.13.0",
"@types/webpack": "^4.41.17",
Expand Down Expand Up @@ -325,10 +336,10 @@
"jest-mock-extended": "^1.0.10",
"make-plural": "^6.2.2",
"mini-css-extract-plugin": "^0.9.0",
"moment": "^2.26.0",
"node-loader": "^0.6.0",
"node-sass": "^4.14.1",
"nodemon": "^2.0.4",
"open": "^7.3.1",
"patch-package": "^6.2.2",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.2.0",
Expand Down
1 change: 1 addition & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jest --env=jsdom ${1:-src}
2 changes: 1 addition & 1 deletion src/common/ipc/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export async function broadcastMessage(channel: string, ...args: any[]) {
view.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...args);
}
} catch (error) {
logger.error("[IPC]: failed to send IPC message", { error });
logger.error("[IPC]: failed to send IPC message", { error: String(error) });
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions src/common/protocol-handler/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Url from "url-parse";

export enum RoutingErrorType {
INVALID_PROTOCOL = "invalid-protocol",
INVALID_HOST = "invalid-host",
INVALID_PATHNAME = "invalid-pathname",
NO_HANDLER = "no-handler",
NO_EXTENSION_ID = "no-ext-id",
MISSING_EXTENSION = "missing-ext",
}

export class RoutingError extends Error {
/**
* Will be set if the routing error originated in an extension route table
*/
public extensionName?: string;

constructor(public type: RoutingErrorType, public url: Url) {
super("routing error");
}

toString(): string {
switch (this.type) {
case RoutingErrorType.INVALID_HOST:
return "invalid host";
case RoutingErrorType.INVALID_PROTOCOL:
return "invalid protocol";
case RoutingErrorType.INVALID_PATHNAME:
return "invalid pathname";
case RoutingErrorType.NO_EXTENSION_ID:
return "no extension ID";
case RoutingErrorType.MISSING_EXTENSION:
return "extension not found";
}
}
}
2 changes: 2 additions & 0 deletions src/common/protocol-handler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./error";
export * from "./router";
Loading