Add "open" URL param

This can be set to an irc:// URL to open. This is useful for
bouncers.
This commit is contained in:
Simon Ser 2021-11-08 12:33:02 +01:00
parent 14031c594b
commit f3c48a3748
2 changed files with 64 additions and 17 deletions

View File

@ -83,6 +83,7 @@ gamja settings can be overridden using URL query parameters:
- `server`: path or URL to the WebSocket server - `server`: path or URL to the WebSocket server
- `nick`: nickname - `nick`: nickname
- `channels`: comma-separated list of channels to join (`#` needs to be escaped) - `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 Alternatively, the channels can be set with the URL fragment (ie, by just
appending the channel name to the gamja URL). 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 [mailing list]: https://lists.sr.ht/~emersion/public-inbox
[issue tracker]: https://todo.sr.ht/~emersion/gamja [issue tracker]: https://todo.sr.ht/~emersion/gamja
[Parcel]: https://parceljs.org [Parcel]: https://parceljs.org
[IRC URL]: https://datatracker.ietf.org/doc/html/draft-butcher-irc-url-04

View File

@ -146,6 +146,11 @@ export default class App extends Component {
buffer = createRef(); buffer = createRef();
composer = createRef(); composer = createRef();
switchToChannel = null; switchToChannel = null;
/**
* Parsed irc:// URL to automatically open. The user will be prompted for
* confirmation for security reasons.
*/
autoOpenURL = null;
constructor(props) { constructor(props) {
super(props); super(props);
@ -243,6 +248,10 @@ export default class App extends Component {
connectParams.autojoin = queryParams.channels.split(","); connectParams.autojoin = queryParams.channels.split(",");
} }
if (typeof queryParams.open === "string") {
this.autoOpenURL = irc.parseURL(queryParams.open);
}
if (window.location.hash) { if (window.location.hash) {
connectParams.autojoin = window.location.hash.split(","); connectParams.autojoin = window.location.hash.split(",");
} }
@ -798,6 +807,12 @@ export default class App extends Component {
params: [join.join(",")], params: [join.join(",")],
}); });
} }
let serverHost = bouncerNetwork ? bouncerNetwork.host : "";
if (this.autoOpenURL && serverHost === this.autoOpenURL.host) {
this.openURL(this.autoOpenURL);
this.autoOpenURL = null;
}
case "JOIN": case "JOIN":
channel = msg.params[0]; channel = msg.params[0];
@ -872,6 +887,24 @@ export default class App extends Component {
} }
}); });
break; 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: default:
if (irc.isError(msg.command) && msg.command != irc.ERR_NOMOTD) { if (irc.isError(msg.command) && msg.command != irc.ERR_NOMOTD) {
let description = msg.params[msg.params.length - 1]; let description = msg.params[msg.params.length - 1];
@ -903,31 +936,44 @@ export default class App extends Component {
} }
handleChannelClick(event) { 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) { if (!url) {
return; return false;
} }
let serverID; let serverID;
if (!url.host) { if (!url.host) {
serverID = State.getActiveServerID(this.state); serverID = State.getActiveServerID(this.state);
} else { } else {
let bouncerNetID; let bouncerNetID = this.findBouncerNetIDByHost(url.host);
for (let [id, bouncerNetwork] of this.state.bouncerNetworks) {
if (bouncerNetwork.host === url.host) {
bouncerNetID = id;
break;
}
}
if (!bouncerNetID) { if (!bouncerNetID) {
// Open dialog to create network if bouncer // Open dialog to create network if bouncer
let client = this.clients.values().next().value; let client = this.clients.values().next().value;
if (client && client.enabledCaps["soju.im/bouncer-networks"]) { if (!client || !client.enabledCaps["soju.im/bouncer-networks"]) {
event.preventDefault(); return false;
let params = { host: url.host };
this.openDialog("network", { params, autojoin: url.entity });
} }
return;
let params = { host: url.host };
this.openDialog("network", { params, autojoin: url.entity });
return true;
} }
for (let [id, server] of this.state.servers) { for (let [id, server] of this.state.servers) {
@ -938,17 +984,16 @@ export default class App extends Component {
} }
} }
if (!serverID) { if (!serverID) {
return; return false;
} }
event.preventDefault();
let buf = State.getBuffer(this.state, { server: serverID, name: url.entity || SERVER_BUFFER }); let buf = State.getBuffer(this.state, { server: serverID, name: url.entity || SERVER_BUFFER });
if (buf) { if (buf) {
this.switchBuffer(buf.id); this.switchBuffer(buf.id);
} else { } else {
this.openDialog("join", { server: serverID, channel: url.entity }); this.openDialog("join", { server: serverID, channel: url.entity });
} }
return true;
} }
handleNickClick(nick) { handleNickClick(nick) {