From 1d0698071d51257e5d795671af6eed5b7af1c651 Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Sat, 24 Oct 2026 16:24:03 +0100 Subject: pirc-extension 0.1.3 --- README.md | 14 +++++++++++--- index.js | 28 ++++++++++++++++++++-------- package.json | 2 +- 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 `). In pi: ``` -/pirc enrol wss://: +/pirc enrol wss://: ``` +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 ` 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 `) and give it to the command: +`/pirc enrol wss://: `. + `/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 "); + if (!c?.relay) lines.push("enrol with: /pirc enrol [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 | reconnect]"; +var USAGE = "usage: /pirc [status | enrol [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 , reconnect", + description: "PiRC: status, enrol [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", -- cgit v1.2.3