Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | 2x 2x 2x 2x 16x 16x 1x 15x 15x 2x 13x 13x 13x 13x 13x 13x 2x 11x 2x 9x 2x 7x 7x 1x 6x 1x 5x 5x 17x 17x 3x 3x 2x 1x | // Made with ❤️ by [Vibey](https://the-vibey-project.github.io/vibey/), Developed by [Adam Matthew Steinberger](https://vibewithadam.matthewsteinberger.com/) ([@adammatthewsteinberger](https://github.com/adammatthewsteinberger/)).
/**
* Reads a pairing offer. The hub's pairing routes (ADR-0068, "the pairing change") have not
* landed, so the QR format here is the client's side of that contract, and the exchange of
* the code for a device key is refused plainly by `PairingExchange` until they do.
* Declared by `interfaces/pairing-interface.ts`.
*/
import type { PairingInterface, PairingOffer, PairingParse } from './interfaces/pairing-interface';
export class Pairing implements PairingInterface {
static readonly SCHEMES: readonly string[] = ['krypton:', 'krypton-nightly:'];
static readonly CODE = /^\d{6}$/;
static readonly FINGERPRINT = /^sha256:[0-9a-f]{64}$/i;
static readonly LOOPBACK: readonly string[] = ['127.0.0.1', 'localhost', '::1', '[::1]'];
parseQr(text: string): PairingParse {
let url: URL;
try {
url = new URL(text.trim());
} catch {
return { ok: false, reason: 'That QR code is not a Krypton pairing code.' };
}
const target = `${url.host}${url.pathname}`.replace(/^\/+/, '');
if (!Pairing.SCHEMES.includes(url.protocol) || target !== 'pair') {
return { ok: false, reason: 'That QR code is not a Krypton pairing code.' };
}
const params = url.searchParams;
const host = params.get('host') ?? '';
const port = Number(params.get('port') ?? '');
const code = this.normaliseCode(params.get('code') ?? '');
const fingerprint = params.get('fp');
if (host === '' || /[\s/?#@]/.test(host)) {
return { ok: false, reason: 'The pairing code names no host.' };
}
if (!Number.isInteger(port) || port < 1 || port > 65535) {
return { ok: false, reason: 'The pairing code names no valid port.' };
}
if (typeof code !== 'string') {
return { ok: false, reason: code.error };
}
const loopback = Pairing.LOOPBACK.includes(host);
if (fingerprint === null && !loopback) {
return { ok: false, reason: 'The pairing code carries no certificate fingerprint, so the hub cannot be trusted.' };
}
if (fingerprint !== null && !Pairing.FINGERPRINT.test(fingerprint)) {
return { ok: false, reason: 'The certificate fingerprint in the pairing code is malformed.' };
}
const offer: PairingOffer = {
host,
port,
scheme: loopback && fingerprint === null ? 'http' : 'https',
fingerprint: fingerprint === null ? null : fingerprint.toLowerCase(),
code,
name: params.get('name'),
};
return { ok: true, offer };
}
normaliseCode(typed: string): string | { readonly error: string } {
const digits = typed.replace(/[\s-]/g, '');
return Pairing.CODE.test(digits) ? digits : { error: 'The code is the 6 digits the host shows.' };
}
baseUrl(offer: PairingOffer): string {
const host = offer.host.includes(':') && !offer.host.startsWith('[') ? `[${offer.host}]` : offer.host;
return `${offer.scheme}://${host}:${offer.port}`;
}
}
/** Exchanging a code for a device key: refused, honestly, until the hub offers the route. */
export class PairingExchange {
static readonly NOT_YET =
'This hub cannot pair devices yet: its pairing routes are still being built. ' +
'For now, connect with the host token (Settings → Connect with a host token).';
exchange(_offer: PairingOffer): Promise<never> {
return Promise.reject(new Error(PairingExchange.NOT_YET));
}
}
|