diff --git a/README.md b/README.md index df222fa..36d521a 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ gamja settings can be overridden using URL query parameters: - `server`: path or URL to the WebSocket server - `nick`: nickname - `channels`: comma-separated list of channels to join (`#` needs to be escaped) +- `open`: [IRC URL] to open Alternatively, the channels can be set with the URL fragment (ie, by just appending the channel name to the gamja URL). @@ -134,3 +135,4 @@ Copyright (C) 2020 The gamja Contributors [mailing list]: https://lists.sr.ht/~emersion/public-inbox [issue tracker]: https://todo.sr.ht/~emersion/gamja [Parcel]: https://parceljs.org +[IRC URL]: https://datatracker.ietf.org/doc/html/draft-butcher-irc-url-04 diff --git a/components/app.js b/components/app.js index 44a8c7b..471c974 100644 --- a/components/app.js +++ b/components/app.js @@ -146,6 +146,11 @@ export default class App extends Component { buffer = createRef(); composer = createRef(); switchToChannel = null; + /** + * Parsed irc:// URL to automatically open. The user will be prompted for + * confirmation for security reasons. + */ + autoOpenURL = null; constructor(props) { super(props); @@ -243,6 +248,10 @@ export default class App extends Component { connectParams.autojoin = queryParams.channels.split(","); } + if (typeof queryParams.open === "string") { + this.autoOpenURL = irc.parseURL(queryParams.open); + } + if (window.location.hash) { connectParams.autojoin = window.location.hash.split(","); } @@ -798,6 +807,12 @@ export default class App extends Component { params: [join.join(",")], }); } + + let serverHost = bouncerNetwork ? bouncerNetwork.host : ""; + if (this.autoOpenURL && serverHost === this.autoOpenURL.host) { + this.openURL(this.autoOpenURL); + this.autoOpenURL = null; + } case "JOIN": channel = msg.params[0]; @@ -872,6 +887,24 @@ export default class App extends Component { } }); break; + case "BATCH": + if (!msg.params[0].startsWith("-")) { + break; + } + let name = msg.params[0].slice(1); + let batch = client.batches.get(name); + if (!batch || batch.type !== "soju.im/bouncer-networks") { + break; + } + + // We've received a BOUNCER NETWORK batch. If we have a URL to + // auto-open and no existing network matches it, ask the user to + // create a new network. + if (this.autoOpenURL && this.autoOpenURL.host && !this.findBouncerNetIDByHost(this.autoOpenURL.host)) { + this.openURL(this.autoOpenURL); + this.autoOpenURL = null; + } + break; default: if (irc.isError(msg.command) && msg.command != irc.ERR_NOMOTD) { let description = msg.params[msg.params.length - 1]; @@ -903,31 +936,44 @@ export default class App extends Component { } handleChannelClick(event) { - let url = irc.parseURL(event.target.href); + let handled = this.openURL(event.target.href); + if (handled) { + event.preventDefault(); + } + } + + findBouncerNetIDByHost(host) { + for (let [id, bouncerNetwork] of this.state.bouncerNetworks) { + if (bouncerNetwork.host === host) { + return id; + } + } + return null; + } + + openURL(url) { + if (typeof url === "string") { + url = irc.parseURL(url); + } if (!url) { - return; + return false; } let serverID; if (!url.host) { serverID = State.getActiveServerID(this.state); } else { - let bouncerNetID; - for (let [id, bouncerNetwork] of this.state.bouncerNetworks) { - if (bouncerNetwork.host === url.host) { - bouncerNetID = id; - break; - } - } + let bouncerNetID = this.findBouncerNetIDByHost(url.host); if (!bouncerNetID) { // Open dialog to create network if bouncer let client = this.clients.values().next().value; - if (client && client.enabledCaps["soju.im/bouncer-networks"]) { - event.preventDefault(); - let params = { host: url.host }; - this.openDialog("network", { params, autojoin: url.entity }); + if (!client || !client.enabledCaps["soju.im/bouncer-networks"]) { + return false; } - return; + + let params = { host: url.host }; + this.openDialog("network", { params, autojoin: url.entity }); + return true; } for (let [id, server] of this.state.servers) { @@ -938,17 +984,16 @@ export default class App extends Component { } } if (!serverID) { - return; + return false; } - event.preventDefault(); - let buf = State.getBuffer(this.state, { server: serverID, name: url.entity || SERVER_BUFFER }); if (buf) { this.switchBuffer(buf.id); } else { this.openDialog("join", { server: serverID, channel: url.entity }); } + return true; } handleNickClick(nick) {