This repository has been archived by the owner on Aug 1, 2022. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): open Upstream via radicle:// (#1652)
* Handle custom protocol on macOS * Handle custom protocol on Linux * Implement custom protocol safeguards according to spec Signed-off-by: Rūdolfs Ošiņš <rudolfs@osins.org> Co-authored-by: Thomas Scholtes <thomas@monadic.xyz>
- Loading branch information
Showing
13 changed files
with
361 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import * as commands from "../support/commands"; | ||
import * as ipcStub from "../support/ipc-stub"; | ||
import * as ipcTypes from "../../native/ipc-types"; | ||
|
||
context("deep linking", () => { | ||
beforeEach(() => { | ||
commands.resetProxyState(); | ||
commands.onboardUser(); | ||
cy.visit("./public/index.html"); | ||
}); | ||
|
||
context("when passing in a valid URL", () => { | ||
it("opens the search modal and pre-fills the input field with the Radicle ID", () => { | ||
ipcStub.getStubs().then(stubs => { | ||
stubs.sendMessage({ | ||
kind: ipcTypes.MainMessageKind.CUSTOM_PROTOCOL_INVOCATION, | ||
data: { | ||
url: | ||
"radicle://link/v0/rad:git:hnrkjm5z3rwae9g3n6jhyo6kzh9eup5ku5odo", | ||
}, | ||
}); | ||
}); | ||
|
||
commands | ||
.pick("search-modal", "search-input") | ||
.should("have.value", "rad:git:hnrkjm5z3rwae9g3n6jhyo6kzh9eup5ku5odo"); | ||
commands | ||
.pick("search-modal", "follow-toggle") | ||
.should("contain", "Follow"); | ||
}); | ||
}); | ||
|
||
context("when passing in an invalid URL", () => { | ||
it("shows an error notification", () => { | ||
ipcStub.getStubs().then(stubs => { | ||
stubs.sendMessage({ | ||
kind: ipcTypes.MainMessageKind.CUSTOM_PROTOCOL_INVOCATION, | ||
data: { | ||
url: "radicle://THIS_IS_NOT_A_VALID_URN", | ||
}, | ||
}); | ||
}); | ||
|
||
commands | ||
.pick("notification") | ||
.should("contain", "Could not parse the provided URL"); | ||
|
||
ipcStub.getStubs().then(stubs => { | ||
stubs.sendMessage({ | ||
kind: ipcTypes.MainMessageKind.CUSTOM_PROTOCOL_INVOCATION, | ||
data: { | ||
url: "radicle://ethereum/v0/", | ||
}, | ||
}); | ||
}); | ||
|
||
commands | ||
.pick("notification") | ||
.should( | ||
"contain", | ||
`The custom protocol namespace "ethereum" is not supported` | ||
); | ||
|
||
ipcStub.getStubs().then(stubs => { | ||
stubs.sendMessage({ | ||
kind: ipcTypes.MainMessageKind.CUSTOM_PROTOCOL_INVOCATION, | ||
data: { | ||
url: "radicle://link/v1/", | ||
}, | ||
}); | ||
}); | ||
|
||
commands | ||
.pick("notification") | ||
.should("contain", "The custom protocol version v1 is not supported"); | ||
|
||
ipcStub.getStubs().then(stubs => { | ||
stubs.sendMessage({ | ||
kind: ipcTypes.MainMessageKind.CUSTOM_PROTOCOL_INVOCATION, | ||
data: { | ||
url: "radicle://link/v0/", | ||
}, | ||
}); | ||
}); | ||
|
||
commands | ||
.pick("notification") | ||
.should("contain", "The provided URL does not contain a Radicle ID"); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import * as sinon from "sinon"; | ||
import { handleCustomProtocolInvocation } from "./nativeCustomProtocolHandler"; | ||
|
||
jest.useFakeTimers("modern"); | ||
|
||
beforeEach(() => { | ||
jest.runAllTimers(); | ||
}); | ||
|
||
describe("handleCustomProtocolInvocation", () => { | ||
it("passes valid URLs", () => { | ||
const callback = sinon.stub(); | ||
handleCustomProtocolInvocation( | ||
`radicle://link/v1/rad:git:hnrkj7qjesxx4omprbj5c6apd97ebc9e5izoo`, | ||
callback | ||
); | ||
expect(callback.callCount).toEqual(1); | ||
}); | ||
|
||
it("rejects empty strings", () => { | ||
const callback = sinon.stub(); | ||
handleCustomProtocolInvocation("", callback); | ||
expect(callback.callCount).toEqual(0); | ||
}); | ||
|
||
it("passes URLs that are exactly 1024 bytes long", () => { | ||
const callback = sinon.stub(); | ||
handleCustomProtocolInvocation(`radicle://${"x".repeat(1014)}`, callback); | ||
expect(callback.callCount).toEqual(1); | ||
}); | ||
|
||
it("rejects handling URLs longer than 1024 bytes", () => { | ||
const callback = sinon.stub(); | ||
handleCustomProtocolInvocation(`radicle://${"x".repeat(1015)}`, callback); | ||
expect(callback.callCount).toEqual(0); | ||
}); | ||
|
||
it("rejects URLs that are not prefixed with radicle://", () => { | ||
const callback = sinon.stub(); | ||
handleCustomProtocolInvocation("upstream://", callback); | ||
expect(callback.callCount).toEqual(0); | ||
}); | ||
|
||
it("throttles incomming requests", () => { | ||
const callback = sinon.stub(); | ||
handleCustomProtocolInvocation("radicle://", callback); | ||
handleCustomProtocolInvocation("radicle://", callback); | ||
handleCustomProtocolInvocation("radicle://", callback); | ||
expect(callback.callCount).toEqual(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import lodash from "lodash"; | ||
|
||
const THROTTLE_TIMEOUT = 1000; // 1 second | ||
|
||
export const handleCustomProtocolInvocation: ( | ||
url: string, | ||
callback: (url: string) => void | ||
) => void = lodash.throttle( | ||
(url, callback) => { | ||
if ( | ||
typeof url !== "string" || | ||
url.length === 0 || | ||
Buffer.byteLength(url, "utf8") > 1024 || | ||
!url.toLowerCase().match(/^radicle:\/\//) | ||
) { | ||
return; | ||
} | ||
|
||
callback(url); | ||
}, | ||
THROTTLE_TIMEOUT, | ||
{ trailing: false } | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.