agent-comms 8.2.1 → 8.3.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +15 -1
- package/dist/bridges/user/web/dist/bundle.js +1 -1
- package/dist/bridges/user/web/dist/sw.js +1 -1
- package/dist/bridges/user/web/frontend/boot-logic.d.ts +9 -8
- package/dist/bridges/user/web/frontend/boot-logic.d.ts.map +1 -1
- package/dist/bridges/user/web/frontend/boot-logic.js +9 -12
- package/dist/bridges/user/web/frontend/boot-logic.js.map +1 -1
- package/dist/bridges/user/web/frontend/main.js +3 -6
- package/dist/bridges/user/web/frontend/main.js.map +1 -1
- package/dist/bridges/user/web/frontend/mesh-client.d.ts.map +1 -1
- package/dist/bridges/user/web/frontend/mesh-client.js +3 -4
- package/dist/bridges/user/web/frontend/mesh-client.js.map +1 -1
- package/dist/bridges/user/web/frontend/served-by-bridge.d.ts +10 -0
- package/dist/bridges/user/web/frontend/served-by-bridge.d.ts.map +1 -0
- package/dist/bridges/user/web/frontend/served-by-bridge.js +10 -0
- package/dist/bridges/user/web/frontend/served-by-bridge.js.map +1 -0
- package/dist/bridges/user/web/server.d.ts +10 -0
- package/dist/bridges/user/web/server.d.ts.map +1 -1
- package/dist/bridges/user/web/server.js +84 -7
- package/dist/bridges/user/web/server.js.map +1 -1
- package/dist/bridges/user/web/test/lan-web-server.integration.test.d.ts +7 -0
- package/dist/bridges/user/web/test/lan-web-server.integration.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/lan-web-server.integration.test.js +245 -0
- package/dist/bridges/user/web/test/lan-web-server.integration.test.js.map +1 -0
- package/dist/bridges/user/web/test/web-access.unit.test.d.ts +5 -0
- package/dist/bridges/user/web/test/web-access.unit.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/web-access.unit.test.js +271 -0
- package/dist/bridges/user/web/test/web-access.unit.test.js.map +1 -0
- package/dist/bridges/user/web/test/web-beacon.unit.test.d.ts +5 -0
- package/dist/bridges/user/web/test/web-beacon.unit.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/web-beacon.unit.test.js +40 -0
- package/dist/bridges/user/web/test/web-beacon.unit.test.js.map +1 -0
- package/dist/bridges/user/web/web-access.d.ts +79 -0
- package/dist/bridges/user/web/web-access.d.ts.map +1 -0
- package/dist/bridges/user/web/web-access.js +262 -0
- package/dist/bridges/user/web/web-access.js.map +1 -0
- package/dist/bridges/user/web/web-beacon.d.ts +27 -0
- package/dist/bridges/user/web/web-beacon.d.ts.map +1 -0
- package/dist/bridges/user/web/web-beacon.js +45 -0
- package/dist/bridges/user/web/web-beacon.js.map +1 -0
- package/dist/core/first-contact.d.ts +2 -0
- package/dist/core/first-contact.d.ts.map +1 -1
- package/dist/core/first-contact.js +1 -1
- package/dist/core/first-contact.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network exposure policy for the bridge web server (agent-comms#346).
|
|
3
|
+
*
|
|
4
|
+
* The default is loopback only, with no token. Setting AGENT_COMMS_WEB_HOST to a non-loopback address is the explicit opt-in to LAN reachability. Because the server exposes every mutating action (POST /api/action and the /ws/mesh oRPC socket), a non-loopback bind always comes with a per-process secret token. Every request, from any client, must carry a Host header naming this server (the DNS rebinding defence: a rebound name resolves here but arrives with the attacker's name in Host). A client on this machine (loopback, or one of this machine's own interface addresses) needs nothing more. Any other client must also present the token (as a bearer token, or as the cookie a one-time `?token=` URL exchange sets) and, when it carries an Origin, carry one that names the same host.
|
|
5
|
+
*/
|
|
6
|
+
/** The environment variable that opts the web server into binding beyond loopback. Its value is an IP address literal to bind: `0.0.0.0` or `::` for every interface, or one interface's own address. */
|
|
7
|
+
export declare const WEB_HOST_ENV = "AGENT_COMMS_WEB_HOST";
|
|
8
|
+
/** The default bind address, reachable from this machine only. */
|
|
9
|
+
export declare const LOOPBACK_HOST = "127.0.0.1";
|
|
10
|
+
/** The prefix of the cookie a `?token=` exchange sets. Cookies are scoped to a host and not to a port (RFC 6265 section 8.5), and every bridge on a machine runs its own server on its own port with its own token, so the name carries the port to keep one browser's cookies for two bridges from overwriting each other. */
|
|
11
|
+
export declare const ACCESS_COOKIE_PREFIX = "agent_comms_web_token";
|
|
12
|
+
/** The name of the cookie a server listening on `port` sets and reads. */
|
|
13
|
+
export declare function accessCookieName(port: number): string;
|
|
14
|
+
/** The query parameter a one-time URL exchange carries the token in. */
|
|
15
|
+
export declare const ACCESS_QUERY_PARAM = "token";
|
|
16
|
+
/** True for any address in 127.0.0.0/8, for ::1, and for an IPv4-mapped form of either (what a dual-stack socket reports for an IPv4 client). */
|
|
17
|
+
export declare function isLoopbackAddress(address: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The canonical spelling of an IP address literal: IPv6 in its compressed form, and an IPv4-mapped IPv6 address as plain IPv4. Every spelling of one address (`::0`, `0:0:0:0:0:0:0:0`, `::ffff:0.0.0.0`) then compares equal, which classification and the Host allow-list rely on. A scoped address (`fe80::1%en0`) is returned unchanged: it is neither a wildcard nor a loopback.
|
|
20
|
+
*/
|
|
21
|
+
export declare function canonicalAddress(address: string): string;
|
|
22
|
+
/** True when `host` binds every interface, in any spelling (see canonicalAddress). */
|
|
23
|
+
export declare function isWildcardHost(host: string): boolean;
|
|
24
|
+
/** The host part of an `http` URL for `address`: IPv6 literals are bracketed. */
|
|
25
|
+
export declare function urlHost(address: string): string;
|
|
26
|
+
/** The URL a client on this machine opens for a server bound to `bindHost` on `port`. A wildcard bind is reached on loopback, which needs no token. */
|
|
27
|
+
export declare function localWebUrl(bindHost: string, port: number): string;
|
|
28
|
+
/**
|
|
29
|
+
* Resolves the address the web server binds. An explicit `host` option wins, then the environment variable, then loopback. The result is the canonical spelling of the address. Anything that is not an IP address literal throws: a typo must not silently fall back to a different exposure than the operator asked for.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveWebBindHost(explicit: string | undefined, env: NodeJS.ProcessEnv): string;
|
|
32
|
+
/** True when binding `host` makes the server reachable from another machine, so the access token applies. */
|
|
33
|
+
export declare function requiresAccessToken(host: string): boolean;
|
|
34
|
+
export declare function generateAccessToken(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Constant-time token comparison. Both sides are hashed to a fixed length first, because crypto.timingSafeEqual requires equal-length inputs and comparing lengths directly would leak the expected token's length.
|
|
37
|
+
*/
|
|
38
|
+
export declare function tokensMatch(presented: string, expected: string): boolean;
|
|
39
|
+
/** The addresses of this machine's own network interfaces. */
|
|
40
|
+
export declare function ownInterfaceAddresses(): ReadonlySet<string>;
|
|
41
|
+
/** The exposure a running server is under. */
|
|
42
|
+
export interface AccessPolicy {
|
|
43
|
+
/** The token every client not on this machine must present. Undefined for a loopback bind, which no such client can reach. */
|
|
44
|
+
readonly token: string | undefined;
|
|
45
|
+
readonly bindHost: string;
|
|
46
|
+
/** The port the server listens on; assigned once it is listening when the OS chose it. */
|
|
47
|
+
port: number;
|
|
48
|
+
/** The addresses whose clients count as on this machine besides loopback, read per request. A client connecting to this machine's own LAN address arrives from that address and not from loopback, so without this the `web_url` of a server bound to one interface address would answer 401 to the operator. Tests replace it to stand in for a remote client. */
|
|
49
|
+
readonly localClientAddresses: () => ReadonlySet<string>;
|
|
50
|
+
}
|
|
51
|
+
/** The parts of an incoming request the gate looks at. Shared by HTTP requests and WebSocket upgrades (an upgrade is an HTTP request). */
|
|
52
|
+
export interface AccessRequest {
|
|
53
|
+
readonly method: string;
|
|
54
|
+
readonly url: string;
|
|
55
|
+
readonly remoteAddress: string | undefined;
|
|
56
|
+
readonly headers: Readonly<Record<string, string | string[] | undefined>>;
|
|
57
|
+
}
|
|
58
|
+
export type AccessDecision = {
|
|
59
|
+
readonly kind: "allow";
|
|
60
|
+
} | {
|
|
61
|
+
readonly kind: "deny";
|
|
62
|
+
readonly status: number;
|
|
63
|
+
readonly message: string;
|
|
64
|
+
} | {
|
|
65
|
+
/** The one-time URL exchange: set the cookie and redirect to a URL without the token, so it does not stay in the address bar or history. */
|
|
66
|
+
readonly kind: "exchange";
|
|
67
|
+
readonly status: number;
|
|
68
|
+
readonly cookie: string;
|
|
69
|
+
readonly location: string;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Decides whether a request may proceed. The Host header must name this server, for every client. A client on this machine is then allowed. Any other client needs the token (a bearer token or the cookie), and any Origin it sends must be this server; a GET carrying the right `?token=` instead exchanges it for the cookie.
|
|
73
|
+
*/
|
|
74
|
+
export declare function authorise(policy: Readonly<AccessPolicy>, request: AccessRequest): AccessDecision;
|
|
75
|
+
/** The URLs an operator can open on the LAN, each carrying the token for the one-time exchange. A wildcard bind lists every external IPv4 address of this machine; a specific bind lists that address. */
|
|
76
|
+
export declare function accessUrls(policy: Readonly<AccessPolicy> & {
|
|
77
|
+
readonly token: string;
|
|
78
|
+
}): string[];
|
|
79
|
+
//# sourceMappingURL=web-access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-access.d.ts","sourceRoot":"","sources":["../../../../src/bridges/user/web/web-access.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,yMAAyM;AACzM,eAAO,MAAM,YAAY,yBAAyB,CAAC;AAEnD,kEAAkE;AAClE,eAAO,MAAM,aAAa,cAAc,CAAC;AAEzC,+TAA+T;AAC/T,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;AAE5D,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wEAAwE;AACxE,eAAO,MAAM,kBAAkB,UAAU,CAAC;AA4B1C,iJAAiJ;AACjJ,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK1D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAaxD;AAED,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGpD;AAED,iFAAiF;AACjF,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,uJAAuJ;AACvJ,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGlE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,MAAM,CASR;AAED,6GAA6G;AAC7G,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIxE;AAED,8DAA8D;AAC9D,wBAAgB,qBAAqB,IAAI,WAAW,CAAC,MAAM,CAAC,CAQ3D;AAED,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IAC3B,8HAA8H;IAC9H,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0FAA0F;IAC1F,IAAI,EAAE,MAAM,CAAC;IACb,mWAAmW;IACnW,QAAQ,CAAC,oBAAoB,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;CAC1D;AAED,0IAA0I;AAC1I,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;CAC3E;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC5E;IACE,4IAA4I;IAC5I,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AA0HN;;GAEG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC9B,OAAO,EAAE,aAAa,GACrB,cAAc,CA4BhB;AAED,0MAA0M;AAC1M,wBAAgB,UAAU,CACxB,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1D,MAAM,EAAE,CAgBV"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network exposure policy for the bridge web server (agent-comms#346).
|
|
3
|
+
*
|
|
4
|
+
* The default is loopback only, with no token. Setting AGENT_COMMS_WEB_HOST to a non-loopback address is the explicit opt-in to LAN reachability. Because the server exposes every mutating action (POST /api/action and the /ws/mesh oRPC socket), a non-loopback bind always comes with a per-process secret token. Every request, from any client, must carry a Host header naming this server (the DNS rebinding defence: a rebound name resolves here but arrives with the attacker's name in Host). A client on this machine (loopback, or one of this machine's own interface addresses) needs nothing more. Any other client must also present the token (as a bearer token, or as the cookie a one-time `?token=` URL exchange sets) and, when it carries an Origin, carry one that names the same host.
|
|
5
|
+
*/
|
|
6
|
+
import * as crypto from "node:crypto";
|
|
7
|
+
import * as net from "node:net";
|
|
8
|
+
import * as os from "node:os";
|
|
9
|
+
/** The environment variable that opts the web server into binding beyond loopback. Its value is an IP address literal to bind: `0.0.0.0` or `::` for every interface, or one interface's own address. */
|
|
10
|
+
export const WEB_HOST_ENV = "AGENT_COMMS_WEB_HOST";
|
|
11
|
+
/** The default bind address, reachable from this machine only. */
|
|
12
|
+
export const LOOPBACK_HOST = "127.0.0.1";
|
|
13
|
+
/** The prefix of the cookie a `?token=` exchange sets. Cookies are scoped to a host and not to a port (RFC 6265 section 8.5), and every bridge on a machine runs its own server on its own port with its own token, so the name carries the port to keep one browser's cookies for two bridges from overwriting each other. */
|
|
14
|
+
export const ACCESS_COOKIE_PREFIX = "agent_comms_web_token";
|
|
15
|
+
/** The name of the cookie a server listening on `port` sets and reads. */
|
|
16
|
+
export function accessCookieName(port) {
|
|
17
|
+
return `${ACCESS_COOKIE_PREFIX}_${String(port)}`;
|
|
18
|
+
}
|
|
19
|
+
/** The query parameter a one-time URL exchange carries the token in. */
|
|
20
|
+
export const ACCESS_QUERY_PARAM = "token";
|
|
21
|
+
/** Random bytes in the access token: 256 bits, so guessing is infeasible however many requests a LAN peer can send. */
|
|
22
|
+
const ACCESS_TOKEN_BYTES = 32;
|
|
23
|
+
/** The port a Host header with no explicit port names (HTTP). */
|
|
24
|
+
const HTTP_DEFAULT_PORT = 80;
|
|
25
|
+
const HTTP_UNAUTHORIZED = 401;
|
|
26
|
+
const HTTP_FORBIDDEN = 403;
|
|
27
|
+
const HTTP_FOUND = 302;
|
|
28
|
+
const BEARER_PREFIX = "Bearer ";
|
|
29
|
+
const LOOPBACK_V4_PREFIX = "127.";
|
|
30
|
+
const IPV4_MAPPED_PREFIX = "::ffff:";
|
|
31
|
+
const LOOPBACK_V6 = "::1";
|
|
32
|
+
const IPV4_WILDCARD = "0.0.0.0";
|
|
33
|
+
const IPV6_WILDCARD = "::";
|
|
34
|
+
/** The names a client on this machine uses for loopback, always accepted in Host whatever the bind. */
|
|
35
|
+
const LOOPBACK_HOSTNAMES = ["localhost", LOOPBACK_HOST, LOOPBACK_V6];
|
|
36
|
+
/** The radix IPv6 groups are written in, and the bits and mask of one byte of the IPv4 address a mapped group pair spells. */
|
|
37
|
+
const HEX_RADIX = 16;
|
|
38
|
+
const BYTE_BITS = 8;
|
|
39
|
+
const BYTE_MASK = 0xff;
|
|
40
|
+
/** True for any address in 127.0.0.0/8, for ::1, and for an IPv4-mapped form of either (what a dual-stack socket reports for an IPv4 client). */
|
|
41
|
+
export function isLoopbackAddress(address) {
|
|
42
|
+
const unmapped = address.startsWith(IPV4_MAPPED_PREFIX)
|
|
43
|
+
? address.slice(IPV4_MAPPED_PREFIX.length)
|
|
44
|
+
: address;
|
|
45
|
+
return unmapped === LOOPBACK_V6 || unmapped.startsWith(LOOPBACK_V4_PREFIX);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The canonical spelling of an IP address literal: IPv6 in its compressed form, and an IPv4-mapped IPv6 address as plain IPv4. Every spelling of one address (`::0`, `0:0:0:0:0:0:0:0`, `::ffff:0.0.0.0`) then compares equal, which classification and the Host allow-list rely on. A scoped address (`fe80::1%en0`) is returned unchanged: it is neither a wildcard nor a loopback.
|
|
49
|
+
*/
|
|
50
|
+
export function canonicalAddress(address) {
|
|
51
|
+
if (net.isIPv4(address) || address.includes("%"))
|
|
52
|
+
return address;
|
|
53
|
+
const compressed = new URL(`http://[${address}]`).hostname.slice(1, -1);
|
|
54
|
+
if (!compressed.startsWith(IPV4_MAPPED_PREFIX))
|
|
55
|
+
return compressed;
|
|
56
|
+
const tail = compressed.slice(IPV4_MAPPED_PREFIX.length);
|
|
57
|
+
if (net.isIPv4(tail))
|
|
58
|
+
return tail;
|
|
59
|
+
return tail
|
|
60
|
+
.split(":")
|
|
61
|
+
.flatMap((group) => {
|
|
62
|
+
const value = Number.parseInt(group, HEX_RADIX);
|
|
63
|
+
return [value >> BYTE_BITS, value & BYTE_MASK];
|
|
64
|
+
})
|
|
65
|
+
.join(".");
|
|
66
|
+
}
|
|
67
|
+
/** True when `host` binds every interface, in any spelling (see canonicalAddress). */
|
|
68
|
+
export function isWildcardHost(host) {
|
|
69
|
+
const canonical = canonicalAddress(host);
|
|
70
|
+
return canonical === IPV4_WILDCARD || canonical === IPV6_WILDCARD;
|
|
71
|
+
}
|
|
72
|
+
/** The host part of an `http` URL for `address`: IPv6 literals are bracketed. */
|
|
73
|
+
export function urlHost(address) {
|
|
74
|
+
return net.isIPv6(address) ? `[${address}]` : address;
|
|
75
|
+
}
|
|
76
|
+
/** The URL a client on this machine opens for a server bound to `bindHost` on `port`. A wildcard bind is reached on loopback, which needs no token. */
|
|
77
|
+
export function localWebUrl(bindHost, port) {
|
|
78
|
+
const host = isWildcardHost(bindHost) ? LOOPBACK_HOST : bindHost;
|
|
79
|
+
return `http://${urlHost(host)}:${String(port)}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Resolves the address the web server binds. An explicit `host` option wins, then the environment variable, then loopback. The result is the canonical spelling of the address. Anything that is not an IP address literal throws: a typo must not silently fall back to a different exposure than the operator asked for.
|
|
83
|
+
*/
|
|
84
|
+
export function resolveWebBindHost(explicit, env) {
|
|
85
|
+
const requested = explicit ?? env[WEB_HOST_ENV];
|
|
86
|
+
if (requested === undefined || requested === "")
|
|
87
|
+
return LOOPBACK_HOST;
|
|
88
|
+
if (net.isIP(requested) === 0) {
|
|
89
|
+
throw new Error(`${WEB_HOST_ENV} must be an IP address (for example 0.0.0.0), received "${requested}"`);
|
|
90
|
+
}
|
|
91
|
+
return canonicalAddress(requested);
|
|
92
|
+
}
|
|
93
|
+
/** True when binding `host` makes the server reachable from another machine, so the access token applies. */
|
|
94
|
+
export function requiresAccessToken(host) {
|
|
95
|
+
return !isLoopbackAddress(host);
|
|
96
|
+
}
|
|
97
|
+
export function generateAccessToken() {
|
|
98
|
+
return crypto.randomBytes(ACCESS_TOKEN_BYTES).toString("hex");
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Constant-time token comparison. Both sides are hashed to a fixed length first, because crypto.timingSafeEqual requires equal-length inputs and comparing lengths directly would leak the expected token's length.
|
|
102
|
+
*/
|
|
103
|
+
export function tokensMatch(presented, expected) {
|
|
104
|
+
const digest = (value) => crypto.createHash("sha256").update(value).digest();
|
|
105
|
+
return crypto.timingSafeEqual(digest(presented), digest(expected));
|
|
106
|
+
}
|
|
107
|
+
/** The addresses of this machine's own network interfaces. */
|
|
108
|
+
export function ownInterfaceAddresses() {
|
|
109
|
+
const addresses = new Set();
|
|
110
|
+
for (const entries of Object.values(os.networkInterfaces())) {
|
|
111
|
+
for (const entry of entries ?? []) {
|
|
112
|
+
addresses.add(canonicalAddress(entry.address));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return addresses;
|
|
116
|
+
}
|
|
117
|
+
function headerValue(headers, name) {
|
|
118
|
+
const value = headers[name];
|
|
119
|
+
return Array.isArray(value) ? value[0] : value;
|
|
120
|
+
}
|
|
121
|
+
/** Every hostname a client may legitimately use to reach this server: loopback names, the addresses it is bound to and this machine's own name (plus its `.local` mDNS form). Never a name an outside party could make resolve here, which is what DNS rebinding relies on, and never the wildcard address itself, which a page in a local browser can also reach. */
|
|
122
|
+
function allowedHostnames(bindHost) {
|
|
123
|
+
const names = new Set(LOOPBACK_HOSTNAMES);
|
|
124
|
+
if (isWildcardHost(bindHost)) {
|
|
125
|
+
for (const address of ownInterfaceAddresses())
|
|
126
|
+
names.add(address);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
names.add(bindHost.toLowerCase());
|
|
130
|
+
}
|
|
131
|
+
const machine = os
|
|
132
|
+
.hostname()
|
|
133
|
+
.toLowerCase()
|
|
134
|
+
.replace(/\.local$/, "");
|
|
135
|
+
names.add(machine);
|
|
136
|
+
names.add(`${machine}.local`);
|
|
137
|
+
return names;
|
|
138
|
+
}
|
|
139
|
+
/** A Host header names either an IP literal (compared in canonical form) or a DNS name (compared as is). */
|
|
140
|
+
function canonicalAddressOrName(hostname) {
|
|
141
|
+
return net.isIP(hostname) === 0 ? hostname : canonicalAddress(hostname);
|
|
142
|
+
}
|
|
143
|
+
function parseAuthority(authority) {
|
|
144
|
+
let parsed;
|
|
145
|
+
try {
|
|
146
|
+
parsed = new URL(`http://${authority}`);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const hostname = canonicalAddressOrName(parsed.hostname.replace(/^\[|\]$/g, "").toLowerCase());
|
|
152
|
+
const port = parsed.port === "" ? HTTP_DEFAULT_PORT : Number(parsed.port);
|
|
153
|
+
return { hostname, port };
|
|
154
|
+
}
|
|
155
|
+
function hostHeaderAllowed(policy, headers) {
|
|
156
|
+
const host = headerValue(headers, "host");
|
|
157
|
+
if (host === undefined)
|
|
158
|
+
return false;
|
|
159
|
+
const authority = parseAuthority(host);
|
|
160
|
+
if (authority === undefined)
|
|
161
|
+
return false;
|
|
162
|
+
if (authority.port !== policy.port)
|
|
163
|
+
return false;
|
|
164
|
+
return allowedHostnames(policy.bindHost).has(authority.hostname);
|
|
165
|
+
}
|
|
166
|
+
/** An Origin, when the browser sends one, must be this very server: otherwise a page from elsewhere on the LAN could drive it with the operator's cookie. */
|
|
167
|
+
function originAllowed(headers) {
|
|
168
|
+
const origin = headerValue(headers, "origin");
|
|
169
|
+
if (origin === undefined)
|
|
170
|
+
return true;
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = new URL(origin);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return (parsed.host.toLowerCase() === headerValue(headers, "host")?.toLowerCase());
|
|
179
|
+
}
|
|
180
|
+
function cookieToken(headers, cookieName) {
|
|
181
|
+
const cookies = headerValue(headers, "cookie");
|
|
182
|
+
if (cookies === undefined)
|
|
183
|
+
return undefined;
|
|
184
|
+
for (const part of cookies.split(";")) {
|
|
185
|
+
const [name, ...rest] = part.trim().split("=");
|
|
186
|
+
if (name === cookieName)
|
|
187
|
+
return rest.join("=");
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
function bearerToken(headers) {
|
|
192
|
+
const authorization = headerValue(headers, "authorization");
|
|
193
|
+
if (authorization === undefined)
|
|
194
|
+
return undefined;
|
|
195
|
+
return authorization.startsWith(BEARER_PREFIX)
|
|
196
|
+
? authorization.slice(BEARER_PREFIX.length)
|
|
197
|
+
: undefined;
|
|
198
|
+
}
|
|
199
|
+
function presentedCredential(policy, token, headers) {
|
|
200
|
+
const presented = [
|
|
201
|
+
bearerToken(headers),
|
|
202
|
+
cookieToken(headers, accessCookieName(policy.port)),
|
|
203
|
+
];
|
|
204
|
+
return presented.some((candidate) => candidate !== undefined && tokensMatch(candidate, token));
|
|
205
|
+
}
|
|
206
|
+
/** True for a client on this machine: loopback, or one of this machine's own interface addresses. */
|
|
207
|
+
function isOwnClient(policy, remoteAddress) {
|
|
208
|
+
if (remoteAddress === undefined)
|
|
209
|
+
return false;
|
|
210
|
+
return (isLoopbackAddress(remoteAddress) ||
|
|
211
|
+
policy.localClientAddresses().has(canonicalAddress(remoteAddress)));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Decides whether a request may proceed. The Host header must name this server, for every client. A client on this machine is then allowed. Any other client needs the token (a bearer token or the cookie), and any Origin it sends must be this server; a GET carrying the right `?token=` instead exchanges it for the cookie.
|
|
215
|
+
*/
|
|
216
|
+
export function authorise(policy, request) {
|
|
217
|
+
if (!hostHeaderAllowed(policy, request.headers)) {
|
|
218
|
+
return { kind: "deny", status: HTTP_FORBIDDEN, message: "Forbidden host" };
|
|
219
|
+
}
|
|
220
|
+
if (isOwnClient(policy, request.remoteAddress))
|
|
221
|
+
return { kind: "allow" };
|
|
222
|
+
const { token } = policy;
|
|
223
|
+
if (token === undefined) {
|
|
224
|
+
return { kind: "deny", status: HTTP_UNAUTHORIZED, message: "Unauthorized" };
|
|
225
|
+
}
|
|
226
|
+
if (presentedCredential(policy, token, request.headers)) {
|
|
227
|
+
return originAllowed(request.headers)
|
|
228
|
+
? { kind: "allow" }
|
|
229
|
+
: { kind: "deny", status: HTTP_FORBIDDEN, message: "Forbidden origin" };
|
|
230
|
+
}
|
|
231
|
+
if (request.method === "GET") {
|
|
232
|
+
const url = new URL(request.url, "http://placeholder");
|
|
233
|
+
const offered = url.searchParams.get(ACCESS_QUERY_PARAM);
|
|
234
|
+
if (offered !== null && tokensMatch(offered, token)) {
|
|
235
|
+
url.searchParams.delete(ACCESS_QUERY_PARAM);
|
|
236
|
+
return {
|
|
237
|
+
kind: "exchange",
|
|
238
|
+
status: HTTP_FOUND,
|
|
239
|
+
cookie: `${accessCookieName(policy.port)}=${token}; HttpOnly; SameSite=Strict; Path=/`,
|
|
240
|
+
location: `${url.pathname}${url.search}`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return { kind: "deny", status: HTTP_UNAUTHORIZED, message: "Unauthorized" };
|
|
245
|
+
}
|
|
246
|
+
/** The URLs an operator can open on the LAN, each carrying the token for the one-time exchange. A wildcard bind lists every external IPv4 address of this machine; a specific bind lists that address. */
|
|
247
|
+
export function accessUrls(policy) {
|
|
248
|
+
const hosts = [];
|
|
249
|
+
if (isWildcardHost(policy.bindHost)) {
|
|
250
|
+
for (const addresses of Object.values(os.networkInterfaces())) {
|
|
251
|
+
for (const entry of addresses ?? []) {
|
|
252
|
+
if (!entry.internal && entry.family === "IPv4")
|
|
253
|
+
hosts.push(entry.address);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
hosts.push(urlHost(policy.bindHost));
|
|
259
|
+
}
|
|
260
|
+
return hosts.map((host) => `http://${host}:${String(policy.port)}/?${ACCESS_QUERY_PARAM}=${policy.token}`);
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=web-access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-access.js","sourceRoot":"","sources":["../../../../src/bridges/user/web/web-access.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LAN advertisement of a bridge's web UI (agent-comms#346).
|
|
3
|
+
*
|
|
4
|
+
* A send-only UDP beacon on the first-contact port announcing where this bridge's web UI listens. It carries the peer id and the web port and nothing else: the receiver learns the host from the datagram's source address, and the access token is never advertised, so seeing the beacon grants no access. The type is distinct from first-contact's own `agent-comms-beacon`, which peers parse for a data port to dial; first-contact ignores any other type, so the two never interfere.
|
|
5
|
+
*/
|
|
6
|
+
/** The beacon type, distinct from first-contact's `agent-comms-beacon`. */
|
|
7
|
+
export declare const WEB_BEACON_TYPE = "agent-comms-web-beacon";
|
|
8
|
+
export interface WebBeaconPayload {
|
|
9
|
+
type: typeof WEB_BEACON_TYPE;
|
|
10
|
+
peerId: string;
|
|
11
|
+
webPort: number;
|
|
12
|
+
}
|
|
13
|
+
export interface WebBeaconOptions {
|
|
14
|
+
peerId: string;
|
|
15
|
+
webPort: number;
|
|
16
|
+
/** The UDP port beacons are sent to. Defaults to the first-contact port; tests pass an OS-assigned free port. */
|
|
17
|
+
port?: number | undefined;
|
|
18
|
+
/** The destination address. Defaults to limited broadcast; tests pass a loopback address so no real network is needed. */
|
|
19
|
+
address?: string;
|
|
20
|
+
onError?: (error: Error) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface WebBeacon {
|
|
23
|
+
stop: () => void;
|
|
24
|
+
}
|
|
25
|
+
/** Starts beaconing immediately and on first-contact's interval. A send failure (broadcast unavailable on this network) is reported through onError and never fatal. */
|
|
26
|
+
export declare function startWebBeacon(options: Readonly<WebBeaconOptions>): WebBeacon;
|
|
27
|
+
//# sourceMappingURL=web-beacon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-beacon.d.ts","sourceRoot":"","sources":["../../../../src/bridges/user/web/web-beacon.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,2EAA2E;AAC3E,eAAO,MAAM,eAAe,2BAA2B,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,eAAe,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,iHAAiH;IACjH,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,0HAA0H;IAC1H,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wKAAwK;AACxK,wBAAgB,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,SAAS,CA+B7E"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LAN advertisement of a bridge's web UI (agent-comms#346).
|
|
3
|
+
*
|
|
4
|
+
* A send-only UDP beacon on the first-contact port announcing where this bridge's web UI listens. It carries the peer id and the web port and nothing else: the receiver learns the host from the datagram's source address, and the access token is never advertised, so seeing the beacon grants no access. The type is distinct from first-contact's own `agent-comms-beacon`, which peers parse for a data port to dial; first-contact ignores any other type, so the two never interfere.
|
|
5
|
+
*/
|
|
6
|
+
import * as dgram from "node:dgram";
|
|
7
|
+
import { BROADCAST_ADDRESS, FIRST_CONTACT_INTERVAL_MS, FIRST_CONTACT_PORT, } from "../../../core/first-contact.js";
|
|
8
|
+
/** The beacon type, distinct from first-contact's `agent-comms-beacon`. */
|
|
9
|
+
export const WEB_BEACON_TYPE = "agent-comms-web-beacon";
|
|
10
|
+
/** Starts beaconing immediately and on first-contact's interval. A send failure (broadcast unavailable on this network) is reported through onError and never fatal. */
|
|
11
|
+
export function startWebBeacon(options) {
|
|
12
|
+
const socket = dgram.createSocket({ type: "udp4" });
|
|
13
|
+
const destinationPort = options.port ?? FIRST_CONTACT_PORT;
|
|
14
|
+
const address = options.address ?? BROADCAST_ADDRESS;
|
|
15
|
+
const payload = {
|
|
16
|
+
type: WEB_BEACON_TYPE,
|
|
17
|
+
peerId: options.peerId,
|
|
18
|
+
webPort: options.webPort,
|
|
19
|
+
};
|
|
20
|
+
const message = JSON.stringify(payload);
|
|
21
|
+
const reportError = (error) => {
|
|
22
|
+
options.onError?.(error);
|
|
23
|
+
};
|
|
24
|
+
socket.on("error", reportError);
|
|
25
|
+
const send = () => {
|
|
26
|
+
socket.send(message, destinationPort, address, (error) => {
|
|
27
|
+
if (error)
|
|
28
|
+
reportError(error);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
socket.bind(0, () => {
|
|
32
|
+
if (address === BROADCAST_ADDRESS)
|
|
33
|
+
socket.setBroadcast(true);
|
|
34
|
+
send();
|
|
35
|
+
});
|
|
36
|
+
const timer = setInterval(send, FIRST_CONTACT_INTERVAL_MS);
|
|
37
|
+
timer.unref();
|
|
38
|
+
return {
|
|
39
|
+
stop() {
|
|
40
|
+
clearInterval(timer);
|
|
41
|
+
socket.close();
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=web-beacon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-beacon.js","sourceRoot":"","sources":["../../../../src/bridges/user/web/web-beacon.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AAExC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAsBxD,wKAAwK;AACxK,MAAM,UAAU,cAAc,CAAC,OAAmC;IAChE,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACpD,MAAM,eAAe,GAAG,OAAO,CAAC,IAAI,IAAI,kBAAkB,CAAC;IAC3D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,iBAAiB,CAAC;IACrD,MAAM,OAAO,GAAqB;QAChC,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,WAAW,GAAG,CAAC,KAAY,EAAQ,EAAE;QACzC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC,CAAC;IACF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,GAAS,EAAE;QACtB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACvD,IAAI,KAAK;gBAAE,WAAW,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE;QAClB,IAAI,OAAO,KAAK,iBAAiB;YAAE,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAC7D,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,yBAAyB,CAAC,CAAC;IAC3D,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,OAAO;QACL,IAAI;YACF,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -14,6 +14,8 @@ import type { PeerInfo } from "./wire-protocol.js";
|
|
|
14
14
|
export declare const FIRST_CONTACT_PORT = 19877;
|
|
15
15
|
/** The administratively scoped multicast group (239.255.0.0/16, organisation-local scope) joined on the loopback interface, so every same-host socket bound to the beacon port hears every packet whatever the state of the network. */
|
|
16
16
|
export declare const FIRST_CONTACT_GROUP = "239.255.19.77";
|
|
17
|
+
/** The limited-broadcast address that carries beacons and probes to other machines on the LAN. */
|
|
18
|
+
export declare const BROADCAST_ADDRESS = "255.255.255.255";
|
|
17
19
|
/** How often the beacon repeats once started; matching discovery-mdns.ts's own interval so the two presences behave alike on the wire. */
|
|
18
20
|
export declare const FIRST_CONTACT_INTERVAL_MS = 30000;
|
|
19
21
|
/** A peer this side discovered and can dial directly. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"first-contact.d.ts","sourceRoot":"","sources":["../../src/core/first-contact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,6IAA6I;AAC7I,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,wOAAwO;AACxO,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"first-contact.d.ts","sourceRoot":"","sources":["../../src/core/first-contact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,6IAA6I;AAC7I,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,wOAAwO;AACxO,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AAKnD,kGAAkG;AAClG,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAEnD,0IAA0I;AAC1I,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAYhD,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAC;IACb,gGAAgG;IAChG,gBAAgB,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;IAC3D,uMAAuM;IACvM,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4HAA4H;IAC5H,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,qBAAa,YAAY;IAKX,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,WAAW,CAA6C;IAChE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;gBAEpB,IAAI,EAAE,QAAQ,CAAC,mBAAmB,CAAC;IAEhE,sWAAsW;IACtW,KAAK,IAAI,IAAI;IA8Bb,4DAA4D;IAC5D,IAAI,IAAI,IAAI;IAYZ,OAAO,CAAC,YAAY;IAmBpB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,SAAS;CAOlB;AAmBD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,QAAQ,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IAC3C,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC,CAAC,GACD,YAAY,CAkBd"}
|
|
@@ -17,7 +17,7 @@ export const FIRST_CONTACT_GROUP = "239.255.19.77";
|
|
|
17
17
|
/** The loopback interface the group is joined and sent on, and the address same-host multicast packets arrive from. */
|
|
18
18
|
const LOOPBACK_INTERFACE = "127.0.0.1";
|
|
19
19
|
/** The limited-broadcast address that carries beacons and probes to other machines on the LAN. */
|
|
20
|
-
const BROADCAST_ADDRESS = "255.255.255.255";
|
|
20
|
+
export const BROADCAST_ADDRESS = "255.255.255.255";
|
|
21
21
|
/** How often the beacon repeats once started; matching discovery-mdns.ts's own interval so the two presences behave alike on the wire. */
|
|
22
22
|
export const FIRST_CONTACT_INTERVAL_MS = 30_000;
|
|
23
23
|
export class FirstContact {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"first-contact.js","sourceRoot":"","sources":["../../src/core/first-contact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAGpC,6IAA6I;AAC7I,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAExC,wOAAwO;AACxO,MAAM,CAAC,MAAM,mBAAmB,GAAG,eAAe,CAAC;AAEnD,uHAAuH;AACvH,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAEvC,kGAAkG;AAClG,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"first-contact.js","sourceRoot":"","sources":["../../src/core/first-contact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAGpC,6IAA6I;AAC7I,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAExC,wOAAwO;AACxO,MAAM,CAAC,MAAM,mBAAmB,GAAG,eAAe,CAAC;AAEnD,uHAAuH;AACvH,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAEvC,kGAAkG;AAClG,MAAM,CAAC,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAEnD,0IAA0I;AAC1I,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAkChD,MAAM,OAAO,YAAY;IAKM;IAJrB,MAAM,CAA2B;IACjC,WAAW,CAA6C;IAC/C,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAEjD,YAA6B,IAAmC;QAAnC,SAAI,GAAJ,IAAI,CAA+B;IAAG,CAAC;IAEpE,sWAAsW;IACtW,KAAK;QACH,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YAChC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAW,EAAE,KAAuB,EAAE,EAAE;YAC5D,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,kBAAkB,EAAE,GAAG,EAAE;YACrD,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YAC1B,IAAI,CAAC;gBACH,MAAM,CAAC,aAAa,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,CAAC;gBAC9D,MAAM,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,CAAC;gBACjD,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;YACpC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,8NAA8N;gBAC9N,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CACjB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,EAAE,CAAC;YACjB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;gBAClC,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,CAAC,EAAE,yBAAyB,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,4DAA4D;IAC5D,IAAI;QACF,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YACnC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAChC,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC/B,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,GAAW,EAAE,KAAuB;QACvD,MAAM,MAAM,GAAY,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC5C,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,4MAA4M;YAC5M,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,CAAC;YACD,OAAO;QACT,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QAC/C,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;YAAE,OAAO;QAChD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC;YACzB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,IAAI,EAAE,KAAK,CAAC,OAAO;YACnB,IAAI,EAAE,MAAM,CAAC,IAAI;SAClB,CAAC,CAAC;IACL,CAAC;IAEO,UAAU;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,kBAAkB,CAAC;QAClD,IAAI,CAAC,YAAY,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;QAC7C,IAAI,CAAC,YAAY,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAEO,YAAY,CAAC,OAAe,EAAE,IAAY;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO;QACjC,MAAM,OAAO,GAAkB;YAC7B,IAAI,EAAE,oBAAoB;YAC1B,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;YACxB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ;YACxB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI;SACrB,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC1D,IAAI,GAAG,EAAE,CAAC;gBACR,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,SAAS;QACf,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC,CAAC;QAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,kBAAkB,CAAC;QAClD,KAAK,MAAM,OAAO,IAAI,CAAC,mBAAmB,EAAE,iBAAiB,CAAC,EAAE,CAAC;YAC/D,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACxC,IAAI,GAAG,EAAE,CAAC;oBACR,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAEO,SAAS,CAAC,GAAW;QAC3B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;CACF;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,oBAAoB;QAAE,OAAO,KAAK,CAAC;IAC5E,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC3E,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACvE,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACvE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,mBAAmB,CAAC;AAC/D,CAAC;AAED,+LAA+L;AAC/L,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAEpC;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAME;IAEF,MAAM,QAAQ,GAAG,IAAI,YAAY,CAAC;QAChC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,IAAI,EAAE,eAAe,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,yBAAyB,CAAC,EAAE;QACvE,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,KAAK,CAAC,MAAM,CAAC;gBACX,EAAE,EAAE,IAAI,CAAC,MAAM;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,IAAI,EAAE,IAAI,CAAC,IAAI;aAChB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,EAAE,KAAK,CAAC,OAAO;KACvB,CAAC,CAAC;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC;IACjB,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/package.json
CHANGED