@dotobokuri/fleet-console 1.54.0 → 1.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/dist/access-protocol.d.ts +58 -0
- package/dist/access-protocol.mjs +165 -0
- package/dist/cli.mjs +1207 -716
- package/dist/client/assets/{_baseUniq-B2XggV1b.js → _baseUniq-Cp0TuK00.js} +1 -1
- package/dist/client/assets/{arc-CPqvR_Wl.js → arc-Cwtz1wxV.js} +1 -1
- package/dist/client/assets/{architectureDiagram-Q4EWVU46-BczXSpod.js → architectureDiagram-Q4EWVU46-f2trvyEV.js} +1 -1
- package/dist/client/assets/{blockDiagram-DXYQGD6D-BPtW_zZR.js → blockDiagram-DXYQGD6D-BhEa9Mh6.js} +1 -1
- package/dist/client/assets/{c4Diagram-AHTNJAMY-Cry2oyLL.js → c4Diagram-AHTNJAMY-CuqbJbZK.js} +1 -1
- package/dist/client/assets/channel-D9kPbZ6w.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-DKkKt4m0.js → chunk-4BX2VUAB-Vx63wo1i.js} +1 -1
- package/dist/client/assets/{chunk-4TB4RGXK-DKGkDbqm.js → chunk-4TB4RGXK-DHRtIlLJ.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-DawsVr8O.js → chunk-55IACEB6-ChTm2kCK.js} +1 -1
- package/dist/client/assets/{chunk-EDXVE4YY-tS8vRy63.js → chunk-EDXVE4YY-C_BQtaY5.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-B0jQQkjI.js → chunk-FMBD7UC4-BPV9Mw6e.js} +1 -1
- package/dist/client/assets/{chunk-OYMX7WX6-FZQSRkEo.js → chunk-OYMX7WX6-ya-VyPHj.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-Bq1WBmr8.js → chunk-QZHKN3VN-CLiSGdJ6.js} +1 -1
- package/dist/client/assets/{chunk-YZCP3GAM-CNWIYKHc.js → chunk-YZCP3GAM-DasGOM9R.js} +1 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-B1zyip5T.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-B1zyip5T.js +1 -0
- package/dist/client/assets/clone-x5mGq45q.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-CmOaWS4P.js → cose-bilkent-S5V4N54A-C11PcGWz.js} +1 -1
- package/dist/client/assets/{dagre-KV5264BT-D2gJaf1o.js → dagre-KV5264BT-BGiNFCUB.js} +1 -1
- package/dist/client/assets/{diagram-5BDNPKRD-DH-M3VUk.js → diagram-5BDNPKRD-CSxK9ZtI.js} +1 -1
- package/dist/client/assets/{diagram-G4DWMVQ6-169IB8uN.js → diagram-G4DWMVQ6-COZtgx_6.js} +1 -1
- package/dist/client/assets/{diagram-MMDJMWI5-D1OVxCyQ.js → diagram-MMDJMWI5-4-mD0i99.js} +1 -1
- package/dist/client/assets/{diagram-TYMM5635-CkiS1glz.js → diagram-TYMM5635-BNX8i0L7.js} +1 -1
- package/dist/client/assets/{erDiagram-SMLLAGMA-CZUp7ncJ.js → erDiagram-SMLLAGMA-1a9K7CGs.js} +1 -1
- package/dist/client/assets/{flowDiagram-DWJPFMVM-9SIhR3aC.js → flowDiagram-DWJPFMVM-B7BZTT0P.js} +1 -1
- package/dist/client/assets/{ganttDiagram-T4ZO3ILL-CA4smcv3.js → ganttDiagram-T4ZO3ILL-FeCgSTyp.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-DF8CSmoM.js → gitGraphDiagram-UUTBAWPF-ItC4KjyQ.js} +1 -1
- package/dist/client/assets/{graph-Bc_ceTI8.js → graph-C0wWu6sL.js} +1 -1
- package/dist/client/assets/{index-NNL49IzO.css → index-B0961B8M.css} +1 -1
- package/dist/client/assets/index-DEW90Ngb.js +479 -0
- package/dist/client/assets/{infoDiagram-42DDH7IO-BC5KSucM.js → infoDiagram-42DDH7IO-5ub69t3k.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-JJIJclQD.js → ishikawaDiagram-UXIWVN3A-BEvDRmdo.js} +1 -1
- package/dist/client/assets/{journeyDiagram-VCZTEJTY-ERzBaHVf.js → journeyDiagram-VCZTEJTY-DUxQ-ZUX.js} +1 -1
- package/dist/client/assets/{kanban-definition-6JOO6SKY-KvnzNd3g.js → kanban-definition-6JOO6SKY-BiPkJbpE.js} +1 -1
- package/dist/client/assets/{layout-DJbQbTix.js → layout-Bhtd-qjm.js} +1 -1
- package/dist/client/assets/{linear-BzBkH5Aq.js → linear-BT8b8xym.js} +1 -1
- package/dist/client/assets/{mermaid.core-CAqwSbV2.js → mermaid.core-BlTpil8e.js} +4 -4
- package/dist/client/assets/{min-giSfwdiX.js → min-DrnMPIwV.js} +1 -1
- package/dist/client/assets/{mindmap-definition-QFDTVHPH-BH0wnMRm.js → mindmap-definition-QFDTVHPH-uQH7Nv45.js} +1 -1
- package/dist/client/assets/{pieDiagram-DEJITSTG-WOHMlPDe.js → pieDiagram-DEJITSTG-MOetI488.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-34T5L4WZ-DcIpvIOj.js → quadrantDiagram-34T5L4WZ-C-jeoH4D.js} +1 -1
- package/dist/client/assets/{requirementDiagram-MS252O5E-C99uWiwG.js → requirementDiagram-MS252O5E-C7cKIVp5.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-XADWPNL6-czWBcwBu.js → sankeyDiagram-XADWPNL6-Dcv2A0AY.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-FGHM5R23-CjtCPppG.js → sequenceDiagram-FGHM5R23-CXO-xPvA.js} +1 -1
- package/dist/client/assets/{stateDiagram-FHFEXIEX-D0UKF8ze.js → stateDiagram-FHFEXIEX-GKivP2tt.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-BfnEUJRo.js +1 -0
- package/dist/client/assets/{timeline-definition-GMOUNBTQ-3isWQZNU.js → timeline-definition-GMOUNBTQ-DLJSUfwG.js} +1 -1
- package/dist/client/assets/{vennDiagram-DHZGUBPP-C_LF2g8l.js → vennDiagram-DHZGUBPP-DFihaprP.js} +1 -1
- package/dist/client/assets/{wardley-RL74JXVD-Bw0Y4M6p.js → wardley-RL74JXVD-BBV41M5Z.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-NUSXRM2D-B6e3mxQI.js → wardleyDiagram-NUSXRM2D-B5J5AiY-.js} +1 -1
- package/dist/client/assets/{xychartDiagram-5P7HB3ND-DjlZXnLg.js → xychartDiagram-5P7HB3ND-D1NFEt18.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/fleet-plugins/file-explorer/routes.mjs +13 -9
- package/dist/fleet-plugins/ledger/routes.mjs +6 -2
- package/dist/fleet-plugins/quota/routes.mjs +22 -18
- package/dist/fleet-plugins/repository/routes.mjs +17 -13
- package/dist/fleet-plugins/scuttlebutt/routes.mjs +13 -14
- package/dist/fleet-plugins/skills/routes.mjs +14 -10
- package/dist/fleet-plugins/terminal/routes.mjs +354 -314
- package/dist/fleet.mjs +3950 -3486
- package/package.json +5 -1
- package/dist/client/assets/channel-D5C5uwWd.js +0 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-Cv4Bxw_r.js +0 -1
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-Cv4Bxw_r.js +0 -1
- package/dist/client/assets/clone-C7dthYDe.js +0 -1
- package/dist/client/assets/index-kTAFpDLu.js +0 -479
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-BdRUP2Pp.js +0 -1
package/AGENTS.md
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
## Security and state constraints
|
|
29
29
|
|
|
30
30
|
- The loopback listener's core browser APIs do not use bearer, admin, MCP, or session tokens. Loopback is not authorization: preserve route-specific Host and Origin gates.
|
|
31
|
-
- A remote listener is the opposite default: every request it accepts requires a session, decided once before routing, and the only
|
|
31
|
+
- A remote listener is the opposite default: every request it accepts requires a session, decided once before routing, and the only exception is the join endpoint. That endpoint is the one unauthenticated door on a public address, so it carries a failure budget per source and a global cap on concurrent attempts; the count of rejected attempts is reported to the owner rather than kept silent. Pairing happens only in the dedicated apps - a browser cannot check this console's certificate fingerprint - so the listener serves no browser-facing surface, not even an explanatory one. Sessions are bound to the listener that issued them and never cross between loopback and remote.
|
|
32
32
|
- A session is an open connection; a pairing is the standing permission behind it, and the two never collapse into one. A single-use grant from an access link creates a pairing; after that the paired device reopens a session on its own, so ending a session — by reclaiming control, by idling out, or by restarting the console — never costs that device its way back. Unpairing and a new certificate do, and so does a device that stays away longer than the cookie carrying its secret: browsers cap that lifetime well below the certificate's, so each return renews it and only a dormant device ages out. Console stores a pairing's hash, never its secret, and the device carries the secret in a cookie the console sets.
|
|
33
33
|
- A link publishes an address as much as a credential, and a device keeps only what the link told it. So the remote listener remembers the port it first opened on and reopens that one, rather than following the console's own port, which is free to move on every start; it never slides to another port on its own, because a device that cannot find the address it was given is unpaired in fact while the list still says otherwise. Releasing that port belongs with the acts that already void every pairing.
|
|
34
34
|
- Console owns which other consoles this one can reach, and that list is reachable only from the loopback listener — a remote session must not enumerate or edit the addresses and certificate pins of third machines. A saved host stores where to go and what certificate to trust, never a credential; a link's single-use grant lives in memory until the shell takes it, and is handed over exactly once.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 액세스 링크는 주소·1회용 자격·서버 신원·이름을 하나의 봉투에 담아 `fleet://join?code=`로
|
|
3
|
+
* 실어 나른다. 주소를 URL 껍데기에 두지 않는 이유는 두 가지다 — 붙여넣는 사람 눈에 사설 IP와
|
|
4
|
+
* 포트가 드러나지 않고, 봉투가 버전을 가지므로 나중에 필드를 늘려도 옛 링크를 구분할 수 있다.
|
|
5
|
+
*
|
|
6
|
+
* 이 스킴은 브라우저가 따라갈 수 없다. 링크를 여는 주체는 Fleet 네이티브 셸(Desktop과 Mobile)이며,
|
|
7
|
+
* 브라우저 원격 접속은 별도 결정 전까지 열지 않는다.
|
|
8
|
+
*
|
|
9
|
+
* 링크를 신뢰한다는 것이 곧 그 서버를 신뢰한다는 뜻이므로 파싱은 관대할 이유가 없다. 모양이
|
|
10
|
+
* 어긋난 링크는 고쳐 쓰지 않고 거절한다.
|
|
11
|
+
*/
|
|
12
|
+
interface AccessLinkPayload {
|
|
13
|
+
readonly v: number;
|
|
14
|
+
/** `https://host:port`. origin 형태 그대로이며 경로·질의·fragment를 갖지 않는다. */
|
|
15
|
+
readonly endpoint: string;
|
|
16
|
+
readonly token: string;
|
|
17
|
+
/** 구분자 없는 대문자 SHA-256 hex. */
|
|
18
|
+
readonly fingerprint: string;
|
|
19
|
+
/** 발급한 콘솔이 스스로를 부르는 이름. 목록에서 호스트를 알아보는 유일한 단서다. */
|
|
20
|
+
readonly label: string;
|
|
21
|
+
}
|
|
22
|
+
interface ValidatedAccessLink {
|
|
23
|
+
readonly origin: string;
|
|
24
|
+
/** 인증서 핀의 키. URL이 대괄호로 감싼 IPv6 리터럴은 벗겨서 담는다. */
|
|
25
|
+
readonly hostname: string;
|
|
26
|
+
readonly port: number;
|
|
27
|
+
readonly label: string;
|
|
28
|
+
readonly consoleUrl: string;
|
|
29
|
+
readonly joinUrl: string;
|
|
30
|
+
readonly token: string;
|
|
31
|
+
readonly fingerprint: string;
|
|
32
|
+
}
|
|
33
|
+
declare const ACCESS_LINK_SCHEME = "fleet:";
|
|
34
|
+
declare const ACCESS_LINK_HOST = "join";
|
|
35
|
+
declare const ACCESS_LINK_PREFIX = "fleet://join?code=";
|
|
36
|
+
declare const ACCESS_LINK_VERSION = 1;
|
|
37
|
+
declare const ACCESS_JOIN_PATH = "/api/v1/join";
|
|
38
|
+
declare const MAX_ACCESS_LABEL_LENGTH = 48;
|
|
39
|
+
/** 링크 후보인지만 가른다 — 유효성은 parseAccessLink가 판정한다. */
|
|
40
|
+
declare function isAccessLinkInput(value: string): boolean;
|
|
41
|
+
declare function encodeAccessLink(payload: Omit<AccessLinkPayload, "v">): string;
|
|
42
|
+
declare function parseAccessLink(input: string): ValidatedAccessLink;
|
|
43
|
+
/** 인증서 핀은 호스트 단위다. Chromium은 검증기에 대괄호 없는 호스트명을 넘긴다. */
|
|
44
|
+
declare function pinHostname(hostname: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* 링크와 UI가 같은 표기를 쓰도록 지문 비교는 항상 정규화한 뒤 수행한다. 콘솔은 구분자 없이
|
|
47
|
+
* 발급하지만 손으로 옮긴 콜론·공백 표기도 같은 값으로 본다.
|
|
48
|
+
*
|
|
49
|
+
* 지문 표기의 유일한 정의다 — 두 벌이 되면 한쪽만 고쳐진 채로 갈라진다.
|
|
50
|
+
*/
|
|
51
|
+
declare function normalizeFingerprint(value: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* 라벨은 목록에 그대로 그려지므로 제어문자와 방향 재정의 문자를 남겨 두지 않는다 — 호스트
|
|
54
|
+
* 이름이 옆줄의 주소를 가리는 형태로 읽히면 안 된다.
|
|
55
|
+
*/
|
|
56
|
+
declare function sanitizeAccessLabel(value: string): string;
|
|
57
|
+
|
|
58
|
+
export { ACCESS_JOIN_PATH, ACCESS_LINK_HOST, ACCESS_LINK_PREFIX, ACCESS_LINK_SCHEME, ACCESS_LINK_VERSION, type AccessLinkPayload, MAX_ACCESS_LABEL_LENGTH, type ValidatedAccessLink, encodeAccessLink, isAccessLinkInput, normalizeFingerprint, parseAccessLink, pinHostname, sanitizeAccessLabel };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// access-protocol/index.ts
|
|
3
|
+
var ACCESS_LINK_SCHEME = "fleet:";
|
|
4
|
+
var ACCESS_LINK_HOST = "join";
|
|
5
|
+
var ACCESS_LINK_PREFIX = "fleet://join?code=";
|
|
6
|
+
var ACCESS_LINK_VERSION = 1;
|
|
7
|
+
var ACCESS_JOIN_PATH = "/api/v1/join";
|
|
8
|
+
var MAX_ACCESS_LABEL_LENGTH = 48;
|
|
9
|
+
var MAX_ACCESS_LINK_LENGTH = 4096;
|
|
10
|
+
var ACCESS_TOKEN = /^[A-Za-z0-9_-]{16,512}$/u;
|
|
11
|
+
var CERTIFICATE_FINGERPRINT = /^[0-9A-F]{64}$/u;
|
|
12
|
+
var PAYLOAD_KEYS = ["v", "endpoint", "token", "fingerprint", "label"];
|
|
13
|
+
var BASE64_URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
14
|
+
var BASE64_URL_DECODE = createBase64UrlDecodeTable();
|
|
15
|
+
function isAccessLinkInput(value) {
|
|
16
|
+
return value.trimStart().toLowerCase().startsWith("fleet://");
|
|
17
|
+
}
|
|
18
|
+
function encodeAccessLink(payload) {
|
|
19
|
+
const envelope = {
|
|
20
|
+
v: ACCESS_LINK_VERSION,
|
|
21
|
+
endpoint: payload.endpoint,
|
|
22
|
+
token: payload.token,
|
|
23
|
+
fingerprint: normalizeFingerprint(payload.fingerprint),
|
|
24
|
+
label: sanitizeAccessLabel(payload.label)
|
|
25
|
+
};
|
|
26
|
+
return `${ACCESS_LINK_PREFIX}${toBase64Url(new TextEncoder().encode(JSON.stringify(envelope)))}`;
|
|
27
|
+
}
|
|
28
|
+
function parseAccessLink(input) {
|
|
29
|
+
if (typeof input !== "string") throw invalidLink();
|
|
30
|
+
const candidate = input.trim();
|
|
31
|
+
if (candidate.length === 0 || candidate.length > MAX_ACCESS_LINK_LENGTH) throw invalidLink();
|
|
32
|
+
if (containsDisallowedLinkWhitespace(candidate)) throw invalidLink();
|
|
33
|
+
let url;
|
|
34
|
+
try {
|
|
35
|
+
url = new URL(candidate);
|
|
36
|
+
} catch {
|
|
37
|
+
throw invalidLink();
|
|
38
|
+
}
|
|
39
|
+
if (url.protocol !== ACCESS_LINK_SCHEME || url.username || url.password || url.hash) throw invalidLink();
|
|
40
|
+
if (url.hostname !== ACCESS_LINK_HOST || url.port !== "" || url.pathname !== "") throw invalidLink();
|
|
41
|
+
const keys = [...url.searchParams.keys()];
|
|
42
|
+
if (keys.length !== 1 || keys[0] !== "code") throw invalidLink();
|
|
43
|
+
return readPayload(decodePayload(url.searchParams.get("code") ?? ""));
|
|
44
|
+
}
|
|
45
|
+
function decodePayload(code) {
|
|
46
|
+
if (!/^[A-Za-z0-9_-]+$/u.test(code)) throw invalidLink();
|
|
47
|
+
let text;
|
|
48
|
+
try {
|
|
49
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(fromBase64Url(code));
|
|
50
|
+
} catch {
|
|
51
|
+
throw invalidLink();
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(text);
|
|
55
|
+
} catch {
|
|
56
|
+
throw invalidLink();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function readPayload(value) {
|
|
60
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidLink();
|
|
61
|
+
const entry = value;
|
|
62
|
+
const keys = Object.keys(entry);
|
|
63
|
+
if (keys.length !== PAYLOAD_KEYS.length || !PAYLOAD_KEYS.every((key) => keys.includes(key))) throw invalidLink();
|
|
64
|
+
if (entry.v !== ACCESS_LINK_VERSION) throw invalidLink();
|
|
65
|
+
if (typeof entry.endpoint !== "string" || typeof entry.token !== "string" || typeof entry.fingerprint !== "string" || typeof entry.label !== "string") throw invalidLink();
|
|
66
|
+
const endpoint = readEndpoint(entry.endpoint);
|
|
67
|
+
const token = entry.token;
|
|
68
|
+
const fingerprint = normalizeFingerprint(entry.fingerprint);
|
|
69
|
+
if (!ACCESS_TOKEN.test(token) || !CERTIFICATE_FINGERPRINT.test(fingerprint)) throw invalidLink();
|
|
70
|
+
const label = sanitizeAccessLabel(entry.label);
|
|
71
|
+
if (label.length === 0) throw invalidLink();
|
|
72
|
+
return {
|
|
73
|
+
origin: endpoint.origin,
|
|
74
|
+
hostname: pinHostname(endpoint.hostname),
|
|
75
|
+
port: endpoint.port,
|
|
76
|
+
label,
|
|
77
|
+
consoleUrl: new URL("/console/", endpoint.origin).toString(),
|
|
78
|
+
joinUrl: new URL(ACCESS_JOIN_PATH, endpoint.origin).toString(),
|
|
79
|
+
token,
|
|
80
|
+
fingerprint
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function readEndpoint(value) {
|
|
84
|
+
let url;
|
|
85
|
+
try {
|
|
86
|
+
url = new URL(value);
|
|
87
|
+
} catch {
|
|
88
|
+
throw invalidLink();
|
|
89
|
+
}
|
|
90
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw invalidLink();
|
|
91
|
+
if (url.pathname !== "/" && url.pathname !== "") throw invalidLink();
|
|
92
|
+
if (url.hostname.length === 0) throw invalidLink();
|
|
93
|
+
const port = url.port === "" ? 443 : Number(url.port);
|
|
94
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65535) throw invalidLink();
|
|
95
|
+
return { origin: url.origin, hostname: url.hostname, port };
|
|
96
|
+
}
|
|
97
|
+
function pinHostname(hostname) {
|
|
98
|
+
const unwrapped = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
99
|
+
return unwrapped.toLowerCase();
|
|
100
|
+
}
|
|
101
|
+
function normalizeFingerprint(value) {
|
|
102
|
+
return value.replace(/[^0-9a-fA-F]/gu, "").toUpperCase();
|
|
103
|
+
}
|
|
104
|
+
function sanitizeAccessLabel(value) {
|
|
105
|
+
return Array.from(value).filter((character) => !isDisallowedAccessLabelCharacter(character.codePointAt(0))).join("").replace(/\s+/gu, " ").trim().slice(0, MAX_ACCESS_LABEL_LENGTH);
|
|
106
|
+
}
|
|
107
|
+
function containsDisallowedLinkWhitespace(value) {
|
|
108
|
+
for (const character of value) {
|
|
109
|
+
const codePoint = character.codePointAt(0);
|
|
110
|
+
if (codePoint <= 31 || codePoint === 127 || isWhitespaceCharacter(character)) return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
function isWhitespaceCharacter(character) {
|
|
115
|
+
return character.trim().length === 0;
|
|
116
|
+
}
|
|
117
|
+
function isDisallowedAccessLabelCharacter(codePoint) {
|
|
118
|
+
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 || codePoint >= 8203 && codePoint <= 8207 || codePoint === 8232 || codePoint === 8233 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297;
|
|
119
|
+
}
|
|
120
|
+
function toBase64Url(bytes) {
|
|
121
|
+
let encoded = "";
|
|
122
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
123
|
+
const first = bytes[index];
|
|
124
|
+
const second = bytes[index + 1];
|
|
125
|
+
const third = bytes[index + 2];
|
|
126
|
+
encoded += BASE64_URL_ALPHABET[first >> 2];
|
|
127
|
+
encoded += BASE64_URL_ALPHABET[(first & 3) << 4 | (second ?? 0) >> 4];
|
|
128
|
+
if (second !== void 0) encoded += BASE64_URL_ALPHABET[(second & 15) << 2 | (third ?? 0) >> 6];
|
|
129
|
+
if (third !== void 0) encoded += BASE64_URL_ALPHABET[third & 63];
|
|
130
|
+
}
|
|
131
|
+
return encoded;
|
|
132
|
+
}
|
|
133
|
+
function fromBase64Url(value) {
|
|
134
|
+
if (value.length % 4 === 1) throw invalidLink();
|
|
135
|
+
const bytes = new Uint8Array(Math.floor(value.length * 6 / 8));
|
|
136
|
+
let buffer = 0;
|
|
137
|
+
let bits = 0;
|
|
138
|
+
let offset = 0;
|
|
139
|
+
for (const character of value) {
|
|
140
|
+
const decoded = BASE64_URL_DECODE[character.charCodeAt(0)] ?? -1;
|
|
141
|
+
if (decoded < 0) throw invalidLink();
|
|
142
|
+
buffer = buffer << 6 | decoded;
|
|
143
|
+
bits += 6;
|
|
144
|
+
if (bits >= 8) {
|
|
145
|
+
bits -= 8;
|
|
146
|
+
bytes[offset] = buffer >> bits & 255;
|
|
147
|
+
offset += 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (bits > 0 && (buffer & (1 << bits) - 1) !== 0) throw invalidLink();
|
|
151
|
+
return bytes;
|
|
152
|
+
}
|
|
153
|
+
function createBase64UrlDecodeTable() {
|
|
154
|
+
const table = new Int16Array(128);
|
|
155
|
+
table.fill(-1);
|
|
156
|
+
for (let index = 0; index < BASE64_URL_ALPHABET.length; index += 1) {
|
|
157
|
+
table[BASE64_URL_ALPHABET.charCodeAt(index)] = index;
|
|
158
|
+
}
|
|
159
|
+
return table;
|
|
160
|
+
}
|
|
161
|
+
function invalidLink() {
|
|
162
|
+
return new Error("pairing_target_invalid");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export { ACCESS_JOIN_PATH, ACCESS_LINK_HOST, ACCESS_LINK_PREFIX, ACCESS_LINK_SCHEME, ACCESS_LINK_VERSION, MAX_ACCESS_LABEL_LENGTH, encodeAccessLink, isAccessLinkInput, normalizeFingerprint, parseAccessLink, pinHostname, sanitizeAccessLabel };
|