@onlyne/node-host 3.1.0 → 3.2.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/dist/acquire.d.ts +62 -0
- package/dist/acquire.d.ts.map +1 -0
- package/dist/acquire.js +146 -0
- package/dist/acquire.js.map +1 -0
- package/dist/host-text.d.ts +2 -0
- package/dist/host-text.d.ts.map +1 -0
- package/dist/host-text.js +103 -0
- package/dist/host-text.js.map +1 -0
- package/dist/i18n.d.ts +9 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +19 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +74 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +20 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +144 -0
- package/dist/keys.js.map +1 -0
- package/dist/log.d.ts +43 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +77 -0
- package/dist/log.js.map +1 -0
- package/dist/network.d.ts +27 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +350 -0
- package/dist/network.js.map +1 -0
- package/dist/node.d.ts +20 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +153 -0
- package/dist/node.js.map +1 -0
- package/dist/store.d.ts +44 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +219 -0
- package/dist/store.js.map +1 -0
- package/dist/tui-text.d.ts +37 -0
- package/dist/tui-text.d.ts.map +1 -0
- package/dist/tui-text.js +69 -0
- package/dist/tui-text.js.map +1 -0
- package/dist/tui.d.ts +19 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +400 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +46 -10
- package/src/i18n.ts +0 -22
- package/src/index.ts +0 -82
- package/src/keys.ts +0 -158
- package/src/log.ts +0 -103
- package/src/network.ts +0 -382
- package/src/tui-text.ts +0 -73
- package/src/tui.ts +0 -329
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** RFC 0018 §2: how far a peer may be acquired without a human confirming first. */
|
|
2
|
+
export type AcquirePolicy = "all" | "segment" | "none";
|
|
3
|
+
/** RFC 0018 §5: host-side acquisition settings. The library never reads them. */
|
|
4
|
+
export interface AcquisitionPolicy {
|
|
5
|
+
policy: AcquirePolicy;
|
|
6
|
+
/** CIDR blocks and host names; only read when `policy` is `segment`. */
|
|
7
|
+
allow: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The default delegation: loopback only. Discovery excludes loopback by default, so the two
|
|
11
|
+
* defaults together mean a remote peer is neither discovered nor approved (RFC 0018 §5.1).
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_ACQUISITION: AcquisitionPolicy;
|
|
14
|
+
/** Turns a host name into the addresses it names; an empty list means it names none. */
|
|
15
|
+
export type Resolver = (host: string) => Promise<string[]>;
|
|
16
|
+
/** The configured policy, or the documented default when the config carries none. */
|
|
17
|
+
export declare function acquisitionOf(config: {
|
|
18
|
+
acquisition?: AcquisitionPolicy;
|
|
19
|
+
}): AcquisitionPolicy;
|
|
20
|
+
export type AcquireReason = "policy_all" | "policy_segment" | "policy_denied_none" | "policy_unobserved" | "policy_denied_observed" | "policy_denied_declared" | "policy_denied_recorded";
|
|
21
|
+
/** RFC 0018 §2.1–2.2: what the host observed, what the peer declared, what discovery recorded. */
|
|
22
|
+
export interface AcquireInput {
|
|
23
|
+
/** The address this statement's connection arrived from; absent when it was not observed. */
|
|
24
|
+
observed?: string;
|
|
25
|
+
/** The `base_url` the peer wrote into its own statement. */
|
|
26
|
+
declared?: string;
|
|
27
|
+
/** The discovery addresses recorded for this node id when the statement arrived. */
|
|
28
|
+
recorded?: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
export interface AcquireVerdict {
|
|
31
|
+
allowed: boolean;
|
|
32
|
+
reason: AcquireReason;
|
|
33
|
+
/** The allow entry the observed address matched, when one did. */
|
|
34
|
+
matched?: string;
|
|
35
|
+
}
|
|
36
|
+
interface Block {
|
|
37
|
+
network: string;
|
|
38
|
+
prefix: number;
|
|
39
|
+
type: "ipv4" | "ipv6";
|
|
40
|
+
}
|
|
41
|
+
/** One `[acquisition] allow` entry: `network/prefix`, or a host name to accept as written. */
|
|
42
|
+
export declare function parseAllowBlock(value: string): Block | {
|
|
43
|
+
name: string;
|
|
44
|
+
} | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* The allow entry a value matches, or undefined. A literal address is judged as itself. A host
|
|
47
|
+
* name matches an allow entry of the same name; otherwise it is resolved and every address it
|
|
48
|
+
* names must fall inside the allow set (RFC 0018 §2.1) — a name that resolves to nothing, or to
|
|
49
|
+
* one address outside the set, matches nothing.
|
|
50
|
+
*/
|
|
51
|
+
export declare function allowMatch(value: string, allow: readonly string[], resolve?: Resolver): Promise<string | undefined>;
|
|
52
|
+
/**
|
|
53
|
+
* RFC 0018 §2: decide whether this peer may be approved without a human confirming first.
|
|
54
|
+
*
|
|
55
|
+
* The declared address is checked because the trust row stores it and `peerBase` falls back to
|
|
56
|
+
* it when discovery has no record. The recorded addresses are checked because
|
|
57
|
+
* `discovery.resolve()` re-probes every one of them before it connects, so a record outside the
|
|
58
|
+
* allow set is an outbound request the policy never authorized.
|
|
59
|
+
*/
|
|
60
|
+
export declare function evaluateAcquisition(policy: AcquisitionPolicy, input: AcquireInput, resolve?: Resolver): Promise<AcquireVerdict>;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=acquire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acquire.d.ts","sourceRoot":"","sources":["../src/acquire.ts"],"names":[],"mappings":"AAGA,oFAAoF;AACpF,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;AAEvD,iFAAiF;AACjF,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,wEAAwE;IACxE,KAAK,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,iBAGjC,CAAC;AAEF,wFAAwF;AACxF,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAI3D,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,MAAM,EAAE;IAAE,WAAW,CAAC,EAAE,iBAAiB,CAAA;CAAE,GAAG,iBAAiB,CAE5F;AAED,MAAM,MAAM,aAAa,GACtB,YAAY,GACZ,gBAAgB,GAChB,oBAAoB,GACpB,mBAAmB,GACnB,wBAAwB,GACxB,wBAAwB,GACxB,wBAAwB,CAAC;AAE5B,kGAAkG;AAClG,MAAM,WAAW,YAAY;IAC5B,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;IACtB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,KAAK;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB;AAID,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAgBnF;AA2CD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,QAAsB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAoBtI;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACxC,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE,QAAsB,GAC7B,OAAO,CAAC,cAAc,CAAC,CAezB"}
|
package/dist/acquire.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { lookup } from "node:dns/promises";
|
|
2
|
+
import { BlockList, isIP } from "node:net";
|
|
3
|
+
/**
|
|
4
|
+
* The default delegation: loopback only. Discovery excludes loopback by default, so the two
|
|
5
|
+
* defaults together mean a remote peer is neither discovered nor approved (RFC 0018 §5.1).
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_ACQUISITION = {
|
|
8
|
+
policy: "segment",
|
|
9
|
+
allow: ["127.0.0.0/8", "::1/128"],
|
|
10
|
+
};
|
|
11
|
+
const dnsResolver = async (host) => (await lookup(host, { all: true })).map(row => row.address);
|
|
12
|
+
/** The configured policy, or the documented default when the config carries none. */
|
|
13
|
+
export function acquisitionOf(config) {
|
|
14
|
+
return config.acquisition ?? DEFAULT_ACQUISITION;
|
|
15
|
+
}
|
|
16
|
+
const HOST_NAME = /^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?$/;
|
|
17
|
+
/** One `[acquisition] allow` entry: `network/prefix`, or a host name to accept as written. */
|
|
18
|
+
export function parseAllowBlock(value) {
|
|
19
|
+
const trimmed = value.trim();
|
|
20
|
+
if (trimmed.length === 0)
|
|
21
|
+
return undefined;
|
|
22
|
+
const slash = trimmed.lastIndexOf("/");
|
|
23
|
+
if (slash <= 0) {
|
|
24
|
+
// A name is matched by its own spelling, so listing it involves no resolution. A bare
|
|
25
|
+
// address is not a block: it needs a prefix to say what it covers.
|
|
26
|
+
if (isIP(trimmed) !== 0 || !HOST_NAME.test(trimmed))
|
|
27
|
+
return undefined;
|
|
28
|
+
return { name: trimmed.toLowerCase() };
|
|
29
|
+
}
|
|
30
|
+
const prefix = Number(trimmed.slice(slash + 1));
|
|
31
|
+
if (!Number.isInteger(prefix) || prefix < 0)
|
|
32
|
+
return undefined;
|
|
33
|
+
const network = literalAddress(trimmed.slice(0, slash));
|
|
34
|
+
if (network === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (prefix > (network.type === "ipv4" ? 32 : 128))
|
|
37
|
+
return undefined;
|
|
38
|
+
return { network: network.address, prefix, type: network.type };
|
|
39
|
+
}
|
|
40
|
+
/** The host a value names: a URL's hostname, or the value itself when it is already a host. */
|
|
41
|
+
function hostOf(value) {
|
|
42
|
+
const trimmed = value.trim();
|
|
43
|
+
if (trimmed.length === 0)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (!trimmed.includes("://"))
|
|
46
|
+
return trimmed.replace(/^\[(.*)\]$/, "$1");
|
|
47
|
+
try {
|
|
48
|
+
const host = new URL(trimmed).hostname;
|
|
49
|
+
return host.replace(/^\[(.*)\]$/, "$1");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** A host that is already a literal address, with IPv4-mapped IPv6 read as IPv4. */
|
|
56
|
+
function literalAddress(host) {
|
|
57
|
+
const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
58
|
+
const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(bare);
|
|
59
|
+
if (mapped !== null)
|
|
60
|
+
return { address: mapped[1], type: "ipv4" };
|
|
61
|
+
const family = isIP(bare);
|
|
62
|
+
if (family === 4)
|
|
63
|
+
return { address: bare, type: "ipv4" };
|
|
64
|
+
if (family === 6)
|
|
65
|
+
return { address: bare, type: "ipv6" };
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
function blockHolds(address, block) {
|
|
69
|
+
if (block.type !== address.type)
|
|
70
|
+
return false;
|
|
71
|
+
const blocks = new BlockList();
|
|
72
|
+
blocks.addSubnet(block.network, block.prefix, block.type);
|
|
73
|
+
return blocks.check(address.address, block.type);
|
|
74
|
+
}
|
|
75
|
+
/** The allow entry an address falls in, or undefined. */
|
|
76
|
+
function entryFor(address, allow) {
|
|
77
|
+
for (const entry of allow) {
|
|
78
|
+
const block = parseAllowBlock(entry);
|
|
79
|
+
if (block === undefined || "name" in block)
|
|
80
|
+
continue;
|
|
81
|
+
if (blockHolds(address, block))
|
|
82
|
+
return entry.trim();
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The allow entry a value matches, or undefined. A literal address is judged as itself. A host
|
|
88
|
+
* name matches an allow entry of the same name; otherwise it is resolved and every address it
|
|
89
|
+
* names must fall inside the allow set (RFC 0018 §2.1) — a name that resolves to nothing, or to
|
|
90
|
+
* one address outside the set, matches nothing.
|
|
91
|
+
*/
|
|
92
|
+
export async function allowMatch(value, allow, resolve = dnsResolver) {
|
|
93
|
+
const host = hostOf(value);
|
|
94
|
+
if (host === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
const literal = literalAddress(host);
|
|
97
|
+
if (literal !== undefined)
|
|
98
|
+
return entryFor(literal, allow);
|
|
99
|
+
const named = allow.find(entry => {
|
|
100
|
+
const block = parseAllowBlock(entry);
|
|
101
|
+
return block !== undefined && "name" in block && block.name === host.toLowerCase();
|
|
102
|
+
});
|
|
103
|
+
if (named !== undefined)
|
|
104
|
+
return named.trim();
|
|
105
|
+
const resolved = await resolve(host).catch(() => []);
|
|
106
|
+
let matched;
|
|
107
|
+
for (const address of resolved) {
|
|
108
|
+
const target = literalAddress(address);
|
|
109
|
+
if (target === undefined)
|
|
110
|
+
return undefined;
|
|
111
|
+
const entry = entryFor(target, allow);
|
|
112
|
+
if (entry === undefined)
|
|
113
|
+
return undefined;
|
|
114
|
+
matched ??= entry;
|
|
115
|
+
}
|
|
116
|
+
return matched;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* RFC 0018 §2: decide whether this peer may be approved without a human confirming first.
|
|
120
|
+
*
|
|
121
|
+
* The declared address is checked because the trust row stores it and `peerBase` falls back to
|
|
122
|
+
* it when discovery has no record. The recorded addresses are checked because
|
|
123
|
+
* `discovery.resolve()` re-probes every one of them before it connects, so a record outside the
|
|
124
|
+
* allow set is an outbound request the policy never authorized.
|
|
125
|
+
*/
|
|
126
|
+
export async function evaluateAcquisition(policy, input, resolve = dnsResolver) {
|
|
127
|
+
if (policy.policy === "none")
|
|
128
|
+
return { allowed: false, reason: "policy_denied_none" };
|
|
129
|
+
if (policy.policy === "all")
|
|
130
|
+
return { allowed: true, reason: "policy_all" };
|
|
131
|
+
if (input.observed === undefined)
|
|
132
|
+
return { allowed: false, reason: "policy_unobserved" };
|
|
133
|
+
const observed = await allowMatch(input.observed, policy.allow, resolve);
|
|
134
|
+
if (observed === undefined)
|
|
135
|
+
return { allowed: false, reason: "policy_denied_observed" };
|
|
136
|
+
if (input.declared !== undefined && await allowMatch(input.declared, policy.allow, resolve) === undefined) {
|
|
137
|
+
return { allowed: false, reason: "policy_denied_declared", matched: observed };
|
|
138
|
+
}
|
|
139
|
+
for (const address of input.recorded ?? []) {
|
|
140
|
+
if (await allowMatch(address, policy.allow, resolve) === undefined) {
|
|
141
|
+
return { allowed: false, reason: "policy_denied_recorded", matched: observed };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { allowed: true, reason: "policy_segment", matched: observed };
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=acquire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acquire.js","sourceRoot":"","sources":["../src/acquire.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const hostText: (key: "regionEmpty" | "nodeIdEmpty" | "addressEmpty" | "addressUrl" | "addressScheme" | "addressShape" | "configMissing" | "configParse" | "languageInvalid" | "keyScopeInvalid" | "publicUrlEmpty" | "bindEmpty" | "bindShape" | "pageSizeRange" | "retiredEndpoint" | "portRange" | "retiredPeers" | "retiredOmp" | "retiredAttention" | "tableExpected" | "stringExpected" | "integerExpected" | "stringArrayExpected" | "trustArray" | "trustEntry" | "acquirePolicy" | "acquireAllow" | "acquireDenied" | "addressUnavailable" | "keyProvider" | "loopbackBind" | "managementIdentity" | "mentionNotFound" | "guideContacts" | "guidePending" | "guideAccept" | "guideDiscover" | "guideRequestExchange" | "resourcePageSize" | "resourceOwner" | "resourceRef" | "resourceSince" | "resourceOp" | "contextNotRegistered" | "mainSummary" | "storeClosed" | "storeNested" | "storeBusy", vars?: Record<string, string | number>) => string;
|
|
2
|
+
//# sourceMappingURL=host-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-text.d.ts","sourceRoot":"","sources":["../src/host-text.ts"],"names":[],"mappings":"AAqGA,eAAO,MAAM,QAAQ,i5BAAwC,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createTranslator } from "./i18n.js";
|
|
2
|
+
const en = {
|
|
3
|
+
regionEmpty: "region must not be empty.",
|
|
4
|
+
nodeIdEmpty: "node.id must not be empty.",
|
|
5
|
+
addressEmpty: "A node address must not be empty.",
|
|
6
|
+
addressUrl: "A node address must be an absolute URL, got {value}.",
|
|
7
|
+
addressScheme: "A node address must use http or https, got {value}.",
|
|
8
|
+
addressShape: "A node address is an origin (http://host:7801) or a service URL (http://host:7801{service}), got {value}.",
|
|
9
|
+
configMissing: "No Onlyne configuration at {path}. Run /onlyne init to create it.",
|
|
10
|
+
configParse: "{path}: {error}",
|
|
11
|
+
languageInvalid: "language must be en or zh-CN.",
|
|
12
|
+
keyScopeInvalid: "node.key_scope must be project, global or none, got {value}.",
|
|
13
|
+
publicUrlEmpty: "server.public_url must not be empty.",
|
|
14
|
+
bindEmpty: "server.bind must not be empty.",
|
|
15
|
+
bindShape: "server.bind must be a hostname or IP address, got {value}.",
|
|
16
|
+
pageSizeRange: "threads.page_size must be an integer from 1 to 100, got {value}.",
|
|
17
|
+
retiredEndpoint: "node.endpoint is not supported; the endpoint comes from server.bind and the listening port.",
|
|
18
|
+
portRange: "server.port must be an integer from 0 to 65535, got {value}.",
|
|
19
|
+
retiredPeers: "peers is not supported; peers are discovered over the network and kept in the project SQLite.",
|
|
20
|
+
retiredOmp: "omp is not supported; delivery and spawning are decided by the OMP judge.",
|
|
21
|
+
retiredAttention: "threads.default_attention is not supported; reads default to the attention list.",
|
|
22
|
+
tableExpected: "{path}: {key} must be a table.",
|
|
23
|
+
stringExpected: "{field} must be a string.",
|
|
24
|
+
integerExpected: "{field} must be an integer.",
|
|
25
|
+
stringArrayExpected: "{field} must be an array of non-empty strings.",
|
|
26
|
+
trustArray: "{path}: trust must be an array of tables.",
|
|
27
|
+
trustEntry: "{path}: a trust entry needs node_id and a 16-character fingerprint.",
|
|
28
|
+
acquirePolicy: "acquisition.policy must be all, segment or none, got {value}.",
|
|
29
|
+
acquireAllow: "acquisition.allow entries must be network/prefix blocks, got {value}.",
|
|
30
|
+
acquireDenied: "The acquisition policy {policy} refused this approval: {reason}.",
|
|
31
|
+
addressUnavailable: "No concrete network address is available for this bind host.",
|
|
32
|
+
keyProvider: "External credential references need a host key provider.",
|
|
33
|
+
loopbackBind: "The unauthenticated development binding requires a loopback bind host. Create a node key to serve a remote address.",
|
|
34
|
+
managementIdentity: "HTTP callers cannot claim this node's management identity.",
|
|
35
|
+
mentionNotFound: "No unique discovered node is named {name}.",
|
|
36
|
+
guideContacts: "Contacts",
|
|
37
|
+
guidePending: "Pending",
|
|
38
|
+
guideAccept: "Accept",
|
|
39
|
+
guideDiscover: "Discover",
|
|
40
|
+
guideRequestExchange: "Request key exchange",
|
|
41
|
+
resourcePageSize: "page_size must be from 1 to 100.",
|
|
42
|
+
resourceOwner: "Resource owner must be a non-empty node id.",
|
|
43
|
+
resourceRef: "Resource reads need meta.resource {owner,id}.",
|
|
44
|
+
resourceSince: "since must be a decimal event id.",
|
|
45
|
+
resourceOp: "Resource reads use list, describe, log, event with event_id, operations, or status with operation_id.",
|
|
46
|
+
contextNotRegistered: "No execution context is registered as {alias}.",
|
|
47
|
+
mainSummary: "Main OMP session",
|
|
48
|
+
storeClosed: "The Onlyne store is closed.",
|
|
49
|
+
storeNested: "The Onlyne store already holds a transaction in this call.",
|
|
50
|
+
storeBusy: "The Onlyne store is busy and this transaction could not start.",
|
|
51
|
+
};
|
|
52
|
+
const zh = {
|
|
53
|
+
regionEmpty: "region 不能为空。",
|
|
54
|
+
nodeIdEmpty: "node.id 不能为空。",
|
|
55
|
+
addressEmpty: "节点地址不能为空。",
|
|
56
|
+
addressUrl: "节点地址必须是绝对 URL,收到的是 {value}。",
|
|
57
|
+
addressScheme: "节点地址必须使用 http 或 https,收到的是 {value}。",
|
|
58
|
+
addressShape: "节点地址应写成源站(http://host:7801)或服务 URL(http://host:7801{service}),收到的是 {value}。",
|
|
59
|
+
configMissing: "在 {path} 没有 Onlyne 配置。运行 /onlyne init 创建。",
|
|
60
|
+
configParse: "{path}: {error}",
|
|
61
|
+
languageInvalid: "language 必须是 en 或 zh-CN。",
|
|
62
|
+
keyScopeInvalid: "node.key_scope 必须是 project、global 或 none,收到的是 {value}。",
|
|
63
|
+
publicUrlEmpty: "server.public_url 不能为空。",
|
|
64
|
+
bindEmpty: "server.bind 不能为空。",
|
|
65
|
+
bindShape: "server.bind 必须是主机名或 IP 地址,收到的是 {value}。",
|
|
66
|
+
pageSizeRange: "threads.page_size 必须是 1 到 100 之间的整数,收到的是 {value}。",
|
|
67
|
+
retiredEndpoint: "不支持 node.endpoint;端点由 server.bind 和监听端口决定。",
|
|
68
|
+
portRange: "server.port 必须是 0 到 65535 之间的整数,收到的是 {value}。",
|
|
69
|
+
retiredPeers: "不支持 peers;对端通过网络发现,并保存在项目 SQLite 中。",
|
|
70
|
+
retiredOmp: "不支持 omp;投送与派生由 OMP judge 决定。",
|
|
71
|
+
retiredAttention: "不支持 threads.default_attention;读取默认使用关注列表。",
|
|
72
|
+
tableExpected: "{path}: {key} 必须是表。",
|
|
73
|
+
stringExpected: "{field} 必须是字符串。",
|
|
74
|
+
integerExpected: "{field} 必须是整数。",
|
|
75
|
+
stringArrayExpected: "{field} 必须是非空字符串数组。",
|
|
76
|
+
trustArray: "{path}: trust 必须是表数组。",
|
|
77
|
+
trustEntry: "{path}: 每条 trust 记录需要 node_id 和 16 个字符的指纹。",
|
|
78
|
+
acquirePolicy: "acquisition.policy 必须是 all、segment 或 none,收到的是 {value}。",
|
|
79
|
+
acquireAllow: "acquisition.allow 的条目必须是 network/prefix 形式,收到的是 {value}。",
|
|
80
|
+
acquireDenied: "获取策略 {policy} 拒绝了这次批准:{reason}。",
|
|
81
|
+
addressUnavailable: "这个绑定主机没有可用的具体网络地址。",
|
|
82
|
+
keyProvider: "外部凭据引用需要主机密钥提供者。",
|
|
83
|
+
loopbackBind: "未认证的开发绑定需要回环绑定主机。要对外提供服务,请创建节点密钥。",
|
|
84
|
+
managementIdentity: "HTTP 调用方不能冒用本节点的管理身份。",
|
|
85
|
+
mentionNotFound: "没有唯一发现到的节点名为 {name}。",
|
|
86
|
+
guideContacts: "联系人",
|
|
87
|
+
guidePending: "待确认",
|
|
88
|
+
guideAccept: "同意",
|
|
89
|
+
guideDiscover: "发现",
|
|
90
|
+
guideRequestExchange: "请求交换密钥",
|
|
91
|
+
resourcePageSize: "page_size 必须是 1 到 100。",
|
|
92
|
+
resourceOwner: "资源 owner 必须是非空节点 id。",
|
|
93
|
+
resourceRef: "读取资源需要 meta.resource {owner,id}。",
|
|
94
|
+
resourceSince: "since 必须是十进制事件 id。",
|
|
95
|
+
resourceOp: "读取资源可用 list、describe、log、带 event_id 的 event、operations,或带 operation_id 的 status。",
|
|
96
|
+
contextNotRegistered: "没有注册名为 {alias} 的执行上下文。",
|
|
97
|
+
mainSummary: "OMP 主会话",
|
|
98
|
+
storeClosed: "Onlyne 存储已关闭。",
|
|
99
|
+
storeNested: "Onlyne 存储已经在当前调用中持有一个事务。",
|
|
100
|
+
storeBusy: "Onlyne 存储正忙,此事务无法开始。",
|
|
101
|
+
};
|
|
102
|
+
export const hostText = createTranslator({ en, "zh-CN": zh });
|
|
103
|
+
//# sourceMappingURL=host-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-text.js","sourceRoot":"","sources":["../src/host-text.ts"],"names":[],"mappings":"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"}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type Locale = 'en' | 'zh-CN';
|
|
2
|
+
export declare const LOCALES: readonly Locale[];
|
|
3
|
+
export declare function parseLocale(value: string | null | undefined): Locale | undefined;
|
|
4
|
+
export declare function resolveLocale(explicit?: string | null, env?: Record<string, string | undefined>): Locale;
|
|
5
|
+
export declare function getLocale(): Locale;
|
|
6
|
+
export declare function setLocale(value: Locale): void;
|
|
7
|
+
export type Catalog<K extends string> = Record<Locale, Record<K, string>>;
|
|
8
|
+
export declare function createTranslator<K extends string>(catalog: Catalog<K>): (key: K, vars?: Record<string, string | number>) => string;
|
|
9
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC;AACpC,eAAO,MAAM,OAAO,EAAE,SAAS,MAAM,EAAoB,CAAC;AAE1D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKhF;AAED,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,MAAM,CAErH;AAGD,wBAAgB,SAAS,IAAI,MAAM,CAAmB;AACtD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAoB;AAClE,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAE1E,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAElI"}
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const LOCALES = ['en', 'zh-CN'];
|
|
2
|
+
export function parseLocale(value) {
|
|
3
|
+
const language = value?.trim().toLowerCase().replace(/_/g, '-');
|
|
4
|
+
if (language === 'en' || language?.startsWith('en-') || language?.startsWith('en.'))
|
|
5
|
+
return 'en';
|
|
6
|
+
if (language === 'zh' || language?.startsWith('zh-') || language?.startsWith('zh.'))
|
|
7
|
+
return 'zh-CN';
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
export function resolveLocale(explicit, env = process.env) {
|
|
11
|
+
return parseLocale(explicit) ?? parseLocale(env.ONLYNE_LANG) ?? parseLocale(env.LC_ALL || env.LANG) ?? 'en';
|
|
12
|
+
}
|
|
13
|
+
let locale = resolveLocale();
|
|
14
|
+
export function getLocale() { return locale; }
|
|
15
|
+
export function setLocale(value) { locale = value; }
|
|
16
|
+
export function createTranslator(catalog) {
|
|
17
|
+
return (key, vars) => catalog[getLocale()][key].replace(/\{(\w+)\}/g, (match, name) => vars?.[name] === undefined ? match : String(vars[name]));
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=i18n.js.map
|
package/dist/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,OAAO,GAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAE1D,MAAM,UAAU,WAAW,CAAC,KAAgC;IAC1D,MAAM,QAAQ,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAChE,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,IAAI,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACjG,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,IAAI,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACpG,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAwB,EAAE,MAA0C,OAAO,CAAC,GAAG;IAC3G,OAAO,WAAW,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AAC9G,CAAC;AAED,IAAI,MAAM,GAAW,aAAa,EAAE,CAAC;AACrC,MAAM,UAAU,SAAS,KAAa,OAAO,MAAM,CAAC,CAAC,CAAC;AACtD,MAAM,UAAU,SAAS,CAAC,KAAa,IAAU,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;AAGlE,MAAM,UAAU,gBAAgB,CAAmB,OAAmB;IACpE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC1J,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { OnlyneNode } from '@onlyne/lib';
|
|
2
|
+
import type { LogRing } from './log.ts';
|
|
3
|
+
export { createNodeKey } from './keys.ts';
|
|
4
|
+
export { NetworkDiscovery } from './network.ts';
|
|
5
|
+
export { createLogger, leveled, resolveLogLevel } from './log.ts';
|
|
6
|
+
export type { GatewayLogger, LeveledLog, LogLevel, LogLine, LogRing, LogSink } from './log.ts';
|
|
7
|
+
export { GatewayTui } from './tui.ts';
|
|
8
|
+
export interface NodeHostOptions {
|
|
9
|
+
node: OnlyneNode;
|
|
10
|
+
endpoint: string;
|
|
11
|
+
bind: string;
|
|
12
|
+
seeds?: string[];
|
|
13
|
+
/** The log ring the TUI renders; the host's logger owns it. */
|
|
14
|
+
ring?: LogRing;
|
|
15
|
+
/** Defaults to the terminal surface when both ends of the process are a TTY. */
|
|
16
|
+
interactive?: boolean;
|
|
17
|
+
/** Gateway-specific state rendered with the trust terminal. */
|
|
18
|
+
status?: () => Promise<string[]>;
|
|
19
|
+
log(message: string, fields?: Record<string, unknown>): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The host half every gateway shares: publish the bound endpoint over DNS-SD, browse for
|
|
23
|
+
* peers, re-observe the catalog, and — on a terminal — hand the operator the trust surface.
|
|
24
|
+
* Discovery only adds candidates; a key exchange still needs both fingerprints confirmed.
|
|
25
|
+
*/
|
|
26
|
+
export declare function startNodeHost(options: NodeHostOptions): Promise<{
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
}>;
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAsB,MAAM,aAAa,CAAC;AAElE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAClE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC/F,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gFAAgF;IAChF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9D;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC;IAAE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAmDjG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { NetworkDiscovery } from "./network.js";
|
|
2
|
+
import { GatewayTui } from "./tui.js";
|
|
3
|
+
export { createNodeKey } from "./keys.js";
|
|
4
|
+
export { NetworkDiscovery } from "./network.js";
|
|
5
|
+
export { createLogger, leveled, resolveLogLevel } from "./log.js";
|
|
6
|
+
export { GatewayTui } from "./tui.js";
|
|
7
|
+
/**
|
|
8
|
+
* The host half every gateway shares: publish the bound endpoint over DNS-SD, browse for
|
|
9
|
+
* peers, re-observe the catalog, and — on a terminal — hand the operator the trust surface.
|
|
10
|
+
* Discovery only adds candidates; a key exchange still needs both fingerprints confirmed.
|
|
11
|
+
*/
|
|
12
|
+
export async function startNodeHost(options) {
|
|
13
|
+
const { node, log } = options;
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const response = await node.fetch(new Request(`${options.endpoint}/description`));
|
|
16
|
+
const description = await response.json();
|
|
17
|
+
const network = new NetworkDiscovery({
|
|
18
|
+
node_id: node.auth.actor, instance_id: description.instance_id, endpoint: options.endpoint, bind: options.bind,
|
|
19
|
+
onCandidate: async (url, source, expectedNode) => {
|
|
20
|
+
if (expectedNode === node.auth.actor)
|
|
21
|
+
return;
|
|
22
|
+
await node.discovery.observe(url, source, expectedNode, controller.signal);
|
|
23
|
+
},
|
|
24
|
+
onError: error => log('onlyne.discovery.failed', { error: String(error) }),
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
await network.start();
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
log('onlyne.discovery.unavailable', { error: String(error) });
|
|
31
|
+
}
|
|
32
|
+
for (const seed of options.seeds ?? []) {
|
|
33
|
+
try {
|
|
34
|
+
await node.discovery.observe(seed, 'bootstrap', undefined, controller.signal);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
log('onlyne.discovery.failed', { seed, error: String(error) });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
let refreshing = false;
|
|
41
|
+
const timer = setInterval(() => {
|
|
42
|
+
if (refreshing)
|
|
43
|
+
return;
|
|
44
|
+
refreshing = true;
|
|
45
|
+
void node.discovery.refresh(controller.signal)
|
|
46
|
+
.catch(error => { if (!controller.signal.aborted)
|
|
47
|
+
log('onlyne.discovery.failed', { error: String(error) }); })
|
|
48
|
+
.finally(() => { refreshing = false; });
|
|
49
|
+
}, 30_000);
|
|
50
|
+
let tui;
|
|
51
|
+
// `ONLYNE_NO_TUI=1` keeps the plain JSON log on stdout, for a pipe or a screen a TUI would spoil.
|
|
52
|
+
const interactive = (options.interactive ?? true) && process.env.ONLYNE_NO_TUI !== '1';
|
|
53
|
+
if (interactive && options.ring !== undefined && process.stdin.isTTY && process.stdout.isTTY) {
|
|
54
|
+
const surface = new GatewayTui(node, options.ring, options.endpoint, undefined, options.status);
|
|
55
|
+
try {
|
|
56
|
+
log('onlyne.tui.starting');
|
|
57
|
+
await surface.start();
|
|
58
|
+
tui = surface;
|
|
59
|
+
log('onlyne.tui.ready');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
log('onlyne.tui.unavailable', { error: String(error) });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
async close() {
|
|
67
|
+
clearInterval(timer);
|
|
68
|
+
await tui?.close();
|
|
69
|
+
controller.abort();
|
|
70
|
+
await network.close();
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/keys.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { KeyHook, KeyMaterial } from "@onlyne/lib";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the key directory for a node (RFC 0017 §2). A project key inside a git work
|
|
4
|
+
* tree whose ignore rules do not cover `.onlyne/keys` is refused, with both escapes named.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ensureKeyDir(cwd: string, nodeId: string, scope: "project" | "global"): Promise<string>;
|
|
7
|
+
/** Loads the node's identity key, generating it on first use. */
|
|
8
|
+
export declare function loadOrCreateKey(dir: string, nodeId: string): Promise<KeyMaterial>;
|
|
9
|
+
/**
|
|
10
|
+
* Writes the key pair atomically. `replace` is the `plugins.keys.replace` path — the only
|
|
11
|
+
* caller allowed to overwrite an existing private key, because `trust.rotate` has already
|
|
12
|
+
* committed the rotation window and must complete the swap.
|
|
13
|
+
*/
|
|
14
|
+
export declare function writeKeyMaterial(dir: string, material: KeyMaterial, options?: {
|
|
15
|
+
replace?: boolean;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
/** Synchronous guard for the startup path: the existing key must already be private. */
|
|
18
|
+
export declare function assertKeyPermissions(dir: string): void;
|
|
19
|
+
export declare function createNodeKey(nodeId: string): Promise<KeyHook>;
|
|
20
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAuExD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAmB5G;AAED,iEAAiE;AACjE,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAkBvF;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAe7H;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAMtD;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKpE"}
|