aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-10-24 16:24:03 +0100
committerIgor Soarez <igor@soarez.org>2026-10-24 16:24:03 +0100
commit1d0698071d51257e5d795671af6eed5b7af1c651 (patch)
treeccb76220c2595d0453e123c7c00473b93c8f076c
parentd9df76f4fcf6c866e7907878dafb9f7cd969ad4f (diff)
pirc-extension 0.1.3v0.1.3
-rw-r--r--README.md14
-rw-r--r--index.js28
-rw-r--r--package.json2
3 files changed, 32 insertions, 12 deletions
diff --git a/README.md b/README.md
index 907da97..adf34e6 100644
--- a/README.md
+++ b/README.md
@@ -26,19 +26,27 @@ relay.
## Enrol
-On the relay, issue a host token (`pirc-relay add-host --label <name>`).
In pi:
```
-/pirc enrol wss://<relay-address>:<port> <token>
+/pirc enrol wss://<relay-address>:<port>
```
+The footer reads "pirc: awaiting approval" until the relay's operator
+approves the host: on the relay, `pirc-relay pending` lists the hosts
+waiting, and `pirc-relay approve <key>` admits one. Compare the key with the
+one `/pirc status` shows here: the label is whatever the host claims.
+
+Or, to skip the approval, issue a host token on the relay first
+(`pirc-relay add-host --label <name>`) and give it to the command:
+`/pirc enrol wss://<relay-address>:<port> <token>`.
+
`/pirc status` shows the connection, and `/pirc reconnect` reconnects.
The enrolment is saved in `~/.config/pirc/host.json` (or the file
`PIRC_CONFIG` names), with the host's key beside it in `host.key`; every pi
process on the host uses the same key. `PIRC_RELAY` overrides the relay
URL, and `PIRC_TOKEN` supplies a registration token, so a host can be
-enrolled without the command.
+enrolled without the command; a relay URL without a token asks for approval.
If the relay's certificate is self-signed or from a private CA, set
`NODE_EXTRA_CA_CERTS` to the CA's PEM file in the environment pi starts in.
diff --git a/index.js b/index.js
index 5d23e3c..d7ff2b8 100644
--- a/index.js
+++ b/index.js
@@ -1,4 +1,4 @@
-// PiRC extension 0.1.1. Third-party code in this file: @bufbuild/protobuf@2.16.0, ws@8.22.0; see NOTICE.
+// PiRC extension 0.1.3. Third-party code in this file: @bufbuild/protobuf@2.16.0, ws@8.22.0; see NOTICE.
import { createRequire as __pircCreateRequire } from "node:module";
const require = __pircCreateRequire(import.meta.url);
var __create = Object.create;
@@ -7133,7 +7133,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
var wrapper_default = import_websocket.default;
// protocol/gen/pirc/v1alpha1/common_pb.ts
-var file_pirc_v1alpha1_common = /* @__PURE__ */ fileDesc("ChpwaXJjL3YxYWxwaGExL2NvbW1vbi5wcm90bxINcGlyYy52MWFscGhhMSJHCgpTZXNzaW9uUmVmEhAKCGhvc3Rfa2V5GAEgASgMEhIKCnNlc3Npb25faWQYAiABKAkSEwoLaW5zdGFuY2VfaWQYAyABKAkiVAoFRXJyb3ISJgoEY29kZRgBIAEoDjIYLnBpcmMudjFhbHBoYTEuRXJyb3JDb2RlEg8KB21lc3NhZ2UYAiABKAkSEgoKcmVxdWVzdF9pZBgDIAEoCSqZAwoJRXJyb3JDb2RlEhoKFkVSUk9SX0NPREVfVU5TUEVDSUZJRUQQABIeChpFUlJPUl9DT0RFX1VOQVVUSEVOVElDQVRFRBABEiAKHEVSUk9SX0NPREVfUEVSTUlTU0lPTl9ERU5JRUQQAhIiCh5FUlJPUl9DT0RFX1ZFUlNJT05fVU5TVVBQT1JURUQQAxIWChJFUlJPUl9DT0RFX0lOVkFMSUQQBBIYChRFUlJPUl9DT0RFX1RPT19MQVJHRRAFEiAKHEVSUk9SX0NPREVfU0VTU0lPTl9OT1RfRk9VTkQQBhIbChdFUlJPUl9DT0RFX1NFU1NJT05fR09ORRAHEhMKD0VSUk9SX0NPREVfQlVTWRAIEhYKEkVSUk9SX0NPREVfVElNRU9VVBAJEhwKGEVSUk9SX0NPREVfVE9LRU5fSU5WQUxJRBAKEhsKF0VSUk9SX0NPREVfUkFURV9MSU1JVEVEEAsSFwoTRVJST1JfQ09ERV9JTlRFUk5BTBAMEhgKFEVSUk9SX0NPREVfUElfRkFJTEVEEA1CrAEKEWNvbS5waXJjLnYxYWxwaGExQgtDb21tb25Qcm90b1ABWjVwaXJjLmRldi9yZWxheS9pbnRlcm5hbC9wYi9waXJjL3YxYWxwaGExO3BpcmN2MWFscGhhMaICA1BYWKoCDVBpcmMuVjFhbHBoYTHKAg1QaXJjXFYxYWxwaGEx4gIZUGlyY1xWMWFscGhhMVxHUEJNZXRhZGF0YeoCDlBpcmM6OlYxYWxwaGExYgZwcm90bzM");
+var file_pirc_v1alpha1_common = /* @__PURE__ */ fileDesc("ChpwaXJjL3YxYWxwaGExL2NvbW1vbi5wcm90bxINcGlyYy52MWFscGhhMSJHCgpTZXNzaW9uUmVmEhAKCGhvc3Rfa2V5GAEgASgMEhIKCnNlc3Npb25faWQYAiABKAkSEwoLaW5zdGFuY2VfaWQYAyABKAkiVAoFRXJyb3ISJgoEY29kZRgBIAEoDjIYLnBpcmMudjFhbHBoYTEuRXJyb3JDb2RlEg8KB21lc3NhZ2UYAiABKAkSEgoKcmVxdWVzdF9pZBgDIAEoCSq6AwoJRXJyb3JDb2RlEhoKFkVSUk9SX0NPREVfVU5TUEVDSUZJRUQQABIeChpFUlJPUl9DT0RFX1VOQVVUSEVOVElDQVRFRBABEiAKHEVSUk9SX0NPREVfUEVSTUlTU0lPTl9ERU5JRUQQAhIiCh5FUlJPUl9DT0RFX1ZFUlNJT05fVU5TVVBQT1JURUQQAxIWChJFUlJPUl9DT0RFX0lOVkFMSUQQBBIYChRFUlJPUl9DT0RFX1RPT19MQVJHRRAFEiAKHEVSUk9SX0NPREVfU0VTU0lPTl9OT1RfRk9VTkQQBhIbChdFUlJPUl9DT0RFX1NFU1NJT05fR09ORRAHEhMKD0VSUk9SX0NPREVfQlVTWRAIEhYKEkVSUk9SX0NPREVfVElNRU9VVBAJEhwKGEVSUk9SX0NPREVfVE9LRU5fSU5WQUxJRBAKEhsKF0VSUk9SX0NPREVfUkFURV9MSU1JVEVEEAsSFwoTRVJST1JfQ09ERV9JTlRFUk5BTBAMEhgKFEVSUk9SX0NPREVfUElfRkFJTEVEEA0SHwobRVJST1JfQ09ERV9BUFBST1ZBTF9QRU5ESU5HEA5CrAEKEWNvbS5waXJjLnYxYWxwaGExQgtDb21tb25Qcm90b1ABWjVwaXJjLmRldi9yZWxheS9pbnRlcm5hbC9wYi9waXJjL3YxYWxwaGExO3BpcmN2MWFscGhhMaICA1BYWKoCDVBpcmMuVjFhbHBoYTHKAg1QaXJjXFYxYWxwaGEx4gIZUGlyY1xWMWFscGhhMVxHUEJNZXRhZGF0YeoCDlBpcmM6OlYxYWxwaGExYgZwcm90bzM");
// protocol/gen/pirc/v1alpha1/relay_pb.ts
var file_pirc_v1alpha1_relay = /* @__PURE__ */ fileDesc("ChlwaXJjL3YxYWxwaGExL3JlbGF5LnByb3RvEg1waXJjLnYxYWxwaGExIqkFCgVGcmFtZRIlCgVoZWxsbxgBIAEoCzIULnBpcmMudjFhbHBoYTEuSGVsbG9IABItCgljaGFsbGVuZ2UYAiABKAsyGC5waXJjLnYxYWxwaGExLkNoYWxsZW5nZUgAEiMKBGF1dGgYAyABKAsyEy5waXJjLnYxYWxwaGExLkF1dGhIABIpCgd3ZWxjb21lGAQgASgLMhYucGlyYy52MWFscGhhMS5XZWxjb21lSAASOgoQc2Vzc2lvbl9hbm5vdW5jZRgKIAEoCzIeLnBpcmMudjFhbHBoYTEuU2Vzc2lvbkFubm91bmNlSAASMAoLc2Vzc2lvbl9lbmQYCyABKAsyGS5waXJjLnYxYWxwaGExLlNlc3Npb25FbmRIABI+ChJzdWJzY3JpYmVfc2Vzc2lvbnMYFCABKAsyIC5waXJjLnYxYWxwaGExLlN1YnNjcmliZVNlc3Npb25zSAASJwoGYXR0YWNoGBUgASgLMhUucGlyYy52MWFscGhhMS5BdHRhY2hIABInCgZkZXRhY2gYFiABKAsyFS5waXJjLnYxYWxwaGExLkRldGFjaEgAEjIKDHNlc3Npb25fbGlzdBgeIAEoCzIaLnBpcmMudjFhbHBoYTEuU2Vzc2lvbkxpc3RIABI4Cg9zZXNzaW9uX3VwZGF0ZWQYHyABKAsyHS5waXJjLnYxYWxwaGExLlNlc3Npb25VcGRhdGVkSAASNAoNc2Vzc2lvbl9lbmRlZBggIAEoCzIbLnBpcmMudjFhbHBoYTEuU2Vzc2lvbkVuZGVkSAASJwoGcm91dGVkGCggASgLMhUucGlyYy52MWFscGhhMS5Sb3V0ZWRIABIlCgVlcnJvchgyIAEoCzIULnBpcmMudjFhbHBoYTEuRXJyb3JIAEIGCgRib2R5IrUBCgVIZWxsbxI4ChBwcm90b2NvbF92ZXJzaW9uGAEgASgOMh4ucGlyYy52MWFscGhhMS5Qcm90b2NvbFZlcnNpb24SIQoEcm9sZRgCIAEoDjITLnBpcmMudjFhbHBoYTEuUm9sZRISCgpwdWJsaWNfa2V5GAMgASgMEhoKEnJlZ2lzdHJhdGlvbl90b2tlbhgEIAEoCRINCgVsYWJlbBgFIAEoCRIQCghzb2Z0d2FyZRgGIAEoCSIaCglDaGFsbGVuZ2USDQoFbm9uY2UYASABKAwiGQoEQXV0aBIRCglzaWduYXR1cmUYASABKAwibgoHV2VsY29tZRI4ChBwcm90b2NvbF92ZXJzaW9uGAEgASgOMh4ucGlyYy52MWFscGhhMS5Qcm90b2NvbFZlcnNpb24SFwoPbWF4X2ZyYW1lX2J5dGVzGAIgASgNEhAKCHNvZnR3YXJlGAMgASgJIk4KD1Nlc3Npb25Bbm5vdW5jZRIqCgdzZXNzaW9uGAEgASgLMhkucGlyYy52MWFscGhhMS5TZXNzaW9uUmVmEg8KB3BheWxvYWQYAiABKAwiOAoKU2Vzc2lvbkVuZBIqCgdzZXNzaW9uGAEgASgLMhkucGlyYy52MWFscGhhMS5TZXNzaW9uUmVmIhMKEVN1YnNjcmliZVNlc3Npb25zIjQKBkF0dGFjaBIqCgdzZXNzaW9uGAEgASgLMhkucGlyYy52MWFscGhhMS5TZXNzaW9uUmVmIjQKBkRldGFjaBIqCgdzZXNzaW9uGAEgASgLMhkucGlyYy52MWFscGhhMS5TZXNzaW9uUmVmIpEBCgxTZXNzaW9uRW50cnkSKgoHc2Vzc2lvbhgBIAEoCzIZLnBpcmMudjFhbHBoYTEuU2Vzc2lvblJlZhISCgpob3N0X2xhYmVsGAIgASgJEg8KB3BheWxvYWQYAyABKAwSMAoMYW5ub3VuY2VkX2F0GAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCJKCgtTZXNzaW9uTGlzdBItCghzZXNzaW9ucxgBIAMoCzIbLnBpcmMudjFhbHBoYTEuU2Vzc2lvbkVudHJ5EgwKBG1vcmUYAiABKAgiPAoOU2Vzc2lvblVwZGF0ZWQSKgoFZW50cnkYASABKAsyGy5waXJjLnYxYWxwaGExLlNlc3Npb25FbnRyeSI6CgxTZXNzaW9uRW5kZWQSKgoHc2Vzc2lvbhgBIAEoCzIZLnBpcmMudjFhbHBoYTEuU2Vzc2lvblJlZiJZCgZSb3V0ZWQSKgoHc2Vzc2lvbhgBIAEoCzIZLnBpcmMudjFhbHBoYTEuU2Vzc2lvblJlZhISCgpjbGllbnRfa2V5GAIgASgMEg8KB3BheWxvYWQYAyABKAwqUgoPUHJvdG9jb2xWZXJzaW9uEiAKHFBST1RPQ09MX1ZFUlNJT05fVU5TUEVDSUZJRUQQABIdChlQUk9UT0NPTF9WRVJTSU9OX1YxQUxQSEExEAEqPAoEUm9sZRIUChBST0xFX1VOU1BFQ0lGSUVEEAASDQoJUk9MRV9IT1NUEAESDwoLUk9MRV9DTElFTlQQAkKrAQoRY29tLnBpcmMudjFhbHBoYTFCClJlbGF5UHJvdG9QAVo1cGlyYy5kZXYvcmVsYXkvaW50ZXJuYWwvcGIvcGlyYy92MWFscGhhMTtwaXJjdjFhbHBoYTGiAgNQWFiqAg1QaXJjLlYxYWxwaGExygINUGlyY1xWMWFscGhhMeICGVBpcmNcVjFhbHBoYTFcR1BCTWV0YWRhdGHqAg5QaXJjOjpWMWFscGhhMWIGcHJvdG8z", [file_google_protobuf_timestamp, file_pirc_v1alpha1_common]);
@@ -7301,6 +7301,15 @@ var RelayConnection = class {
const wait = delayMs ?? backoff / 2 + Math.random() * (backoff / 2);
this.retryTimer = setTimeout(() => this.connect(), wait);
}
+ // Asks again in a while, without backing off: approval comes from a
+ // person, who expects it to work soon after.
+ await(reason) {
+ this.teardown();
+ if (this.stopped) return;
+ this.attempt = 0;
+ this.setState({ kind: "pending", reason });
+ this.retryTimer = setTimeout(() => this.connect(), this.opts.approvalRetryMs ?? 1e4);
+ }
refuse(code, reason) {
this.teardown();
this.stopped = true;
@@ -7308,7 +7317,7 @@ var RelayConnection = class {
}
connect() {
if (this.stopped) return;
- this.setState({ kind: "connecting" });
+ if (this.state.kind !== "pending") this.setState({ kind: "connecting" });
const ws = new wrapper_default(this.url, { handshakeTimeout: 1e4, perMessageDeflate: false });
ws.binaryType = "nodebuffer";
this.ws = ws;
@@ -7351,6 +7360,7 @@ var RelayConnection = class {
this.token = void 0;
return this.retry("registration token already used; retrying with the key alone", 1e3);
}
+ if (code === 14 /* APPROVAL_PENDING */) return this.await(message);
if (REFUSALS.has(code)) return this.refuse(code, message);
return this.retry(message || "login failed");
}
@@ -8343,6 +8353,8 @@ var Host = class {
return "pirc: connecting";
case "offline":
return "pirc: offline";
+ case "pending":
+ return "pirc: awaiting approval";
case "refused":
return "pirc: refused";
case "stopped":
@@ -8361,7 +8373,7 @@ var Host = class {
if ("reason" in this.state) lines.push(`relay said: ${this.state.reason}`);
if (this.live) lines.push(`session ${this.live.sessionId}, instance ${this.instanceId}`);
if (this.lastError) lines.push(`last error: ${this.lastError}`);
- if (!c?.relay) lines.push("enrol with: /pirc enrol <relay-url> <token>");
+ if (!c?.relay) lines.push("enrol with: /pirc enrol <relay-url> [token]; without a token, the relay's operator approves this key");
return lines.join("\n");
}
showStatus() {
@@ -8458,7 +8470,7 @@ function requestIdOf(m) {
}
// extension/src/index.ts
-var EXTENSION_VERSION = true ? "0.1.1" : "dev";
+var EXTENSION_VERSION = true ? "0.1.3" : "dev";
function piVersion() {
try {
let dir = dirname3(realpathSync(process.argv[1]));
@@ -8477,7 +8489,7 @@ var KEY = /* @__PURE__ */ Symbol.for("pirc.extension");
var proc = globalThis[KEY] ??= {
host: new Host(randomBytes3(8).toString("hex"), { pi: piVersion(), extension: EXTENSION_VERSION })
};
-var USAGE = "usage: /pirc [status | enrol <relay-url> <token> | reconnect]";
+var USAGE = "usage: /pirc [status | enrol <relay-url> [token] | reconnect]";
function index_default(pi) {
const host = proc.host;
let active = false;
@@ -8532,7 +8544,7 @@ function index_default(pi) {
}
});
pi.registerCommand("pirc", {
- description: "PiRC: status, enrol <relay-url> <token>, reconnect",
+ description: "PiRC: status, enrol <relay-url> [token], reconnect",
handler: async (args, ctx) => {
const [sub = "status", ...rest] = args.trim().split(/\s+/).filter(Boolean);
switch (sub) {
@@ -8542,7 +8554,7 @@ function index_default(pi) {
case "enrol":
case "enroll": {
const [url, token] = rest;
- if (!url || !token || rest.length !== 2) return ctx.ui.notify(USAGE, "error");
+ if (!url || rest.length > 2) return ctx.ui.notify(USAGE, "error");
try {
relayURL(url);
saveRelay(url);
diff --git a/package.json b/package.json
index 5773548..741564f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "pirc-extension",
- "version": "0.1.1",
+ "version": "0.1.3",
"description": "PiRC: puts every interactive pi session on a PiRC relay",
"license": "MIT",
"homepage": "https://pirc.dev",