tslocal 0.2.2 → 0.3.1
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/README.md +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/ts/src/types.ts +3 -3
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -354,7 +354,7 @@ const StatusSchema = zod.z.object({
|
|
|
354
354
|
HaveNodeKey: zod.z.boolean().nullish(),
|
|
355
355
|
AuthURL: zod.z.string().default(""),
|
|
356
356
|
TailscaleIPs: goSlice(zod.z.string()),
|
|
357
|
-
Self: PeerStatusSchema.
|
|
357
|
+
Self: PeerStatusSchema.prefault({}),
|
|
358
358
|
ExitNodeStatus: ExitNodeStatusSchema.nullish(),
|
|
359
359
|
Health: goSlice(zod.z.string()),
|
|
360
360
|
MagicDNSSuffix: zod.z.string().default(""),
|
|
@@ -541,8 +541,8 @@ const ServeConfigSchema = zod.z.object({
|
|
|
541
541
|
* In successful whois responses, Node and UserProfile are never nil.
|
|
542
542
|
*/
|
|
543
543
|
const WhoIsResponseSchema = zod.z.object({
|
|
544
|
-
Node: NodeSchema.
|
|
545
|
-
UserProfile: UserProfileSchema.
|
|
544
|
+
Node: NodeSchema.prefault({}),
|
|
545
|
+
UserProfile: UserProfileSchema.prefault({}),
|
|
546
546
|
CapMap: goMap(goSlice(zod.z.unknown()))
|
|
547
547
|
});
|
|
548
548
|
/** Alias for TailnetStatus for backward compatibility. */
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["execFile","http","z"],"sources":["../ts/src/json.ts","../ts/src/errors.ts","../ts/src/safesocket.ts","../ts/src/transport.ts","../ts/src/types.ts","../ts/src/client.ts"],"sourcesContent":["declare global {\n interface JSON {\n rawJSON(value: string): unknown;\n }\n}\n\n/** JSON reviver that converts large integers to BigInt to avoid precision loss. */\nexport const jsonReviver = (_key: string, value: unknown, context?: { source?: string }) => {\n if (typeof value === \"number\" && context?.source && !Number.isSafeInteger(value) && /^-?\\d+$/.test(context.source)) {\n return BigInt(context.source);\n }\n return value;\n};\n\n/** Parse a JSON string using {@link jsonReviver} for BigInt-safe integer handling. */\nexport const parseJSON = (str: string) => JSON.parse(str, jsonReviver);\n\n/** JSON replacer that serializes BigInt values as raw JSON numbers. */\nexport const jsonReplacer = (_key: string, value: unknown) =>\n typeof value === \"bigint\" ? JSON.rawJSON(value.toString()) : value;\n","/** Base error for all Tailscale Local API errors. */\nexport class TailscaleError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TailscaleError\";\n }\n}\n\n/** Raised when the server returns HTTP 403. */\nexport class AccessDeniedError extends TailscaleError {\n constructor(message: string) {\n super(`Access denied: ${message}`);\n this.name = \"AccessDeniedError\";\n }\n}\n\n/** Raised when the server returns HTTP 412. */\nexport class PreconditionsFailedError extends TailscaleError {\n constructor(message: string) {\n super(`Preconditions failed: ${message}`);\n this.name = \"PreconditionsFailedError\";\n }\n}\n\n/** Raised when a WhoIs lookup returns HTTP 404. */\nexport class PeerNotFoundError extends TailscaleError {\n constructor(message: string) {\n super(`Peer not found: ${message}`);\n this.name = \"PeerNotFoundError\";\n }\n}\n\n/** Raised when the connection to tailscaled fails. */\nexport class ConnectionError extends TailscaleError {\n constructor(message: string) {\n super(message);\n this.name = \"ConnectionError\";\n }\n}\n\n/** Raised when tailscaled is not running. */\nexport class DaemonNotRunningError extends ConnectionError {\n constructor(message: string) {\n super(message);\n this.name = \"DaemonNotRunningError\";\n }\n}\n\n/** Raised for unexpected HTTP status codes. */\nexport class HttpError extends TailscaleError {\n public readonly status: number;\n\n constructor(status: number, message: string) {\n super(`HTTP ${status}: ${message}`);\n this.name = \"HttpError\";\n this.status = status;\n }\n}\n\n/** Extract error message from a JSON body like Go's errorMessageFromBody. */\nexport function errorMessageFromBody(body: string): string | undefined {\n try {\n const data = JSON.parse(body);\n return data?.error;\n } catch {\n return undefined;\n }\n}\n","import { execFile } from \"node:child_process\";\nimport { readFile, readlink } from \"node:fs/promises\";\nimport { platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nexport const LOCAL_API_HOST = \"local-tailscaled.sock\";\nexport const CURRENT_CAP_VERSION = 131;\n\nexport interface PortAndToken {\n port: number;\n token: string;\n}\n\n/** Return the default socket path for the current platform. */\nexport function defaultSocketPath(): string {\n if (platform() === \"darwin\") {\n return \"/var/run/tailscaled.socket\";\n }\n // Linux and other Unix\n return \"/var/run/tailscale/tailscaled.sock\";\n}\n\n/** Attempt to discover macOS TCP port and token for tailscaled. */\nexport async function localTcpPortAndToken(): Promise<PortAndToken | undefined> {\n if (platform() !== \"darwin\") {\n return undefined;\n }\n\n // Try lsof method first (macOS GUI app)\n const result = await readMacosSameUserProof();\n if (result) return result;\n\n // Try filesystem method (macOS system extension)\n return readMacsysSameUserProof();\n}\n\nconst execFileP = promisify(execFile);\n\nasync function readMacosSameUserProof(): Promise<PortAndToken | undefined> {\n try {\n const uid = process.getuid?.();\n if (uid === undefined) return undefined;\n\n const { stdout: output } = await execFileP(\"lsof\", [\n \"-n\",\n \"-a\",\n `-u${uid}`,\n \"-c\",\n \"IPNExtension\",\n \"-F\",\n ]);\n return parseLsofOutput(output);\n } catch {\n return undefined;\n }\n}\n\n/** Parse lsof -F output looking for sameuserproof-PORT-TOKEN. */\nexport function parseLsofOutput(output: string): PortAndToken | undefined {\n const needle = \".tailscale.ipn.macos/sameuserproof-\";\n for (const line of output.split(\"\\n\")) {\n const idx = line.indexOf(needle);\n if (idx === -1) continue;\n const rest = line.slice(idx + needle.length);\n const dash = rest.indexOf(\"-\");\n if (dash === -1) continue;\n const portStr = rest.slice(0, dash);\n const token = rest.slice(dash + 1);\n const port = parseInt(portStr, 10);\n if (!isNaN(port)) {\n return { port, token };\n }\n }\n return undefined;\n}\n\nasync function readMacsysSameUserProof(\n sharedDir = \"/Library/Tailscale\",\n): Promise<PortAndToken | undefined> {\n try {\n const portPath = join(sharedDir, \"ipnport\");\n const portStr = await readlink(portPath, \"utf-8\");\n const port = parseInt(portStr, 10);\n if (isNaN(port)) return undefined;\n const tokenPath = join(sharedDir, `sameuserproof-${port}`);\n const tokenRaw = await readFile(tokenPath, \"utf-8\");\n const token = tokenRaw.trim();\n return { port, token };\n } catch {\n return undefined;\n }\n}\n","import * as http from \"node:http\";\nimport {\n CURRENT_CAP_VERSION,\n LOCAL_API_HOST,\n type PortAndToken,\n defaultSocketPath,\n localTcpPortAndToken,\n} from \"./safesocket.js\";\n\nexport interface TransportOptions {\n socketPath?: string;\n useSocketOnly?: boolean;\n}\n\n/**\n * Discover TCP port and token for this request.\n */\nasync function resolvePortAndToken(useSocketOnly: boolean): Promise<PortAndToken | undefined> {\n if (useSocketOnly) return undefined;\n return localTcpPortAndToken();\n}\n\n/**\n * HTTP transport that connects to tailscaled.\n * Reuses connections via Node.js http.Agent keep-alive.\n * Port and token are discovered per-request (matching Go's behavior),\n * so the client adapts to daemon restarts and late starts.\n */\nexport class Transport {\n private readonly socketPath: string;\n private readonly useSocketOnly: boolean;\n private readonly agent: http.Agent;\n\n constructor(opts: TransportOptions = {}) {\n this.socketPath = opts.socketPath ?? defaultSocketPath();\n this.useSocketOnly = opts.useSocketOnly ?? false;\n\n // Single agent with keep-alive — pools connections by host:port key\n this.agent = new http.Agent({\n keepAlive: true,\n keepAliveMsecs: 60_000,\n });\n }\n\n async request(\n method: string,\n path: string,\n body?: Buffer | string,\n extraHeaders?: Record<string, string>,\n ): Promise<{ status: number; body: Buffer; headers: http.IncomingHttpHeaders }> {\n const portAndToken = await resolvePortAndToken(this.useSocketOnly);\n\n return new Promise((resolve, reject) => {\n const headers: Record<string, string> = {\n Host: LOCAL_API_HOST,\n \"Tailscale-Cap\": String(CURRENT_CAP_VERSION),\n ...extraHeaders,\n };\n\n if (portAndToken) {\n const cred = Buffer.from(`:${portAndToken.token}`).toString(\"base64\");\n headers[\"Authorization\"] = `Basic ${cred}`;\n }\n\n const options: http.RequestOptions = {\n method,\n path,\n headers,\n agent: this.agent,\n };\n\n if (portAndToken) {\n options.host = \"127.0.0.1\";\n options.port = portAndToken.port;\n } else {\n options.socketPath = this.socketPath;\n }\n\n const req = http.request(options, (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n resolve({\n status: res.statusCode ?? 0,\n body: Buffer.concat(chunks),\n headers: res.headers,\n });\n });\n res.on(\"error\", reject);\n });\n\n req.on(\"error\", reject);\n\n if (body !== undefined) {\n req.write(body);\n }\n req.end();\n });\n }\n\n destroy(): void {\n this.agent.destroy();\n }\n}\n","// Code generated by cmd/typegen; DO NOT EDIT.\n\nimport { z } from \"zod\";\n\nconst goSlice = <T extends z.ZodTypeAny>(item: T) =>\n z.array(item).nullish().transform(v => v ?? []);\n\nconst goMap = <V extends z.ZodTypeAny>(val: V) =>\n z.record(z.string(), val).nullish().transform(v => v ?? {});\n\nconst int64 = z.union([z.number().int(), z.bigint()]).transform(v => BigInt(v));\n\n/**\n * Location represents geographical location data about a\n * Tailscale host. Location is optional and only set if\n * explicitly declared by a node.\n */\nexport const LocationSchema = z.object({\n /** User friendly country name, with proper capitalization (\"Canada\") */\n Country: z.string().nullish(),\n /** ISO 3166-1 alpha-2 in upper case (\"CA\") */\n CountryCode: z.string().nullish(),\n /** User friendly city name, with proper capitalization (\"Squamish\") */\n City: z.string().nullish(),\n /**\n * CityCode is a short code representing the city in upper case.\n * CityCode is used to disambiguate a city from another location\n * with the same city name. It uniquely identifies a particular\n * geographical location, within the tailnet.\n * IATA, ICAO or ISO 3166-2 codes are recommended (\"YSE\")\n */\n CityCode: z.string().nullish(),\n /**\n * Latitude, Longitude are optional geographical coordinates of the node, in degrees.\n * No particular accuracy level is promised; the coordinates may simply be the center of the city or country.\n */\n Latitude: z.number().nullish(),\n Longitude: z.number().nullish(),\n /**\n * Priority determines the order of use of an exit node when a\n * location based preference matches more than one exit node,\n * the node with the highest priority wins. Nodes of equal\n * probability may be selected arbitrarily.\n * \n * A value of 0 means the exit node does not have a priority\n * preference. A negative int is not allowed.\n */\n Priority: int64.nullish(),\n});\nexport type Location = z.infer<typeof LocationSchema>;\n\n/**\n * PeerStatus describes a peer node and its current state.\n * WARNING: The fields in PeerStatus are merged by the AddPeer method in the StatusBuilder.\n * When adding a new field to PeerStatus, you must update AddPeer to handle merging\n * the new field. The AddPeer function is responsible for combining multiple updates\n * to the same peer, and any new field that is not merged properly may lead to\n * inconsistencies or lost data in the peer status.\n */\nexport const PeerStatusSchema = z.object({\n ID: z.string().default(\"\"),\n PublicKey: z.string().default(\"\"),\n /** HostInfo's Hostname (not a DNS name or necessarily unique) */\n HostName: z.string().default(\"\"),\n /**\n * DNSName is the Peer's FQDN. It ends with a dot.\n * It has the form \"host.<MagicDNSSuffix>.\"\n */\n DNSName: z.string().default(\"\"),\n /** HostInfo.OS */\n OS: z.string().default(\"\"),\n UserID: int64.default(0n),\n /**\n * AltSharerUserID is the user who shared this node\n * if it's different than UserID. Otherwise it's zero.\n */\n AltSharerUserID: int64.nullish(),\n /** TailscaleIPs are the IP addresses assigned to the node. */\n TailscaleIPs: goSlice(z.string()),\n /** AllowedIPs are IP addresses allowed to route to this node. */\n AllowedIPs: goSlice(z.string()),\n /**\n * Tags are the list of ACL tags applied to this node.\n * See tailscale.com/tailcfg#Node.Tags for more information.\n */\n Tags: goSlice(z.string()),\n /**\n * PrimaryRoutes are the routes this node is currently the primary\n * subnet router for, as determined by the control plane. It does\n * not include the IPs in TailscaleIPs.\n */\n PrimaryRoutes: goSlice(z.string()),\n /** Endpoints: */\n Addrs: goSlice(z.string()),\n /** one of Addrs, or unique if roaming */\n CurAddr: z.string().default(\"\"),\n /** DERP region */\n Relay: z.string().default(\"\"),\n /** peer relay address (ip:port:vni) */\n PeerRelay: z.string().default(\"\"),\n RxBytes: int64.default(0n),\n TxBytes: int64.default(0n),\n /** time registered with tailcontrol */\n Created: z.string().default(\"\"),\n /** time last packet sent */\n LastWrite: z.string().default(\"\"),\n /** last seen to tailcontrol; only present if offline */\n LastSeen: z.string().default(\"\"),\n /** with local wireguard */\n LastHandshake: z.string().default(\"\"),\n /** whether node is connected to the control plane */\n Online: z.boolean().default(false),\n /** true if this is the currently selected exit node. */\n ExitNode: z.boolean().default(false),\n /** true if this node can be an exit node (offered && approved) */\n ExitNodeOption: z.boolean().default(false),\n /**\n * Active is whether the node was recently active. The\n * definition is somewhat undefined but has historically and\n * currently means that there was some packet sent to this\n * peer in the past two minutes. That definition is subject to\n * change.\n */\n Active: z.boolean().default(false),\n /** PeerAPIURL are the URLs of the node's PeerAPI servers. */\n PeerAPIURL: goSlice(z.string()),\n /** TaildropTargetStatus represents the node's eligibility to have files shared to it. */\n TaildropTarget: int64.default(0n),\n /** Reason why this peer cannot receive files. Empty if CanReceiveFiles=true */\n NoFileSharingReason: z.string().default(\"\"),\n /**\n * Capabilities are capabilities that the node has.\n * They're free-form strings, but should be in the form of URLs/URIs\n * such as:\n * \"https://tailscale.com/cap/is-admin\"\n * \"https://tailscale.com/cap/file-sharing\"\n * \"funnel\"\n * \n * Deprecated: use CapMap instead. See https://github.com/tailscale/tailscale/issues/11508\n * Every value is Capabilities is also a key in CapMap, even if it\n * has no values in that map.\n */\n Capabilities: goSlice(z.string()),\n /** CapMap is a map of capabilities to their values. */\n CapMap: goMap(goSlice(z.unknown())),\n /** SSH_HostKeys are the node's SSH host keys, if known. */\n sshHostKeys: goSlice(z.string()),\n /**\n * ShareeNode indicates this node exists in the netmap because\n * it's owned by a shared-to user and that node might connect\n * to us. These nodes should be hidden by \"tailscale status\"\n * etc by default.\n */\n ShareeNode: z.boolean().nullish(),\n /**\n * InNetworkMap means that this peer was seen in our latest network map.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InNetworkMap: z.boolean().default(false),\n /**\n * InMagicSock means that this peer is being tracked by magicsock.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InMagicSock: z.boolean().default(false),\n /**\n * InEngine means that this peer is tracked by the wireguard engine.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InEngine: z.boolean().default(false),\n /**\n * Expired means that this peer's node key has expired, based on either\n * information from control or optimisically set on the client if the\n * expiration time has passed.\n */\n Expired: z.boolean().nullish(),\n /**\n * KeyExpiry, if present, is the time at which the node key expired or\n * will expire.\n */\n KeyExpiry: z.string().nullish(),\n Location: LocationSchema.nullish(),\n});\nexport type PeerStatus = z.infer<typeof PeerStatusSchema>;\n\n/** ExitNodeStatus describes the current exit node. */\nexport const ExitNodeStatusSchema = z.object({\n /** ID is the exit node's ID. */\n ID: z.string().default(\"\"),\n /** Online is whether the exit node is alive. */\n Online: z.boolean().default(false),\n /** TailscaleIPs are the exit node's IP addresses assigned to the node. */\n TailscaleIPs: goSlice(z.string()),\n});\nexport type ExitNodeStatus = z.infer<typeof ExitNodeStatusSchema>;\n\n/** TailnetStatus is information about a Tailscale network (\"tailnet\"). */\nexport const TailnetStatusSchema = z.object({\n /** Name is the name of the network that's currently in use. */\n Name: z.string().default(\"\"),\n /**\n * MagicDNSSuffix is the network's MagicDNS suffix for nodes\n * in the network such as \"userfoo.tailscale.net\".\n * There are no surrounding dots.\n * MagicDNSSuffix should be populated regardless of whether a domain\n * has MagicDNS enabled.\n */\n MagicDNSSuffix: z.string().default(\"\"),\n /**\n * MagicDNSEnabled is whether or not the network has MagicDNS enabled.\n * Note that the current device may still not support MagicDNS if\n * `--accept-dns=false` was used.\n */\n MagicDNSEnabled: z.boolean().default(false),\n});\nexport type TailnetStatus = z.infer<typeof TailnetStatusSchema>;\n\n/**\n * A UserProfile is display-friendly data for a [User].\n * It includes the LoginName for display purposes but *not* the Provider.\n * It also includes derived data from one of the user's logins.\n */\nexport const UserProfileSchema = z.object({\n ID: int64.default(0n),\n /** \"alice@smith.com\"; for display purposes only (provider is not listed) */\n LoginName: z.string().default(\"\"),\n /** \"Alice Smith\" */\n DisplayName: z.string().default(\"\"),\n ProfilePicURL: z.string().nullish(),\n});\nexport type UserProfile = z.infer<typeof UserProfileSchema>;\n\n/**\n * ClientVersion is information about the latest client version that's available\n * for the client (and whether they're already running it).\n * \n * It does not include a URL to download the client, as that varies by platform.\n */\nexport const ClientVersionSchema = z.object({\n /** RunningLatest is true if the client is running the latest build. */\n RunningLatest: z.boolean().nullish(),\n /**\n * LatestVersion is the latest version.Short (\"1.34.2\") version available\n * for download for the client's platform and packaging type.\n * It won't be populated if RunningLatest is true.\n */\n LatestVersion: z.string().nullish(),\n /**\n * UrgentSecurityUpdate is set when the client is missing an important\n * security update. That update may be in LatestVersion or earlier.\n * UrgentSecurityUpdate should not be set if RunningLatest is false.\n */\n UrgentSecurityUpdate: z.boolean().nullish(),\n /**\n * Notify is whether the client should do an OS-specific notification about\n * a new version being available. This should not be populated if\n * RunningLatest is true. The client should not notify multiple times for\n * the same LatestVersion value.\n */\n Notify: z.boolean().nullish(),\n /**\n * NotifyURL is a URL to open in the browser when the user clicks on the\n * notification, when Notify is true.\n */\n NotifyURL: z.string().nullish(),\n /** NotifyText is the text to show in the notification, when Notify is true. */\n NotifyText: z.string().nullish(),\n});\nexport type ClientVersion = z.infer<typeof ClientVersionSchema>;\n\n/** Status represents the entire state of the IPN network. */\nexport const StatusSchema = z.object({\n /** Version is the daemon's long version (see version.Long). */\n Version: z.string().default(\"\"),\n /**\n * TUN is whether /dev/net/tun (or equivalent kernel interface) is being\n * used. If false, it's running in userspace mode.\n */\n TUN: z.boolean().default(false),\n /**\n * BackendState is an ipn.State string value:\n * \"NoState\", \"NeedsLogin\", \"NeedsMachineAuth\", \"Stopped\",\n * \"Starting\", \"Running\".\n */\n BackendState: z.string().default(\"\"),\n /** HaveNodeKey is whether the current profile has a node key configured. */\n HaveNodeKey: z.boolean().nullish(),\n /** current URL provided by control to authorize client */\n AuthURL: z.string().default(\"\"),\n /** Tailscale IP(s) assigned to this node */\n TailscaleIPs: goSlice(z.string()),\n Self: PeerStatusSchema.nullish(),\n /**\n * ExitNodeStatus describes the current exit node.\n * If nil, an exit node is not in use.\n */\n ExitNodeStatus: ExitNodeStatusSchema.nullish(),\n /**\n * Health contains health check problems.\n * Empty means everything is good. (or at least that no known\n * problems are detected)\n */\n Health: goSlice(z.string()),\n /**\n * This field is the legacy name of CurrentTailnet.MagicDNSSuffix.\n * \n * Deprecated: use CurrentTailnet.MagicDNSSuffix instead.\n */\n MagicDNSSuffix: z.string().default(\"\"),\n /**\n * CurrentTailnet is information about the tailnet that the node\n * is currently connected to. When not connected, this field is nil.\n */\n CurrentTailnet: TailnetStatusSchema.nullish(),\n /**\n * CertDomains are the set of DNS names for which the control\n * plane server will assist with provisioning TLS\n * certificates. See SetDNSRequest for dns-01 ACME challenges\n * for e.g. LetsEncrypt. These names are FQDNs without\n * trailing periods, and without any \"_acme-challenge.\" prefix.\n */\n CertDomains: goSlice(z.string()),\n /** Peer is the state of each peer, keyed by each peer's current public key. */\n Peer: goMap(PeerStatusSchema),\n /**\n * User contains profile information about UserIDs referenced by\n * PeerStatus.UserID, PeerStatus.AltSharerUserID, etc.\n */\n User: goMap(UserProfileSchema),\n /**\n * ClientVersion, when non-nil, contains information about the latest\n * version of the Tailscale client that's available. Depending on\n * the platform and client settings, it may not be available.\n */\n ClientVersion: ClientVersionSchema.nullish(),\n});\nexport type Status = z.infer<typeof StatusSchema>;\n\n/** Service represents a service running on a node. */\nexport const ServiceSchema = z.object({\n /**\n * Proto is the type of service. It's usually the constant TCP\n * or UDP (\"tcp\" or \"udp\"), but it can also be one of the\n * following meta service values:\n * \n * * \"peerapi4\": peerapi is available on IPv4; Port is the\n * port number that the peerapi is running on the\n * node's Tailscale IPv4 address.\n * * \"peerapi6\": peerapi is available on IPv6; Port is the\n * port number that the peerapi is running on the\n * node's Tailscale IPv6 address.\n * * \"peerapi-dns-proxy\": the local peerapi service supports\n * being a DNS proxy (when the node is an exit\n * node). For this service, the Port number must only be 1.\n */\n Proto: z.string().default(\"\"),\n /**\n * Port is the port number.\n * \n * For Proto \"peerapi-dns\", it must be 1.\n */\n Port: z.number().default(0),\n /**\n * Description is the textual description of the service,\n * usually the process name that's running.\n */\n Description: z.string().nullish(),\n});\nexport type Service = z.infer<typeof ServiceSchema>;\n\n/** NetInfo contains information about the host's network state. */\nexport const NetInfoSchema = z.object({\n /**\n * MappingVariesByDestIP says whether the host's NAT mappings\n * vary based on the destination IP.\n */\n MappingVariesByDestIP: z.boolean().nullish(),\n /** WorkingIPv6 is whether the host has IPv6 internet connectivity. */\n WorkingIPv6: z.boolean().nullish(),\n /**\n * OSHasIPv6 is whether the OS supports IPv6 at all, regardless of\n * whether IPv6 internet connectivity is available.\n */\n OSHasIPv6: z.boolean().nullish(),\n /** WorkingUDP is whether the host has UDP internet connectivity. */\n WorkingUDP: z.boolean().nullish(),\n /**\n * WorkingICMPv4 is whether ICMPv4 works.\n * Empty means not checked.\n */\n WorkingICMPv4: z.boolean().nullish(),\n /**\n * HavePortMap is whether we have an existing portmap open\n * (UPnP, PMP, or PCP).\n */\n HavePortMap: z.boolean().nullish(),\n /**\n * UPnP is whether UPnP appears present on the LAN.\n * Empty means not checked.\n */\n UPnP: z.boolean().nullish(),\n /**\n * PMP is whether NAT-PMP appears present on the LAN.\n * Empty means not checked.\n */\n PMP: z.boolean().nullish(),\n /**\n * PCP is whether PCP appears present on the LAN.\n * Empty means not checked.\n */\n PCP: z.boolean().nullish(),\n /**\n * PreferredDERP is this node's preferred (home) DERP region ID.\n * This is where the node expects to be contacted to begin a\n * peer-to-peer connection. The node might be be temporarily\n * connected to multiple DERP servers (to speak to other nodes\n * that are located elsewhere) but PreferredDERP is the region ID\n * that the node subscribes to traffic at.\n * Zero means disconnected or unknown.\n */\n PreferredDERP: int64.nullish(),\n /** LinkType is the current link type, if known. */\n LinkType: z.string().nullish(),\n /**\n * DERPLatency is the fastest recent time to reach various\n * DERP STUN servers, in seconds. The map key is the\n * \"regionID-v4\" or \"-v6\"; it was previously the DERP server's\n * STUN host:port.\n * \n * This should only be updated rarely, or when there's a\n * material change, as any change here also gets uploaded to\n * the control plane.\n */\n DERPLatency: goMap(z.number()),\n /**\n * FirewallMode encodes both which firewall mode was selected and why.\n * It is Linux-specific (at least as of 2023-08-19) and is meant to help\n * debug iptables-vs-nftables issues. The string is of the form\n * \"{nft,ift}-REASON\", like \"nft-forced\" or \"ipt-default\". Empty means\n * either not Linux or a configuration in which the host firewall rules\n * are not managed by tailscaled.\n */\n FirewallMode: z.string().nullish(),\n});\nexport type NetInfo = z.infer<typeof NetInfoSchema>;\n\n/**\n * TPMInfo contains information about a TPM 2.0 device present on a node.\n * All fields are read from TPM_CAP_TPM_PROPERTIES, see Part 2, section 6.13 of\n * https://trustedcomputinggroup.org/resource/tpm-library-specification/.\n */\nexport const TPMInfoSchema = z.object({\n /**\n * Manufacturer is a 4-letter code from section 4.1 of\n * https://trustedcomputinggroup.org/resource/vendor-id-registry/,\n * for example \"MSFT\" for Microsoft.\n * Read from TPM_PT_MANUFACTURER.\n */\n Manufacturer: z.string().nullish(),\n /**\n * Vendor is a vendor ID string, up to 16 characters.\n * Read from TPM_PT_VENDOR_STRING_*.\n */\n Vendor: z.string().nullish(),\n /**\n * Model is a vendor-defined TPM model.\n * Read from TPM_PT_VENDOR_TPM_TYPE.\n */\n Model: int64.nullish(),\n /**\n * FirmwareVersion is the version number of the firmware.\n * Read from TPM_PT_FIRMWARE_VERSION_*.\n */\n FirmwareVersion: int64.nullish(),\n /**\n * SpecRevision is the TPM 2.0 spec revision encoded as a single number. All\n * revisions can be found at\n * https://trustedcomputinggroup.org/resource/tpm-library-specification/.\n * Before revision 184, TCG used the \"01.83\" format for revision 183.\n */\n SpecRevision: int64.nullish(),\n /**\n * FamilyIndicator is the TPM spec family, like \"2.0\".\n * Read from TPM_PT_FAMILY_INDICATOR.\n */\n FamilyIndicator: z.string().nullish(),\n});\nexport type TPMInfo = z.infer<typeof TPMInfoSchema>;\n\n/**\n * Hostinfo contains a summary of a Tailscale host.\n * \n * Because it contains pointers (slices), this type should not be used\n * as a value type.\n */\nexport const HostinfoSchema = z.object({\n /** version of this code (in version.Long format) */\n IPNVersion: z.string().nullish(),\n /** logtail ID of frontend instance */\n FrontendLogID: z.string().nullish(),\n /** logtail ID of backend instance */\n BackendLogID: z.string().nullish(),\n /** operating system the client runs on (a version.OS value) */\n OS: z.string().nullish(),\n /**\n * OSVersion is the version of the OS, if available.\n * \n * For Android, it's like \"10\", \"11\", \"12\", etc. For iOS and macOS it's like\n * \"15.6.1\" or \"12.4.0\". For Windows it's like \"10.0.19044.1889\". For\n * FreeBSD it's like \"12.3-STABLE\".\n * \n * For Linux, prior to Tailscale 1.32, we jammed a bunch of fields into this\n * string on Linux, like \"Debian 10.4; kernel=xxx; container; env=kn\" and so\n * on. As of Tailscale 1.32, this is simply the kernel version on Linux, like\n * \"5.10.0-17-amd64\".\n */\n OSVersion: z.string().nullish(),\n /** best-effort whether the client is running in a container */\n Container: z.boolean().nullish(),\n /** a hostinfo.EnvType in string form */\n Env: z.string().nullish(),\n /** \"debian\", \"ubuntu\", \"nixos\", ... */\n Distro: z.string().nullish(),\n /** \"20.04\", ... */\n DistroVersion: z.string().nullish(),\n /** \"jammy\", \"bullseye\", ... */\n DistroCodeName: z.string().nullish(),\n /** App is used to disambiguate Tailscale clients that run using tsnet. */\n App: z.string().nullish(),\n /** if a desktop was detected on Linux */\n Desktop: z.boolean().nullish(),\n /** Tailscale package to disambiguate (\"choco\", \"appstore\", etc; \"\" for unknown) */\n Package: z.string().nullish(),\n /** mobile phone model (\"Pixel 3a\", \"iPhone12,3\") */\n DeviceModel: z.string().nullish(),\n /** macOS/iOS APNs device token for notifications (and Android in the future) */\n PushDeviceToken: z.string().nullish(),\n /** name of the host the client runs on */\n Hostname: z.string().nullish(),\n /** indicates whether the host is blocking incoming connections */\n ShieldsUp: z.boolean().nullish(),\n /** indicates this node exists in netmap because it's owned by a shared-to user */\n ShareeNode: z.boolean().nullish(),\n /** indicates that the user has opted out of sending logs and support */\n NoLogsNoSupport: z.boolean().nullish(),\n /**\n * WireIngress indicates that the node would like to be wired up server-side\n * (DNS, etc) to be able to use Tailscale Funnel, even if it's not currently\n * enabled. For example, the user might only use it for intermittent\n * foreground CLI serve sessions, for which they'd like it to work right\n * away, even if it's disabled most of the time. As an optimization, this is\n * only sent if IngressEnabled is false, as IngressEnabled implies that this\n * option is true.\n */\n WireIngress: z.boolean().nullish(),\n /** if the node has any funnel endpoint enabled */\n IngressEnabled: z.boolean().nullish(),\n /** indicates that the node has opted-in to admin-console-drive remote updates */\n AllowsUpdate: z.boolean().nullish(),\n /** the current host's machine type (uname -m) */\n Machine: z.string().nullish(),\n /** GOARCH value (of the built binary) */\n GoArch: z.string().nullish(),\n /** GOARM, GOAMD64, etc (of the built binary) */\n GoArchVar: z.string().nullish(),\n /** Go version binary was built with */\n GoVersion: z.string().nullish(),\n /** set of IP ranges this client can route */\n RoutableIPs: goSlice(z.string()),\n /** set of ACL tags this node wants to claim */\n RequestTags: goSlice(z.string()),\n /** MAC address(es) to send Wake-on-LAN packets to wake this node (lowercase hex w/ colons) */\n WoLMACs: goSlice(z.string()),\n /** services advertised by this machine */\n Services: goSlice(ServiceSchema),\n NetInfo: NetInfoSchema.nullish(),\n /** if advertised */\n sshHostKeys: goSlice(z.string()),\n Cloud: z.string().nullish(),\n /** if the client is running in userspace (netstack) mode */\n Userspace: z.boolean().nullish(),\n /** if the client's subnet router is running in userspace (netstack) mode */\n UserspaceRouter: z.boolean().nullish(),\n /** if the client is running the app-connector service */\n AppConnector: z.boolean().nullish(),\n /** opaque hash of the most recent list of tailnet services, change in hash indicates config should be fetched via c2n */\n ServicesHash: z.string().nullish(),\n /** the client’s selected exit node, empty when unselected. */\n ExitNodeID: z.string().nullish(),\n /**\n * Location represents geographical location data about a\n * Tailscale host. Location is optional and only set if\n * explicitly declared by a node.\n */\n Location: LocationSchema.nullish(),\n /** TPM device metadata, if available */\n TPM: TPMInfoSchema.nullish(),\n /**\n * StateEncrypted reports whether the node state is stored encrypted on\n * disk. The actual mechanism is platform-specific:\n * * Apple nodes use the Keychain\n * * Linux and Windows nodes use the TPM\n * * Android apps use EncryptedSharedPreferences\n */\n StateEncrypted: z.boolean().nullish(),\n});\nexport type Hostinfo = z.infer<typeof HostinfoSchema>;\n\n/** Resolver is the configuration for one DNS resolver. */\nexport const ResolverSchema = z.object({\n /**\n * Addr is the address of the DNS resolver, one of:\n * - A plain IP address for a \"classic\" UDP+TCP DNS resolver.\n * This is the common format as sent by the control plane.\n * - An IP:port, for tests.\n * - \"https://resolver.com/path\" for DNS over HTTPS; currently\n * as of 2022-09-08 only used for certain well-known resolvers\n * (see the publicdns package) for which the IP addresses to dial DoH are\n * known ahead of time, so bootstrap DNS resolution is not required.\n * - \"http://node-address:port/path\" for DNS over HTTP over WireGuard. This\n * is implemented in the PeerAPI for exit nodes and app connectors.\n * - [TODO] \"tls://resolver.com\" for DNS over TCP+TLS\n */\n Addr: z.string().nullish(),\n /**\n * BootstrapResolution is an optional suggested resolution for the\n * DoT/DoH resolver, if the resolver URL does not reference an IP\n * address directly.\n * BootstrapResolution may be empty, in which case clients should\n * look up the DoT/DoH server using their local \"classic\" DNS\n * resolver.\n * \n * As of 2022-09-08, BootstrapResolution is not yet used.\n */\n BootstrapResolution: goSlice(z.string()),\n /**\n * UseWithExitNode designates that this resolver should continue to be used when an\n * exit node is in use. Normally, DNS resolution is delegated to the exit node but\n * there are situations where it is preferable to still use a Split DNS server and/or\n * global DNS server instead of the exit node.\n */\n UseWithExitNode: z.boolean().nullish(),\n});\nexport type Resolver = z.infer<typeof ResolverSchema>;\n\n/** Node is a Tailscale device in a tailnet. */\nexport const NodeSchema = z.object({\n ID: int64.default(0n),\n StableID: z.string().default(\"\"),\n /**\n * Name is the FQDN of the node.\n * It is also the MagicDNS name for the node.\n * It has a trailing dot.\n * e.g. \"host.tail-scale.ts.net.\"\n */\n Name: z.string().default(\"\"),\n /**\n * User is the user who created the node. If ACL tags are in use for the\n * node then it doesn't reflect the ACL identity that the node is running\n * as.\n */\n User: int64.default(0n),\n /** Sharer, if non-zero, is the user who shared this node, if different than User. */\n Sharer: int64.nullish(),\n Key: z.string().default(\"\"),\n /** the zero value if this node does not expire */\n KeyExpiry: z.string().nullish(),\n KeySignature: z.string().nullish(),\n Machine: z.string().nullish(),\n DiscoKey: z.string().nullish(),\n /** Addresses are the IP addresses of this Node directly. */\n Addresses: goSlice(z.string()),\n /**\n * AllowedIPs are the IP ranges to route to this node.\n * \n * As of CapabilityVersion 112, this may be nil (null or undefined) on the wire\n * to mean the same as Addresses. Internally, it is always filled in with\n * its possibly-implicit value.\n */\n AllowedIPs: goSlice(z.string()),\n /** IP+port (public via STUN, and local LANs) */\n Endpoints: goSlice(z.string()),\n /**\n * LegacyDERPString is this node's home LegacyDERPString region ID integer, but shoved into an\n * IP:port string for legacy reasons. The IP address is always \"127.3.3.40\"\n * (a loopback address (127) followed by the digits over the letters DERP on\n * a QWERTY keyboard (3.3.40)). The \"port number\" is the home LegacyDERPString region ID\n * integer.\n * \n * Deprecated: HomeDERP has replaced this, but old servers might still send\n * this field. See tailscale/tailscale#14636. Do not use this field in code\n * other than in the upgradeNode func, which canonicalizes it to HomeDERP\n * if it arrives as a LegacyDERPString string on the wire.\n */\n DERP: z.string().nullish(),\n /**\n * HomeDERP is the modern version of the DERP string field, with just an\n * integer. The client advertises support for this as of capver 111.\n * \n * HomeDERP may be zero if not (yet) known, but ideally always be non-zero\n * for magicsock connectivity to function normally.\n */\n HomeDERP: int64.nullish(),\n Hostinfo: HostinfoSchema.nullish(),\n Created: z.string().nullish(),\n /** if non-zero, the node's capability version; old servers might not send */\n Cap: int64.nullish(),\n /**\n * Tags are the list of ACL tags applied to this node.\n * Tags take the form of `tag:<value>` where value starts\n * with a letter and only contains alphanumerics and dashes `-`.\n * Some valid tag examples:\n * `tag:prod`\n * `tag:database`\n * `tag:lab-1`\n */\n Tags: goSlice(z.string()),\n /**\n * PrimaryRoutes are the routes from AllowedIPs that this node\n * is currently the primary subnet router for, as determined\n * by the control plane. It does not include the self address\n * values from Addresses that are in AllowedIPs.\n */\n PrimaryRoutes: goSlice(z.string()),\n /**\n * LastSeen is when the node was last online. It is not\n * updated when Online is true. It is nil if the current\n * node doesn't have permission to know, or the node\n * has never been online.\n */\n LastSeen: z.string().nullish(),\n /**\n * Online is whether the node is currently connected to the\n * coordination server. A value of nil means unknown, or the\n * current node doesn't have permission to know.\n */\n Online: z.boolean().nullish(),\n /** TODO(crawshaw): replace with MachineStatus */\n MachineAuthorized: z.boolean().nullish(),\n /**\n * Capabilities are capabilities that the node has.\n * They're free-form strings, but should be in the form of URLs/URIs\n * such as:\n * \"https://tailscale.com/cap/is-admin\"\n * \"https://tailscale.com/cap/file-sharing\"\n * \n * Deprecated: use CapMap instead. See https://github.com/tailscale/tailscale/issues/11508\n */\n Capabilities: goSlice(z.string()),\n /**\n * CapMap is a map of capabilities to their optional argument/data values.\n * \n * It is valid for a capability to not have any argument/data values; such\n * capabilities can be tested for using the HasCap method. These type of\n * capabilities are used to indicate that a node has a capability, but there\n * is no additional data associated with it. These were previously\n * represented by the Capabilities field, but can now be represented by\n * CapMap with an empty value.\n * \n * See NodeCapability for more information on keys.\n * \n * Metadata about nodes can be transmitted in 3 ways:\n * 1. MapResponse.Node.CapMap describes attributes that affect behavior for\n * this node, such as which features have been enabled through the admin\n * panel and any associated configuration details.\n * 2. MapResponse.PacketFilter(s) describes access (both IP and application\n * based) that should be granted to peers.\n * 3. MapResponse.Peers[].CapMap describes attributes regarding a peer node,\n * such as which features the peer supports or if that peer is preferred\n * for a particular task vs other peers that could also be chosen.\n */\n CapMap: goMap(goSlice(z.unknown())),\n /**\n * UnsignedPeerAPIOnly means that this node is not signed nor subject to TKA\n * restrictions. However, in exchange for that privilege, it does not get\n * network access. It can only access this node's peerapi, which may not let\n * it do anything. It is the tailscaled client's job to double-check the\n * MapResponse's PacketFilter to verify that its AllowedIPs will not be\n * accepted by the packet filter.\n */\n UnsignedPeerAPIOnly: z.boolean().nullish(),\n /** MagicDNS base name (for normal non-shared-in nodes), FQDN (without trailing dot, for shared-in nodes), or Hostname (if no MagicDNS) */\n ComputedName: z.string().nullish(),\n /** either \"ComputedName\" or \"ComputedName (computedHostIfDifferent)\", if computedHostIfDifferent is set */\n ComputedNameWithHost: z.string().nullish(),\n /** DataPlaneAuditLogID is the per-node logtail ID used for data plane audit logging. */\n DataPlaneAuditLogID: z.string().nullish(),\n /**\n * Expired is whether this node's key has expired. Control may send\n * this; clients are only allowed to set this from false to true. On\n * the client, this is calculated client-side based on a timestamp sent\n * from control, to avoid clock skew issues.\n */\n Expired: z.boolean().nullish(),\n /**\n * SelfNodeV4MasqAddrForThisPeer is the IPv4 that this peer knows the current node as.\n * It may be empty if the peer knows the current node by its native\n * IPv4 address.\n * This field is only populated in a MapResponse for peers and not\n * for the current node.\n * \n * If set, it should be used to masquerade traffic originating from the\n * current node to this peer. The masquerade address is only relevant\n * for this peer and not for other peers.\n * \n * This only applies to traffic originating from the current node to the\n * peer or any of its subnets. Traffic originating from subnet routes will\n * not be masqueraded (e.g. in case of --snat-subnet-routes).\n */\n SelfNodeV4MasqAddrForThisPeer: z.string().nullish(),\n /**\n * SelfNodeV6MasqAddrForThisPeer is the IPv6 that this peer knows the current node as.\n * It may be empty if the peer knows the current node by its native\n * IPv6 address.\n * This field is only populated in a MapResponse for peers and not\n * for the current node.\n * \n * If set, it should be used to masquerade traffic originating from the\n * current node to this peer. The masquerade address is only relevant\n * for this peer and not for other peers.\n * \n * This only applies to traffic originating from the current node to the\n * peer or any of its subnets. Traffic originating from subnet routes will\n * not be masqueraded (e.g. in case of --snat-subnet-routes).\n */\n SelfNodeV6MasqAddrForThisPeer: z.string().nullish(),\n /**\n * IsWireGuardOnly indicates that this is a non-Tailscale WireGuard peer, it\n * is not expected to speak Disco or DERP, and it must have Endpoints in\n * order to be reachable.\n */\n IsWireGuardOnly: z.boolean().nullish(),\n /**\n * IsJailed indicates that this node is jailed and should not be allowed\n * initiate connections, however outbound connections to it should still be\n * allowed.\n */\n IsJailed: z.boolean().nullish(),\n /**\n * ExitNodeDNSResolvers is the list of DNS servers that should be used when this\n * node is marked IsWireGuardOnly and being used as an exit node.\n */\n ExitNodeDNSResolvers: goSlice(ResolverSchema),\n});\nexport type Node = z.infer<typeof NodeSchema>;\n\n/**\n * TCPPortHandler describes what to do when handling a TCP\n * connection.\n */\nexport const TCPPortHandlerSchema = z.object({\n /**\n * HTTPS, if true, means that tailscaled should handle this connection as an\n * HTTPS request as configured by ServeConfig.Web.\n * \n * It is mutually exclusive with TCPForward.\n */\n HTTPS: z.boolean().nullish(),\n /**\n * HTTP, if true, means that tailscaled should handle this connection as an\n * HTTP request as configured by ServeConfig.Web.\n * \n * It is mutually exclusive with TCPForward.\n */\n HTTP: z.boolean().nullish(),\n /**\n * TCPForward is the IP:port to forward TCP connections to.\n * Whether or not TLS is terminated by tailscaled depends on\n * TerminateTLS.\n * \n * It is mutually exclusive with HTTPS.\n */\n TCPForward: z.string().nullish(),\n /**\n * TerminateTLS, if non-empty, means that tailscaled should terminate the\n * TLS connections before forwarding them to TCPForward, permitting only the\n * SNI name with this value. It is only used if TCPForward is non-empty.\n * (the HTTPS mode uses ServeConfig.Web)\n */\n TerminateTLS: z.string().nullish(),\n /**\n * ProxyProtocol indicates whether to send a PROXY protocol header\n * before forwarding the connection to TCPForward.\n * \n * This is only valid if TCPForward is non-empty.\n */\n ProxyProtocol: int64.nullish(),\n});\nexport type TCPPortHandler = z.infer<typeof TCPPortHandlerSchema>;\n\n/** HTTPHandler is either a path or a proxy to serve. */\nexport const HTTPHandlerSchema = z.object({\n /** absolute path to directory or file to serve */\n Path: z.string().nullish(),\n /** http://localhost:3000/, localhost:3030, 3030 */\n Proxy: z.string().nullish(),\n /** plaintext to serve (primarily for testing) */\n Text: z.string().nullish(),\n /** peer capabilities to forward in grant header, e.g. example.com/cap/mon */\n AcceptAppCaps: goSlice(z.string()),\n /**\n * Redirect, if not empty, is the target URL to redirect requests to.\n * By default, we redirect with HTTP 302 (Found) status.\n * If Redirect starts with '<httpcode>:', then we use that status instead.\n * \n * The target URL supports the following expansion variables:\n * - ${HOST}: replaced with the request's Host header value\n * - ${REQUEST_URI}: replaced with the request's full URI (path and query string)\n */\n Redirect: z.string().nullish(),\n});\nexport type HTTPHandler = z.infer<typeof HTTPHandlerSchema>;\n\n/** WebServerConfig describes a web server's configuration. */\nexport const WebServerConfigSchema = z.object({\n /** mountPoint => handler */\n Handlers: goMap(HTTPHandlerSchema),\n});\nexport type WebServerConfig = z.infer<typeof WebServerConfigSchema>;\n\n/**\n * ServiceConfig contains the config information for a single service.\n * it contains a bool to indicate if the service is in Tun mode (L3 forwarding).\n * If the service is not in Tun mode, the service is configured by the L4 forwarding\n * (TCP ports) and/or the L7 forwarding (http handlers) information.\n */\nexport const ServiceConfigSchema = z.object({\n /**\n * TCP are the list of TCP port numbers that tailscaled should handle for\n * the Tailscale IP addresses. (not subnet routers, etc)\n */\n TCP: goMap(TCPPortHandlerSchema),\n /**\n * Web maps from \"$SNI_NAME:$PORT\" to a set of HTTP handlers\n * keyed by mount point (\"/\", \"/foo\", etc)\n */\n Web: goMap(WebServerConfigSchema),\n /** Tun determines if the service should be using L3 forwarding (Tun mode). */\n Tun: z.boolean().nullish(),\n});\nexport type ServiceConfig = z.infer<typeof ServiceConfigSchema>;\n\nconst _ServeConfigRef: z.ZodTypeAny = z.lazy(() => ServeConfigSchema);\n/**\n * ServeConfig is the JSON type stored in the StateStore for\n * StateKey \"_serve/$PROFILE_ID\" as returned by ServeConfigKey.\n */\nexport const ServeConfigSchema = z.object({\n /**\n * TCP are the list of TCP port numbers that tailscaled should handle for\n * the Tailscale IP addresses. (not subnet routers, etc)\n */\n TCP: goMap(TCPPortHandlerSchema),\n /**\n * Web maps from \"$SNI_NAME:$PORT\" to a set of HTTP handlers\n * keyed by mount point (\"/\", \"/foo\", etc)\n */\n Web: goMap(WebServerConfigSchema),\n /**\n * Services maps from service name (in the form \"svc:dns-label\") to a ServiceConfig.\n * Which describes the L3, L4, and L7 forwarding information for the service.\n */\n Services: goMap(ServiceConfigSchema),\n /**\n * AllowFunnel is the set of SNI:port values for which funnel\n * traffic is allowed, from trusted ingress peers.\n */\n AllowFunnel: goMap(z.boolean()),\n /**\n * Foreground is a map of an IPN Bus session ID to an alternate foreground serve config that's valid for the\n * life of that WatchIPNBus session ID. This allows the config to specify ephemeral configs that are used\n * in the CLI's foreground mode to ensure ungraceful shutdowns of either the client or the LocalBackend does not\n * expose ports that users are not aware of. In practice this contains any serve config set via 'tailscale\n * serve' command run without the '--bg' flag. ServeConfig contained by Foreground is not expected itself to contain\n * another Foreground block.\n */\n Foreground: goMap(_ServeConfigRef),\n});\nexport type ServeConfig = z.infer<typeof ServeConfigSchema> & {\n /**\n * ETag is the checksum of the serve config that's populated\n * by the LocalClient through the HTTP ETag header during a\n * GetServeConfig request and is translated to an If-Match header\n * during a SetServeConfig request.\n */\n ETag: string;\n};\n\n/**\n * WhoIsResponse is the JSON type returned by tailscaled debug server's /whois?ip=$IP handler.\n * In successful whois responses, Node and UserProfile are never nil.\n */\nexport const WhoIsResponseSchema = z.object({\n Node: NodeSchema.nullish(),\n UserProfile: UserProfileSchema.nullish(),\n /**\n * CapMap is a map of capabilities to their values.\n * See tailcfg.PeerCapMap and tailcfg.PeerCapability for details.\n */\n CapMap: goMap(goSlice(z.unknown())),\n});\nexport type WhoIsResponse = z.infer<typeof WhoIsResponseSchema>;\n\n/** Alias for TailnetStatus for backward compatibility. */\nexport const CurrentTailnetSchema = z.object({\n Name: z.string(),\n MagicDNSSuffix: z.string(),\n MagicDNSEnabled: z.boolean(),\n});\nexport type CurrentTailnet = z.infer<typeof CurrentTailnetSchema>;\n","import { parseJSON, jsonReplacer } from \"./json.js\";\nimport {\n AccessDeniedError,\n ConnectionError,\n DaemonNotRunningError,\n HttpError,\n PeerNotFoundError,\n PreconditionsFailedError,\n errorMessageFromBody,\n} from \"./errors.js\";\nimport { Transport, type TransportOptions } from \"./transport.js\";\nimport {\n ServeConfigSchema,\n StatusSchema,\n WhoIsResponseSchema,\n type ServeConfig,\n type Status,\n type WhoIsResponse,\n} from \"./types.js\";\n\n/**\n * Client for the Tailscale Local API.\n *\n * Connections are reused via HTTP keep-alive.\n */\nexport class Client {\n private readonly transport: Transport;\n\n constructor(opts: TransportOptions = {}) {\n this.transport = new Transport(opts);\n }\n\n private async doRequest(\n method: string,\n path: string,\n body?: Buffer | string,\n headers?: Record<string, string>,\n ): Promise<{ status: number; body: Buffer; headers: Record<string, string | string[] | undefined> }> {\n try {\n return await this.transport.request(method, path, body, headers);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"ECONNREFUSED\") || msg.includes(\"ENOENT\")) {\n throw new DaemonNotRunningError(msg);\n }\n throw new ConnectionError(msg);\n }\n }\n\n private async doRequestNice(\n method: string,\n path: string,\n body?: Buffer | string,\n headers?: Record<string, string>,\n ): Promise<Buffer> {\n const resp = await this.doRequest(method, path, body, headers);\n if (resp.status >= 200 && resp.status < 300) {\n return resp.body;\n }\n\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n\n if (resp.status === 403) throw new AccessDeniedError(msg);\n if (resp.status === 412) throw new PreconditionsFailedError(msg);\n throw new HttpError(resp.status, msg);\n }\n\n private async get200(path: string): Promise<Buffer> {\n return this.doRequestNice(\"GET\", path);\n }\n\n private async post200(path: string, body?: Buffer | string): Promise<Buffer> {\n return this.doRequestNice(\"POST\", path, body);\n }\n\n // --- Status ---\n\n /** Get the current tailscaled status. */\n async status(): Promise<Status> {\n const data = await this.get200(\"/localapi/v0/status\");\n return StatusSchema.parse(parseJSON(data.toString(\"utf-8\")));\n }\n\n /** Get the current tailscaled status without peer information. */\n async statusWithoutPeers(): Promise<Status> {\n const data = await this.get200(\"/localapi/v0/status?peers=false\");\n return StatusSchema.parse(parseJSON(data.toString(\"utf-8\")));\n }\n\n // --- WhoIs ---\n\n private async doWhoIs(params: string): Promise<WhoIsResponse> {\n const resp = await this.doRequest(\n \"GET\",\n `/localapi/v0/whois?${params}`,\n );\n if (resp.status === 404) {\n throw new PeerNotFoundError(params);\n }\n if (resp.status !== 200) {\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n if (resp.status === 403) throw new AccessDeniedError(msg);\n throw new HttpError(resp.status, msg);\n }\n return WhoIsResponseSchema.parse(parseJSON(resp.body.toString(\"utf-8\")));\n }\n\n /** Look up the owner of an IP address or IP:port. */\n async whoIs(remoteAddr: string): Promise<WhoIsResponse> {\n return this.doWhoIs(`addr=${encodeURIComponent(remoteAddr)}`);\n }\n\n /** Look up a peer by node key. */\n async whoIsNodeKey(nodeKey: string): Promise<WhoIsResponse> {\n return this.whoIs(nodeKey);\n }\n\n /** Look up the owner of an IP address with a specific protocol (\"tcp\" or \"udp\"). */\n async whoIsProto(proto: string, remoteAddr: string): Promise<WhoIsResponse> {\n return this.doWhoIs(`proto=${encodeURIComponent(proto)}&addr=${encodeURIComponent(remoteAddr)}`);\n }\n\n // --- Cert ---\n\n /** Get a TLS certificate and private key for the given domain. */\n async certPair(domain: string): Promise<{ cert: Buffer; key: Buffer }> {\n return this.certPairWithValidity(domain, 0);\n }\n\n /** Get a TLS certificate with minimum validity duration (in seconds). */\n async certPairWithValidity(\n domain: string,\n minValiditySecs: number,\n ): Promise<{ cert: Buffer; key: Buffer }> {\n const body = await this.get200(\n `/localapi/v0/cert/${encodeURIComponent(domain)}?type=pair&min_validity=${minValiditySecs}s`,\n );\n // Response is key PEM then cert PEM, separated by \"--\\n--\"\n const delimiter = Buffer.from(\"--\\n--\");\n const pos = body.indexOf(delimiter);\n if (pos === -1) {\n throw new Error(\"unexpected cert response: no delimiter\");\n }\n const split = pos + 3; // include \"--\\n\"\n const key = body.subarray(0, split);\n const cert = body.subarray(split);\n return { cert, key };\n }\n\n // --- Config ---\n\n /**\n * Get the current serve configuration.\n *\n * The returned ServeConfig has its ETag field populated from the\n * HTTP Etag response header.\n */\n async getServeConfig(): Promise<ServeConfig> {\n const resp = await this.doRequest(\"GET\", \"/localapi/v0/serve-config\");\n if (resp.status !== 200) {\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n if (resp.status === 403) throw new AccessDeniedError(msg);\n if (resp.status === 412) throw new PreconditionsFailedError(msg);\n throw new HttpError(resp.status, msg);\n }\n const config = ServeConfigSchema.parse(parseJSON(resp.body.toString(\"utf-8\"))) as ServeConfig;\n config.ETag = (resp.headers[\"etag\"] as string) ?? \"\";\n return config;\n }\n\n /**\n * Set the serve configuration.\n *\n * The ETag field on the config is sent as the If-Match header\n * for conditional updates.\n */\n async setServeConfig(config: ServeConfig): Promise<void> {\n const headers: Record<string, string> = {};\n if (config.ETag) headers[\"If-Match\"] = config.ETag;\n const body = JSON.stringify(config, jsonReplacer);\n await this.doRequestNice(\"POST\", \"/localapi/v0/serve-config\", body, headers);\n }\n\n /** Close the underlying transport and release resources. */\n destroy(): void {\n this.transport.destroy();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["execFile","http","z"],"sources":["../ts/src/json.ts","../ts/src/errors.ts","../ts/src/safesocket.ts","../ts/src/transport.ts","../ts/src/types.ts","../ts/src/client.ts"],"sourcesContent":["declare global {\n interface JSON {\n rawJSON(value: string): unknown;\n }\n}\n\n/** JSON reviver that converts large integers to BigInt to avoid precision loss. */\nexport const jsonReviver = (_key: string, value: unknown, context?: { source?: string }) => {\n if (typeof value === \"number\" && context?.source && !Number.isSafeInteger(value) && /^-?\\d+$/.test(context.source)) {\n return BigInt(context.source);\n }\n return value;\n};\n\n/** Parse a JSON string using {@link jsonReviver} for BigInt-safe integer handling. */\nexport const parseJSON = (str: string) => JSON.parse(str, jsonReviver);\n\n/** JSON replacer that serializes BigInt values as raw JSON numbers. */\nexport const jsonReplacer = (_key: string, value: unknown) =>\n typeof value === \"bigint\" ? JSON.rawJSON(value.toString()) : value;\n","/** Base error for all Tailscale Local API errors. */\nexport class TailscaleError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TailscaleError\";\n }\n}\n\n/** Raised when the server returns HTTP 403. */\nexport class AccessDeniedError extends TailscaleError {\n constructor(message: string) {\n super(`Access denied: ${message}`);\n this.name = \"AccessDeniedError\";\n }\n}\n\n/** Raised when the server returns HTTP 412. */\nexport class PreconditionsFailedError extends TailscaleError {\n constructor(message: string) {\n super(`Preconditions failed: ${message}`);\n this.name = \"PreconditionsFailedError\";\n }\n}\n\n/** Raised when a WhoIs lookup returns HTTP 404. */\nexport class PeerNotFoundError extends TailscaleError {\n constructor(message: string) {\n super(`Peer not found: ${message}`);\n this.name = \"PeerNotFoundError\";\n }\n}\n\n/** Raised when the connection to tailscaled fails. */\nexport class ConnectionError extends TailscaleError {\n constructor(message: string) {\n super(message);\n this.name = \"ConnectionError\";\n }\n}\n\n/** Raised when tailscaled is not running. */\nexport class DaemonNotRunningError extends ConnectionError {\n constructor(message: string) {\n super(message);\n this.name = \"DaemonNotRunningError\";\n }\n}\n\n/** Raised for unexpected HTTP status codes. */\nexport class HttpError extends TailscaleError {\n public readonly status: number;\n\n constructor(status: number, message: string) {\n super(`HTTP ${status}: ${message}`);\n this.name = \"HttpError\";\n this.status = status;\n }\n}\n\n/** Extract error message from a JSON body like Go's errorMessageFromBody. */\nexport function errorMessageFromBody(body: string): string | undefined {\n try {\n const data = JSON.parse(body);\n return data?.error;\n } catch {\n return undefined;\n }\n}\n","import { execFile } from \"node:child_process\";\nimport { readFile, readlink } from \"node:fs/promises\";\nimport { platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nexport const LOCAL_API_HOST = \"local-tailscaled.sock\";\nexport const CURRENT_CAP_VERSION = 131;\n\nexport interface PortAndToken {\n port: number;\n token: string;\n}\n\n/** Return the default socket path for the current platform. */\nexport function defaultSocketPath(): string {\n if (platform() === \"darwin\") {\n return \"/var/run/tailscaled.socket\";\n }\n // Linux and other Unix\n return \"/var/run/tailscale/tailscaled.sock\";\n}\n\n/** Attempt to discover macOS TCP port and token for tailscaled. */\nexport async function localTcpPortAndToken(): Promise<PortAndToken | undefined> {\n if (platform() !== \"darwin\") {\n return undefined;\n }\n\n // Try lsof method first (macOS GUI app)\n const result = await readMacosSameUserProof();\n if (result) return result;\n\n // Try filesystem method (macOS system extension)\n return readMacsysSameUserProof();\n}\n\nconst execFileP = promisify(execFile);\n\nasync function readMacosSameUserProof(): Promise<PortAndToken | undefined> {\n try {\n const uid = process.getuid?.();\n if (uid === undefined) return undefined;\n\n const { stdout: output } = await execFileP(\"lsof\", [\n \"-n\",\n \"-a\",\n `-u${uid}`,\n \"-c\",\n \"IPNExtension\",\n \"-F\",\n ]);\n return parseLsofOutput(output);\n } catch {\n return undefined;\n }\n}\n\n/** Parse lsof -F output looking for sameuserproof-PORT-TOKEN. */\nexport function parseLsofOutput(output: string): PortAndToken | undefined {\n const needle = \".tailscale.ipn.macos/sameuserproof-\";\n for (const line of output.split(\"\\n\")) {\n const idx = line.indexOf(needle);\n if (idx === -1) continue;\n const rest = line.slice(idx + needle.length);\n const dash = rest.indexOf(\"-\");\n if (dash === -1) continue;\n const portStr = rest.slice(0, dash);\n const token = rest.slice(dash + 1);\n const port = parseInt(portStr, 10);\n if (!isNaN(port)) {\n return { port, token };\n }\n }\n return undefined;\n}\n\nasync function readMacsysSameUserProof(\n sharedDir = \"/Library/Tailscale\",\n): Promise<PortAndToken | undefined> {\n try {\n const portPath = join(sharedDir, \"ipnport\");\n const portStr = await readlink(portPath, \"utf-8\");\n const port = parseInt(portStr, 10);\n if (isNaN(port)) return undefined;\n const tokenPath = join(sharedDir, `sameuserproof-${port}`);\n const tokenRaw = await readFile(tokenPath, \"utf-8\");\n const token = tokenRaw.trim();\n return { port, token };\n } catch {\n return undefined;\n }\n}\n","import * as http from \"node:http\";\nimport {\n CURRENT_CAP_VERSION,\n LOCAL_API_HOST,\n type PortAndToken,\n defaultSocketPath,\n localTcpPortAndToken,\n} from \"./safesocket.js\";\n\nexport interface TransportOptions {\n socketPath?: string;\n useSocketOnly?: boolean;\n}\n\n/**\n * Discover TCP port and token for this request.\n */\nasync function resolvePortAndToken(useSocketOnly: boolean): Promise<PortAndToken | undefined> {\n if (useSocketOnly) return undefined;\n return localTcpPortAndToken();\n}\n\n/**\n * HTTP transport that connects to tailscaled.\n * Reuses connections via Node.js http.Agent keep-alive.\n * Port and token are discovered per-request (matching Go's behavior),\n * so the client adapts to daemon restarts and late starts.\n */\nexport class Transport {\n private readonly socketPath: string;\n private readonly useSocketOnly: boolean;\n private readonly agent: http.Agent;\n\n constructor(opts: TransportOptions = {}) {\n this.socketPath = opts.socketPath ?? defaultSocketPath();\n this.useSocketOnly = opts.useSocketOnly ?? false;\n\n // Single agent with keep-alive — pools connections by host:port key\n this.agent = new http.Agent({\n keepAlive: true,\n keepAliveMsecs: 60_000,\n });\n }\n\n async request(\n method: string,\n path: string,\n body?: Buffer | string,\n extraHeaders?: Record<string, string>,\n ): Promise<{ status: number; body: Buffer; headers: http.IncomingHttpHeaders }> {\n const portAndToken = await resolvePortAndToken(this.useSocketOnly);\n\n return new Promise((resolve, reject) => {\n const headers: Record<string, string> = {\n Host: LOCAL_API_HOST,\n \"Tailscale-Cap\": String(CURRENT_CAP_VERSION),\n ...extraHeaders,\n };\n\n if (portAndToken) {\n const cred = Buffer.from(`:${portAndToken.token}`).toString(\"base64\");\n headers[\"Authorization\"] = `Basic ${cred}`;\n }\n\n const options: http.RequestOptions = {\n method,\n path,\n headers,\n agent: this.agent,\n };\n\n if (portAndToken) {\n options.host = \"127.0.0.1\";\n options.port = portAndToken.port;\n } else {\n options.socketPath = this.socketPath;\n }\n\n const req = http.request(options, (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n resolve({\n status: res.statusCode ?? 0,\n body: Buffer.concat(chunks),\n headers: res.headers,\n });\n });\n res.on(\"error\", reject);\n });\n\n req.on(\"error\", reject);\n\n if (body !== undefined) {\n req.write(body);\n }\n req.end();\n });\n }\n\n destroy(): void {\n this.agent.destroy();\n }\n}\n","// Code generated by cmd/typegen; DO NOT EDIT.\n\nimport { z } from \"zod\";\n\nconst goSlice = <T extends z.ZodTypeAny>(item: T) =>\n z.array(item).nullish().transform(v => v ?? []);\n\nconst goMap = <V extends z.ZodTypeAny>(val: V) =>\n z.record(z.string(), val).nullish().transform(v => v ?? {});\n\nconst int64 = z.union([z.number().int(), z.bigint()]).transform(v => BigInt(v));\n\n/**\n * Location represents geographical location data about a\n * Tailscale host. Location is optional and only set if\n * explicitly declared by a node.\n */\nexport const LocationSchema = z.object({\n /** User friendly country name, with proper capitalization (\"Canada\") */\n Country: z.string().nullish(),\n /** ISO 3166-1 alpha-2 in upper case (\"CA\") */\n CountryCode: z.string().nullish(),\n /** User friendly city name, with proper capitalization (\"Squamish\") */\n City: z.string().nullish(),\n /**\n * CityCode is a short code representing the city in upper case.\n * CityCode is used to disambiguate a city from another location\n * with the same city name. It uniquely identifies a particular\n * geographical location, within the tailnet.\n * IATA, ICAO or ISO 3166-2 codes are recommended (\"YSE\")\n */\n CityCode: z.string().nullish(),\n /**\n * Latitude, Longitude are optional geographical coordinates of the node, in degrees.\n * No particular accuracy level is promised; the coordinates may simply be the center of the city or country.\n */\n Latitude: z.number().nullish(),\n Longitude: z.number().nullish(),\n /**\n * Priority determines the order of use of an exit node when a\n * location based preference matches more than one exit node,\n * the node with the highest priority wins. Nodes of equal\n * probability may be selected arbitrarily.\n * \n * A value of 0 means the exit node does not have a priority\n * preference. A negative int is not allowed.\n */\n Priority: int64.nullish(),\n});\nexport type Location = z.infer<typeof LocationSchema>;\n\n/**\n * PeerStatus describes a peer node and its current state.\n * WARNING: The fields in PeerStatus are merged by the AddPeer method in the StatusBuilder.\n * When adding a new field to PeerStatus, you must update AddPeer to handle merging\n * the new field. The AddPeer function is responsible for combining multiple updates\n * to the same peer, and any new field that is not merged properly may lead to\n * inconsistencies or lost data in the peer status.\n */\nexport const PeerStatusSchema = z.object({\n ID: z.string().default(\"\"),\n PublicKey: z.string().default(\"\"),\n /** HostInfo's Hostname (not a DNS name or necessarily unique) */\n HostName: z.string().default(\"\"),\n /**\n * DNSName is the Peer's FQDN. It ends with a dot.\n * It has the form \"host.<MagicDNSSuffix>.\"\n */\n DNSName: z.string().default(\"\"),\n /** HostInfo.OS */\n OS: z.string().default(\"\"),\n UserID: int64.default(0n),\n /**\n * AltSharerUserID is the user who shared this node\n * if it's different than UserID. Otherwise it's zero.\n */\n AltSharerUserID: int64.nullish(),\n /** TailscaleIPs are the IP addresses assigned to the node. */\n TailscaleIPs: goSlice(z.string()),\n /** AllowedIPs are IP addresses allowed to route to this node. */\n AllowedIPs: goSlice(z.string()),\n /**\n * Tags are the list of ACL tags applied to this node.\n * See tailscale.com/tailcfg#Node.Tags for more information.\n */\n Tags: goSlice(z.string()),\n /**\n * PrimaryRoutes are the routes this node is currently the primary\n * subnet router for, as determined by the control plane. It does\n * not include the IPs in TailscaleIPs.\n */\n PrimaryRoutes: goSlice(z.string()),\n /** Endpoints: */\n Addrs: goSlice(z.string()),\n /** one of Addrs, or unique if roaming */\n CurAddr: z.string().default(\"\"),\n /** DERP region */\n Relay: z.string().default(\"\"),\n /** peer relay address (ip:port:vni) */\n PeerRelay: z.string().default(\"\"),\n RxBytes: int64.default(0n),\n TxBytes: int64.default(0n),\n /** time registered with tailcontrol */\n Created: z.string().default(\"\"),\n /** time last packet sent */\n LastWrite: z.string().default(\"\"),\n /** last seen to tailcontrol; only present if offline */\n LastSeen: z.string().default(\"\"),\n /** with local wireguard */\n LastHandshake: z.string().default(\"\"),\n /** whether node is connected to the control plane */\n Online: z.boolean().default(false),\n /** true if this is the currently selected exit node. */\n ExitNode: z.boolean().default(false),\n /** true if this node can be an exit node (offered && approved) */\n ExitNodeOption: z.boolean().default(false),\n /**\n * Active is whether the node was recently active. The\n * definition is somewhat undefined but has historically and\n * currently means that there was some packet sent to this\n * peer in the past two minutes. That definition is subject to\n * change.\n */\n Active: z.boolean().default(false),\n /** PeerAPIURL are the URLs of the node's PeerAPI servers. */\n PeerAPIURL: goSlice(z.string()),\n /** TaildropTargetStatus represents the node's eligibility to have files shared to it. */\n TaildropTarget: int64.default(0n),\n /** Reason why this peer cannot receive files. Empty if CanReceiveFiles=true */\n NoFileSharingReason: z.string().default(\"\"),\n /**\n * Capabilities are capabilities that the node has.\n * They're free-form strings, but should be in the form of URLs/URIs\n * such as:\n * \"https://tailscale.com/cap/is-admin\"\n * \"https://tailscale.com/cap/file-sharing\"\n * \"funnel\"\n * \n * Deprecated: use CapMap instead. See https://github.com/tailscale/tailscale/issues/11508\n * Every value is Capabilities is also a key in CapMap, even if it\n * has no values in that map.\n */\n Capabilities: goSlice(z.string()),\n /** CapMap is a map of capabilities to their values. */\n CapMap: goMap(goSlice(z.unknown())),\n /** SSH_HostKeys are the node's SSH host keys, if known. */\n sshHostKeys: goSlice(z.string()),\n /**\n * ShareeNode indicates this node exists in the netmap because\n * it's owned by a shared-to user and that node might connect\n * to us. These nodes should be hidden by \"tailscale status\"\n * etc by default.\n */\n ShareeNode: z.boolean().nullish(),\n /**\n * InNetworkMap means that this peer was seen in our latest network map.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InNetworkMap: z.boolean().default(false),\n /**\n * InMagicSock means that this peer is being tracked by magicsock.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InMagicSock: z.boolean().default(false),\n /**\n * InEngine means that this peer is tracked by the wireguard engine.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InEngine: z.boolean().default(false),\n /**\n * Expired means that this peer's node key has expired, based on either\n * information from control or optimisically set on the client if the\n * expiration time has passed.\n */\n Expired: z.boolean().nullish(),\n /**\n * KeyExpiry, if present, is the time at which the node key expired or\n * will expire.\n */\n KeyExpiry: z.string().nullish(),\n Location: LocationSchema.nullish(),\n});\nexport type PeerStatus = z.infer<typeof PeerStatusSchema>;\n\n/** ExitNodeStatus describes the current exit node. */\nexport const ExitNodeStatusSchema = z.object({\n /** ID is the exit node's ID. */\n ID: z.string().default(\"\"),\n /** Online is whether the exit node is alive. */\n Online: z.boolean().default(false),\n /** TailscaleIPs are the exit node's IP addresses assigned to the node. */\n TailscaleIPs: goSlice(z.string()),\n});\nexport type ExitNodeStatus = z.infer<typeof ExitNodeStatusSchema>;\n\n/** TailnetStatus is information about a Tailscale network (\"tailnet\"). */\nexport const TailnetStatusSchema = z.object({\n /** Name is the name of the network that's currently in use. */\n Name: z.string().default(\"\"),\n /**\n * MagicDNSSuffix is the network's MagicDNS suffix for nodes\n * in the network such as \"userfoo.tailscale.net\".\n * There are no surrounding dots.\n * MagicDNSSuffix should be populated regardless of whether a domain\n * has MagicDNS enabled.\n */\n MagicDNSSuffix: z.string().default(\"\"),\n /**\n * MagicDNSEnabled is whether or not the network has MagicDNS enabled.\n * Note that the current device may still not support MagicDNS if\n * `--accept-dns=false` was used.\n */\n MagicDNSEnabled: z.boolean().default(false),\n});\nexport type TailnetStatus = z.infer<typeof TailnetStatusSchema>;\n\n/**\n * A UserProfile is display-friendly data for a [User].\n * It includes the LoginName for display purposes but *not* the Provider.\n * It also includes derived data from one of the user's logins.\n */\nexport const UserProfileSchema = z.object({\n ID: int64.default(0n),\n /** \"alice@smith.com\"; for display purposes only (provider is not listed) */\n LoginName: z.string().default(\"\"),\n /** \"Alice Smith\" */\n DisplayName: z.string().default(\"\"),\n ProfilePicURL: z.string().nullish(),\n});\nexport type UserProfile = z.infer<typeof UserProfileSchema>;\n\n/**\n * ClientVersion is information about the latest client version that's available\n * for the client (and whether they're already running it).\n * \n * It does not include a URL to download the client, as that varies by platform.\n */\nexport const ClientVersionSchema = z.object({\n /** RunningLatest is true if the client is running the latest build. */\n RunningLatest: z.boolean().nullish(),\n /**\n * LatestVersion is the latest version.Short (\"1.34.2\") version available\n * for download for the client's platform and packaging type.\n * It won't be populated if RunningLatest is true.\n */\n LatestVersion: z.string().nullish(),\n /**\n * UrgentSecurityUpdate is set when the client is missing an important\n * security update. That update may be in LatestVersion or earlier.\n * UrgentSecurityUpdate should not be set if RunningLatest is false.\n */\n UrgentSecurityUpdate: z.boolean().nullish(),\n /**\n * Notify is whether the client should do an OS-specific notification about\n * a new version being available. This should not be populated if\n * RunningLatest is true. The client should not notify multiple times for\n * the same LatestVersion value.\n */\n Notify: z.boolean().nullish(),\n /**\n * NotifyURL is a URL to open in the browser when the user clicks on the\n * notification, when Notify is true.\n */\n NotifyURL: z.string().nullish(),\n /** NotifyText is the text to show in the notification, when Notify is true. */\n NotifyText: z.string().nullish(),\n});\nexport type ClientVersion = z.infer<typeof ClientVersionSchema>;\n\n/** Status represents the entire state of the IPN network. */\nexport const StatusSchema = z.object({\n /** Version is the daemon's long version (see version.Long). */\n Version: z.string().default(\"\"),\n /**\n * TUN is whether /dev/net/tun (or equivalent kernel interface) is being\n * used. If false, it's running in userspace mode.\n */\n TUN: z.boolean().default(false),\n /**\n * BackendState is an ipn.State string value:\n * \"NoState\", \"NeedsLogin\", \"NeedsMachineAuth\", \"Stopped\",\n * \"Starting\", \"Running\".\n */\n BackendState: z.string().default(\"\"),\n /** HaveNodeKey is whether the current profile has a node key configured. */\n HaveNodeKey: z.boolean().nullish(),\n /** current URL provided by control to authorize client */\n AuthURL: z.string().default(\"\"),\n /** Tailscale IP(s) assigned to this node */\n TailscaleIPs: goSlice(z.string()),\n Self: PeerStatusSchema.prefault({}),\n /**\n * ExitNodeStatus describes the current exit node.\n * If nil, an exit node is not in use.\n */\n ExitNodeStatus: ExitNodeStatusSchema.nullish(),\n /**\n * Health contains health check problems.\n * Empty means everything is good. (or at least that no known\n * problems are detected)\n */\n Health: goSlice(z.string()),\n /**\n * This field is the legacy name of CurrentTailnet.MagicDNSSuffix.\n * \n * Deprecated: use CurrentTailnet.MagicDNSSuffix instead.\n */\n MagicDNSSuffix: z.string().default(\"\"),\n /**\n * CurrentTailnet is information about the tailnet that the node\n * is currently connected to. When not connected, this field is nil.\n */\n CurrentTailnet: TailnetStatusSchema.nullish(),\n /**\n * CertDomains are the set of DNS names for which the control\n * plane server will assist with provisioning TLS\n * certificates. See SetDNSRequest for dns-01 ACME challenges\n * for e.g. LetsEncrypt. These names are FQDNs without\n * trailing periods, and without any \"_acme-challenge.\" prefix.\n */\n CertDomains: goSlice(z.string()),\n /** Peer is the state of each peer, keyed by each peer's current public key. */\n Peer: goMap(PeerStatusSchema),\n /**\n * User contains profile information about UserIDs referenced by\n * PeerStatus.UserID, PeerStatus.AltSharerUserID, etc.\n */\n User: goMap(UserProfileSchema),\n /**\n * ClientVersion, when non-nil, contains information about the latest\n * version of the Tailscale client that's available. Depending on\n * the platform and client settings, it may not be available.\n */\n ClientVersion: ClientVersionSchema.nullish(),\n});\nexport type Status = z.infer<typeof StatusSchema>;\n\n/** Service represents a service running on a node. */\nexport const ServiceSchema = z.object({\n /**\n * Proto is the type of service. It's usually the constant TCP\n * or UDP (\"tcp\" or \"udp\"), but it can also be one of the\n * following meta service values:\n * \n * * \"peerapi4\": peerapi is available on IPv4; Port is the\n * port number that the peerapi is running on the\n * node's Tailscale IPv4 address.\n * * \"peerapi6\": peerapi is available on IPv6; Port is the\n * port number that the peerapi is running on the\n * node's Tailscale IPv6 address.\n * * \"peerapi-dns-proxy\": the local peerapi service supports\n * being a DNS proxy (when the node is an exit\n * node). For this service, the Port number must only be 1.\n */\n Proto: z.string().default(\"\"),\n /**\n * Port is the port number.\n * \n * For Proto \"peerapi-dns\", it must be 1.\n */\n Port: z.number().default(0),\n /**\n * Description is the textual description of the service,\n * usually the process name that's running.\n */\n Description: z.string().nullish(),\n});\nexport type Service = z.infer<typeof ServiceSchema>;\n\n/** NetInfo contains information about the host's network state. */\nexport const NetInfoSchema = z.object({\n /**\n * MappingVariesByDestIP says whether the host's NAT mappings\n * vary based on the destination IP.\n */\n MappingVariesByDestIP: z.boolean().nullish(),\n /** WorkingIPv6 is whether the host has IPv6 internet connectivity. */\n WorkingIPv6: z.boolean().nullish(),\n /**\n * OSHasIPv6 is whether the OS supports IPv6 at all, regardless of\n * whether IPv6 internet connectivity is available.\n */\n OSHasIPv6: z.boolean().nullish(),\n /** WorkingUDP is whether the host has UDP internet connectivity. */\n WorkingUDP: z.boolean().nullish(),\n /**\n * WorkingICMPv4 is whether ICMPv4 works.\n * Empty means not checked.\n */\n WorkingICMPv4: z.boolean().nullish(),\n /**\n * HavePortMap is whether we have an existing portmap open\n * (UPnP, PMP, or PCP).\n */\n HavePortMap: z.boolean().nullish(),\n /**\n * UPnP is whether UPnP appears present on the LAN.\n * Empty means not checked.\n */\n UPnP: z.boolean().nullish(),\n /**\n * PMP is whether NAT-PMP appears present on the LAN.\n * Empty means not checked.\n */\n PMP: z.boolean().nullish(),\n /**\n * PCP is whether PCP appears present on the LAN.\n * Empty means not checked.\n */\n PCP: z.boolean().nullish(),\n /**\n * PreferredDERP is this node's preferred (home) DERP region ID.\n * This is where the node expects to be contacted to begin a\n * peer-to-peer connection. The node might be be temporarily\n * connected to multiple DERP servers (to speak to other nodes\n * that are located elsewhere) but PreferredDERP is the region ID\n * that the node subscribes to traffic at.\n * Zero means disconnected or unknown.\n */\n PreferredDERP: int64.nullish(),\n /** LinkType is the current link type, if known. */\n LinkType: z.string().nullish(),\n /**\n * DERPLatency is the fastest recent time to reach various\n * DERP STUN servers, in seconds. The map key is the\n * \"regionID-v4\" or \"-v6\"; it was previously the DERP server's\n * STUN host:port.\n * \n * This should only be updated rarely, or when there's a\n * material change, as any change here also gets uploaded to\n * the control plane.\n */\n DERPLatency: goMap(z.number()),\n /**\n * FirewallMode encodes both which firewall mode was selected and why.\n * It is Linux-specific (at least as of 2023-08-19) and is meant to help\n * debug iptables-vs-nftables issues. The string is of the form\n * \"{nft,ift}-REASON\", like \"nft-forced\" or \"ipt-default\". Empty means\n * either not Linux or a configuration in which the host firewall rules\n * are not managed by tailscaled.\n */\n FirewallMode: z.string().nullish(),\n});\nexport type NetInfo = z.infer<typeof NetInfoSchema>;\n\n/**\n * TPMInfo contains information about a TPM 2.0 device present on a node.\n * All fields are read from TPM_CAP_TPM_PROPERTIES, see Part 2, section 6.13 of\n * https://trustedcomputinggroup.org/resource/tpm-library-specification/.\n */\nexport const TPMInfoSchema = z.object({\n /**\n * Manufacturer is a 4-letter code from section 4.1 of\n * https://trustedcomputinggroup.org/resource/vendor-id-registry/,\n * for example \"MSFT\" for Microsoft.\n * Read from TPM_PT_MANUFACTURER.\n */\n Manufacturer: z.string().nullish(),\n /**\n * Vendor is a vendor ID string, up to 16 characters.\n * Read from TPM_PT_VENDOR_STRING_*.\n */\n Vendor: z.string().nullish(),\n /**\n * Model is a vendor-defined TPM model.\n * Read from TPM_PT_VENDOR_TPM_TYPE.\n */\n Model: int64.nullish(),\n /**\n * FirmwareVersion is the version number of the firmware.\n * Read from TPM_PT_FIRMWARE_VERSION_*.\n */\n FirmwareVersion: int64.nullish(),\n /**\n * SpecRevision is the TPM 2.0 spec revision encoded as a single number. All\n * revisions can be found at\n * https://trustedcomputinggroup.org/resource/tpm-library-specification/.\n * Before revision 184, TCG used the \"01.83\" format for revision 183.\n */\n SpecRevision: int64.nullish(),\n /**\n * FamilyIndicator is the TPM spec family, like \"2.0\".\n * Read from TPM_PT_FAMILY_INDICATOR.\n */\n FamilyIndicator: z.string().nullish(),\n});\nexport type TPMInfo = z.infer<typeof TPMInfoSchema>;\n\n/**\n * Hostinfo contains a summary of a Tailscale host.\n * \n * Because it contains pointers (slices), this type should not be used\n * as a value type.\n */\nexport const HostinfoSchema = z.object({\n /** version of this code (in version.Long format) */\n IPNVersion: z.string().nullish(),\n /** logtail ID of frontend instance */\n FrontendLogID: z.string().nullish(),\n /** logtail ID of backend instance */\n BackendLogID: z.string().nullish(),\n /** operating system the client runs on (a version.OS value) */\n OS: z.string().nullish(),\n /**\n * OSVersion is the version of the OS, if available.\n * \n * For Android, it's like \"10\", \"11\", \"12\", etc. For iOS and macOS it's like\n * \"15.6.1\" or \"12.4.0\". For Windows it's like \"10.0.19044.1889\". For\n * FreeBSD it's like \"12.3-STABLE\".\n * \n * For Linux, prior to Tailscale 1.32, we jammed a bunch of fields into this\n * string on Linux, like \"Debian 10.4; kernel=xxx; container; env=kn\" and so\n * on. As of Tailscale 1.32, this is simply the kernel version on Linux, like\n * \"5.10.0-17-amd64\".\n */\n OSVersion: z.string().nullish(),\n /** best-effort whether the client is running in a container */\n Container: z.boolean().nullish(),\n /** a hostinfo.EnvType in string form */\n Env: z.string().nullish(),\n /** \"debian\", \"ubuntu\", \"nixos\", ... */\n Distro: z.string().nullish(),\n /** \"20.04\", ... */\n DistroVersion: z.string().nullish(),\n /** \"jammy\", \"bullseye\", ... */\n DistroCodeName: z.string().nullish(),\n /** App is used to disambiguate Tailscale clients that run using tsnet. */\n App: z.string().nullish(),\n /** if a desktop was detected on Linux */\n Desktop: z.boolean().nullish(),\n /** Tailscale package to disambiguate (\"choco\", \"appstore\", etc; \"\" for unknown) */\n Package: z.string().nullish(),\n /** mobile phone model (\"Pixel 3a\", \"iPhone12,3\") */\n DeviceModel: z.string().nullish(),\n /** macOS/iOS APNs device token for notifications (and Android in the future) */\n PushDeviceToken: z.string().nullish(),\n /** name of the host the client runs on */\n Hostname: z.string().nullish(),\n /** indicates whether the host is blocking incoming connections */\n ShieldsUp: z.boolean().nullish(),\n /** indicates this node exists in netmap because it's owned by a shared-to user */\n ShareeNode: z.boolean().nullish(),\n /** indicates that the user has opted out of sending logs and support */\n NoLogsNoSupport: z.boolean().nullish(),\n /**\n * WireIngress indicates that the node would like to be wired up server-side\n * (DNS, etc) to be able to use Tailscale Funnel, even if it's not currently\n * enabled. For example, the user might only use it for intermittent\n * foreground CLI serve sessions, for which they'd like it to work right\n * away, even if it's disabled most of the time. As an optimization, this is\n * only sent if IngressEnabled is false, as IngressEnabled implies that this\n * option is true.\n */\n WireIngress: z.boolean().nullish(),\n /** if the node has any funnel endpoint enabled */\n IngressEnabled: z.boolean().nullish(),\n /** indicates that the node has opted-in to admin-console-drive remote updates */\n AllowsUpdate: z.boolean().nullish(),\n /** the current host's machine type (uname -m) */\n Machine: z.string().nullish(),\n /** GOARCH value (of the built binary) */\n GoArch: z.string().nullish(),\n /** GOARM, GOAMD64, etc (of the built binary) */\n GoArchVar: z.string().nullish(),\n /** Go version binary was built with */\n GoVersion: z.string().nullish(),\n /** set of IP ranges this client can route */\n RoutableIPs: goSlice(z.string()),\n /** set of ACL tags this node wants to claim */\n RequestTags: goSlice(z.string()),\n /** MAC address(es) to send Wake-on-LAN packets to wake this node (lowercase hex w/ colons) */\n WoLMACs: goSlice(z.string()),\n /** services advertised by this machine */\n Services: goSlice(ServiceSchema),\n NetInfo: NetInfoSchema.nullish(),\n /** if advertised */\n sshHostKeys: goSlice(z.string()),\n Cloud: z.string().nullish(),\n /** if the client is running in userspace (netstack) mode */\n Userspace: z.boolean().nullish(),\n /** if the client's subnet router is running in userspace (netstack) mode */\n UserspaceRouter: z.boolean().nullish(),\n /** if the client is running the app-connector service */\n AppConnector: z.boolean().nullish(),\n /** opaque hash of the most recent list of tailnet services, change in hash indicates config should be fetched via c2n */\n ServicesHash: z.string().nullish(),\n /** the client’s selected exit node, empty when unselected. */\n ExitNodeID: z.string().nullish(),\n /**\n * Location represents geographical location data about a\n * Tailscale host. Location is optional and only set if\n * explicitly declared by a node.\n */\n Location: LocationSchema.nullish(),\n /** TPM device metadata, if available */\n TPM: TPMInfoSchema.nullish(),\n /**\n * StateEncrypted reports whether the node state is stored encrypted on\n * disk. The actual mechanism is platform-specific:\n * * Apple nodes use the Keychain\n * * Linux and Windows nodes use the TPM\n * * Android apps use EncryptedSharedPreferences\n */\n StateEncrypted: z.boolean().nullish(),\n});\nexport type Hostinfo = z.infer<typeof HostinfoSchema>;\n\n/** Resolver is the configuration for one DNS resolver. */\nexport const ResolverSchema = z.object({\n /**\n * Addr is the address of the DNS resolver, one of:\n * - A plain IP address for a \"classic\" UDP+TCP DNS resolver.\n * This is the common format as sent by the control plane.\n * - An IP:port, for tests.\n * - \"https://resolver.com/path\" for DNS over HTTPS; currently\n * as of 2022-09-08 only used for certain well-known resolvers\n * (see the publicdns package) for which the IP addresses to dial DoH are\n * known ahead of time, so bootstrap DNS resolution is not required.\n * - \"http://node-address:port/path\" for DNS over HTTP over WireGuard. This\n * is implemented in the PeerAPI for exit nodes and app connectors.\n * - [TODO] \"tls://resolver.com\" for DNS over TCP+TLS\n */\n Addr: z.string().nullish(),\n /**\n * BootstrapResolution is an optional suggested resolution for the\n * DoT/DoH resolver, if the resolver URL does not reference an IP\n * address directly.\n * BootstrapResolution may be empty, in which case clients should\n * look up the DoT/DoH server using their local \"classic\" DNS\n * resolver.\n * \n * As of 2022-09-08, BootstrapResolution is not yet used.\n */\n BootstrapResolution: goSlice(z.string()),\n /**\n * UseWithExitNode designates that this resolver should continue to be used when an\n * exit node is in use. Normally, DNS resolution is delegated to the exit node but\n * there are situations where it is preferable to still use a Split DNS server and/or\n * global DNS server instead of the exit node.\n */\n UseWithExitNode: z.boolean().nullish(),\n});\nexport type Resolver = z.infer<typeof ResolverSchema>;\n\n/** Node is a Tailscale device in a tailnet. */\nexport const NodeSchema = z.object({\n ID: int64.default(0n),\n StableID: z.string().default(\"\"),\n /**\n * Name is the FQDN of the node.\n * It is also the MagicDNS name for the node.\n * It has a trailing dot.\n * e.g. \"host.tail-scale.ts.net.\"\n */\n Name: z.string().default(\"\"),\n /**\n * User is the user who created the node. If ACL tags are in use for the\n * node then it doesn't reflect the ACL identity that the node is running\n * as.\n */\n User: int64.default(0n),\n /** Sharer, if non-zero, is the user who shared this node, if different than User. */\n Sharer: int64.nullish(),\n Key: z.string().default(\"\"),\n /** the zero value if this node does not expire */\n KeyExpiry: z.string().nullish(),\n KeySignature: z.string().nullish(),\n Machine: z.string().nullish(),\n DiscoKey: z.string().nullish(),\n /** Addresses are the IP addresses of this Node directly. */\n Addresses: goSlice(z.string()),\n /**\n * AllowedIPs are the IP ranges to route to this node.\n * \n * As of CapabilityVersion 112, this may be nil (null or undefined) on the wire\n * to mean the same as Addresses. Internally, it is always filled in with\n * its possibly-implicit value.\n */\n AllowedIPs: goSlice(z.string()),\n /** IP+port (public via STUN, and local LANs) */\n Endpoints: goSlice(z.string()),\n /**\n * LegacyDERPString is this node's home LegacyDERPString region ID integer, but shoved into an\n * IP:port string for legacy reasons. The IP address is always \"127.3.3.40\"\n * (a loopback address (127) followed by the digits over the letters DERP on\n * a QWERTY keyboard (3.3.40)). The \"port number\" is the home LegacyDERPString region ID\n * integer.\n * \n * Deprecated: HomeDERP has replaced this, but old servers might still send\n * this field. See tailscale/tailscale#14636. Do not use this field in code\n * other than in the upgradeNode func, which canonicalizes it to HomeDERP\n * if it arrives as a LegacyDERPString string on the wire.\n */\n DERP: z.string().nullish(),\n /**\n * HomeDERP is the modern version of the DERP string field, with just an\n * integer. The client advertises support for this as of capver 111.\n * \n * HomeDERP may be zero if not (yet) known, but ideally always be non-zero\n * for magicsock connectivity to function normally.\n */\n HomeDERP: int64.nullish(),\n Hostinfo: HostinfoSchema.nullish(),\n Created: z.string().nullish(),\n /** if non-zero, the node's capability version; old servers might not send */\n Cap: int64.nullish(),\n /**\n * Tags are the list of ACL tags applied to this node.\n * Tags take the form of `tag:<value>` where value starts\n * with a letter and only contains alphanumerics and dashes `-`.\n * Some valid tag examples:\n * `tag:prod`\n * `tag:database`\n * `tag:lab-1`\n */\n Tags: goSlice(z.string()),\n /**\n * PrimaryRoutes are the routes from AllowedIPs that this node\n * is currently the primary subnet router for, as determined\n * by the control plane. It does not include the self address\n * values from Addresses that are in AllowedIPs.\n */\n PrimaryRoutes: goSlice(z.string()),\n /**\n * LastSeen is when the node was last online. It is not\n * updated when Online is true. It is nil if the current\n * node doesn't have permission to know, or the node\n * has never been online.\n */\n LastSeen: z.string().nullish(),\n /**\n * Online is whether the node is currently connected to the\n * coordination server. A value of nil means unknown, or the\n * current node doesn't have permission to know.\n */\n Online: z.boolean().nullish(),\n /** TODO(crawshaw): replace with MachineStatus */\n MachineAuthorized: z.boolean().nullish(),\n /**\n * Capabilities are capabilities that the node has.\n * They're free-form strings, but should be in the form of URLs/URIs\n * such as:\n * \"https://tailscale.com/cap/is-admin\"\n * \"https://tailscale.com/cap/file-sharing\"\n * \n * Deprecated: use CapMap instead. See https://github.com/tailscale/tailscale/issues/11508\n */\n Capabilities: goSlice(z.string()),\n /**\n * CapMap is a map of capabilities to their optional argument/data values.\n * \n * It is valid for a capability to not have any argument/data values; such\n * capabilities can be tested for using the HasCap method. These type of\n * capabilities are used to indicate that a node has a capability, but there\n * is no additional data associated with it. These were previously\n * represented by the Capabilities field, but can now be represented by\n * CapMap with an empty value.\n * \n * See NodeCapability for more information on keys.\n * \n * Metadata about nodes can be transmitted in 3 ways:\n * 1. MapResponse.Node.CapMap describes attributes that affect behavior for\n * this node, such as which features have been enabled through the admin\n * panel and any associated configuration details.\n * 2. MapResponse.PacketFilter(s) describes access (both IP and application\n * based) that should be granted to peers.\n * 3. MapResponse.Peers[].CapMap describes attributes regarding a peer node,\n * such as which features the peer supports or if that peer is preferred\n * for a particular task vs other peers that could also be chosen.\n */\n CapMap: goMap(goSlice(z.unknown())),\n /**\n * UnsignedPeerAPIOnly means that this node is not signed nor subject to TKA\n * restrictions. However, in exchange for that privilege, it does not get\n * network access. It can only access this node's peerapi, which may not let\n * it do anything. It is the tailscaled client's job to double-check the\n * MapResponse's PacketFilter to verify that its AllowedIPs will not be\n * accepted by the packet filter.\n */\n UnsignedPeerAPIOnly: z.boolean().nullish(),\n /** MagicDNS base name (for normal non-shared-in nodes), FQDN (without trailing dot, for shared-in nodes), or Hostname (if no MagicDNS) */\n ComputedName: z.string().nullish(),\n /** either \"ComputedName\" or \"ComputedName (computedHostIfDifferent)\", if computedHostIfDifferent is set */\n ComputedNameWithHost: z.string().nullish(),\n /** DataPlaneAuditLogID is the per-node logtail ID used for data plane audit logging. */\n DataPlaneAuditLogID: z.string().nullish(),\n /**\n * Expired is whether this node's key has expired. Control may send\n * this; clients are only allowed to set this from false to true. On\n * the client, this is calculated client-side based on a timestamp sent\n * from control, to avoid clock skew issues.\n */\n Expired: z.boolean().nullish(),\n /**\n * SelfNodeV4MasqAddrForThisPeer is the IPv4 that this peer knows the current node as.\n * It may be empty if the peer knows the current node by its native\n * IPv4 address.\n * This field is only populated in a MapResponse for peers and not\n * for the current node.\n * \n * If set, it should be used to masquerade traffic originating from the\n * current node to this peer. The masquerade address is only relevant\n * for this peer and not for other peers.\n * \n * This only applies to traffic originating from the current node to the\n * peer or any of its subnets. Traffic originating from subnet routes will\n * not be masqueraded (e.g. in case of --snat-subnet-routes).\n */\n SelfNodeV4MasqAddrForThisPeer: z.string().nullish(),\n /**\n * SelfNodeV6MasqAddrForThisPeer is the IPv6 that this peer knows the current node as.\n * It may be empty if the peer knows the current node by its native\n * IPv6 address.\n * This field is only populated in a MapResponse for peers and not\n * for the current node.\n * \n * If set, it should be used to masquerade traffic originating from the\n * current node to this peer. The masquerade address is only relevant\n * for this peer and not for other peers.\n * \n * This only applies to traffic originating from the current node to the\n * peer or any of its subnets. Traffic originating from subnet routes will\n * not be masqueraded (e.g. in case of --snat-subnet-routes).\n */\n SelfNodeV6MasqAddrForThisPeer: z.string().nullish(),\n /**\n * IsWireGuardOnly indicates that this is a non-Tailscale WireGuard peer, it\n * is not expected to speak Disco or DERP, and it must have Endpoints in\n * order to be reachable.\n */\n IsWireGuardOnly: z.boolean().nullish(),\n /**\n * IsJailed indicates that this node is jailed and should not be allowed\n * initiate connections, however outbound connections to it should still be\n * allowed.\n */\n IsJailed: z.boolean().nullish(),\n /**\n * ExitNodeDNSResolvers is the list of DNS servers that should be used when this\n * node is marked IsWireGuardOnly and being used as an exit node.\n */\n ExitNodeDNSResolvers: goSlice(ResolverSchema),\n});\nexport type Node = z.infer<typeof NodeSchema>;\n\n/**\n * TCPPortHandler describes what to do when handling a TCP\n * connection.\n */\nexport const TCPPortHandlerSchema = z.object({\n /**\n * HTTPS, if true, means that tailscaled should handle this connection as an\n * HTTPS request as configured by ServeConfig.Web.\n * \n * It is mutually exclusive with TCPForward.\n */\n HTTPS: z.boolean().nullish(),\n /**\n * HTTP, if true, means that tailscaled should handle this connection as an\n * HTTP request as configured by ServeConfig.Web.\n * \n * It is mutually exclusive with TCPForward.\n */\n HTTP: z.boolean().nullish(),\n /**\n * TCPForward is the IP:port to forward TCP connections to.\n * Whether or not TLS is terminated by tailscaled depends on\n * TerminateTLS.\n * \n * It is mutually exclusive with HTTPS.\n */\n TCPForward: z.string().nullish(),\n /**\n * TerminateTLS, if non-empty, means that tailscaled should terminate the\n * TLS connections before forwarding them to TCPForward, permitting only the\n * SNI name with this value. It is only used if TCPForward is non-empty.\n * (the HTTPS mode uses ServeConfig.Web)\n */\n TerminateTLS: z.string().nullish(),\n /**\n * ProxyProtocol indicates whether to send a PROXY protocol header\n * before forwarding the connection to TCPForward.\n * \n * This is only valid if TCPForward is non-empty.\n */\n ProxyProtocol: int64.nullish(),\n});\nexport type TCPPortHandler = z.infer<typeof TCPPortHandlerSchema>;\n\n/** HTTPHandler is either a path or a proxy to serve. */\nexport const HTTPHandlerSchema = z.object({\n /** absolute path to directory or file to serve */\n Path: z.string().nullish(),\n /** http://localhost:3000/, localhost:3030, 3030 */\n Proxy: z.string().nullish(),\n /** plaintext to serve (primarily for testing) */\n Text: z.string().nullish(),\n /** peer capabilities to forward in grant header, e.g. example.com/cap/mon */\n AcceptAppCaps: goSlice(z.string()),\n /**\n * Redirect, if not empty, is the target URL to redirect requests to.\n * By default, we redirect with HTTP 302 (Found) status.\n * If Redirect starts with '<httpcode>:', then we use that status instead.\n * \n * The target URL supports the following expansion variables:\n * - ${HOST}: replaced with the request's Host header value\n * - ${REQUEST_URI}: replaced with the request's full URI (path and query string)\n */\n Redirect: z.string().nullish(),\n});\nexport type HTTPHandler = z.infer<typeof HTTPHandlerSchema>;\n\n/** WebServerConfig describes a web server's configuration. */\nexport const WebServerConfigSchema = z.object({\n /** mountPoint => handler */\n Handlers: goMap(HTTPHandlerSchema),\n});\nexport type WebServerConfig = z.infer<typeof WebServerConfigSchema>;\n\n/**\n * ServiceConfig contains the config information for a single service.\n * it contains a bool to indicate if the service is in Tun mode (L3 forwarding).\n * If the service is not in Tun mode, the service is configured by the L4 forwarding\n * (TCP ports) and/or the L7 forwarding (http handlers) information.\n */\nexport const ServiceConfigSchema = z.object({\n /**\n * TCP are the list of TCP port numbers that tailscaled should handle for\n * the Tailscale IP addresses. (not subnet routers, etc)\n */\n TCP: goMap(TCPPortHandlerSchema),\n /**\n * Web maps from \"$SNI_NAME:$PORT\" to a set of HTTP handlers\n * keyed by mount point (\"/\", \"/foo\", etc)\n */\n Web: goMap(WebServerConfigSchema),\n /** Tun determines if the service should be using L3 forwarding (Tun mode). */\n Tun: z.boolean().nullish(),\n});\nexport type ServiceConfig = z.infer<typeof ServiceConfigSchema>;\n\nconst _ServeConfigRef: z.ZodTypeAny = z.lazy(() => ServeConfigSchema);\n/**\n * ServeConfig is the JSON type stored in the StateStore for\n * StateKey \"_serve/$PROFILE_ID\" as returned by ServeConfigKey.\n */\nexport const ServeConfigSchema = z.object({\n /**\n * TCP are the list of TCP port numbers that tailscaled should handle for\n * the Tailscale IP addresses. (not subnet routers, etc)\n */\n TCP: goMap(TCPPortHandlerSchema),\n /**\n * Web maps from \"$SNI_NAME:$PORT\" to a set of HTTP handlers\n * keyed by mount point (\"/\", \"/foo\", etc)\n */\n Web: goMap(WebServerConfigSchema),\n /**\n * Services maps from service name (in the form \"svc:dns-label\") to a ServiceConfig.\n * Which describes the L3, L4, and L7 forwarding information for the service.\n */\n Services: goMap(ServiceConfigSchema),\n /**\n * AllowFunnel is the set of SNI:port values for which funnel\n * traffic is allowed, from trusted ingress peers.\n */\n AllowFunnel: goMap(z.boolean()),\n /**\n * Foreground is a map of an IPN Bus session ID to an alternate foreground serve config that's valid for the\n * life of that WatchIPNBus session ID. This allows the config to specify ephemeral configs that are used\n * in the CLI's foreground mode to ensure ungraceful shutdowns of either the client or the LocalBackend does not\n * expose ports that users are not aware of. In practice this contains any serve config set via 'tailscale\n * serve' command run without the '--bg' flag. ServeConfig contained by Foreground is not expected itself to contain\n * another Foreground block.\n */\n Foreground: goMap(_ServeConfigRef),\n});\nexport type ServeConfig = z.infer<typeof ServeConfigSchema> & {\n /**\n * ETag is the checksum of the serve config that's populated\n * by the LocalClient through the HTTP ETag header during a\n * GetServeConfig request and is translated to an If-Match header\n * during a SetServeConfig request.\n */\n ETag: string;\n};\n\n/**\n * WhoIsResponse is the JSON type returned by tailscaled debug server's /whois?ip=$IP handler.\n * In successful whois responses, Node and UserProfile are never nil.\n */\nexport const WhoIsResponseSchema = z.object({\n Node: NodeSchema.prefault({}),\n UserProfile: UserProfileSchema.prefault({}),\n /**\n * CapMap is a map of capabilities to their values.\n * See tailcfg.PeerCapMap and tailcfg.PeerCapability for details.\n */\n CapMap: goMap(goSlice(z.unknown())),\n});\nexport type WhoIsResponse = z.infer<typeof WhoIsResponseSchema>;\n\n/** Alias for TailnetStatus for backward compatibility. */\nexport const CurrentTailnetSchema = z.object({\n Name: z.string(),\n MagicDNSSuffix: z.string(),\n MagicDNSEnabled: z.boolean(),\n});\nexport type CurrentTailnet = z.infer<typeof CurrentTailnetSchema>;\n","import { parseJSON, jsonReplacer } from \"./json.js\";\nimport {\n AccessDeniedError,\n ConnectionError,\n DaemonNotRunningError,\n HttpError,\n PeerNotFoundError,\n PreconditionsFailedError,\n errorMessageFromBody,\n} from \"./errors.js\";\nimport { Transport, type TransportOptions } from \"./transport.js\";\nimport {\n ServeConfigSchema,\n StatusSchema,\n WhoIsResponseSchema,\n type ServeConfig,\n type Status,\n type WhoIsResponse,\n} from \"./types.js\";\n\n/**\n * Client for the Tailscale Local API.\n *\n * Connections are reused via HTTP keep-alive.\n */\nexport class Client {\n private readonly transport: Transport;\n\n constructor(opts: TransportOptions = {}) {\n this.transport = new Transport(opts);\n }\n\n private async doRequest(\n method: string,\n path: string,\n body?: Buffer | string,\n headers?: Record<string, string>,\n ): Promise<{ status: number; body: Buffer; headers: Record<string, string | string[] | undefined> }> {\n try {\n return await this.transport.request(method, path, body, headers);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"ECONNREFUSED\") || msg.includes(\"ENOENT\")) {\n throw new DaemonNotRunningError(msg);\n }\n throw new ConnectionError(msg);\n }\n }\n\n private async doRequestNice(\n method: string,\n path: string,\n body?: Buffer | string,\n headers?: Record<string, string>,\n ): Promise<Buffer> {\n const resp = await this.doRequest(method, path, body, headers);\n if (resp.status >= 200 && resp.status < 300) {\n return resp.body;\n }\n\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n\n if (resp.status === 403) throw new AccessDeniedError(msg);\n if (resp.status === 412) throw new PreconditionsFailedError(msg);\n throw new HttpError(resp.status, msg);\n }\n\n private async get200(path: string): Promise<Buffer> {\n return this.doRequestNice(\"GET\", path);\n }\n\n private async post200(path: string, body?: Buffer | string): Promise<Buffer> {\n return this.doRequestNice(\"POST\", path, body);\n }\n\n // --- Status ---\n\n /** Get the current tailscaled status. */\n async status(): Promise<Status> {\n const data = await this.get200(\"/localapi/v0/status\");\n return StatusSchema.parse(parseJSON(data.toString(\"utf-8\")));\n }\n\n /** Get the current tailscaled status without peer information. */\n async statusWithoutPeers(): Promise<Status> {\n const data = await this.get200(\"/localapi/v0/status?peers=false\");\n return StatusSchema.parse(parseJSON(data.toString(\"utf-8\")));\n }\n\n // --- WhoIs ---\n\n private async doWhoIs(params: string): Promise<WhoIsResponse> {\n const resp = await this.doRequest(\n \"GET\",\n `/localapi/v0/whois?${params}`,\n );\n if (resp.status === 404) {\n throw new PeerNotFoundError(params);\n }\n if (resp.status !== 200) {\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n if (resp.status === 403) throw new AccessDeniedError(msg);\n throw new HttpError(resp.status, msg);\n }\n return WhoIsResponseSchema.parse(parseJSON(resp.body.toString(\"utf-8\")));\n }\n\n /** Look up the owner of an IP address or IP:port. */\n async whoIs(remoteAddr: string): Promise<WhoIsResponse> {\n return this.doWhoIs(`addr=${encodeURIComponent(remoteAddr)}`);\n }\n\n /** Look up a peer by node key. */\n async whoIsNodeKey(nodeKey: string): Promise<WhoIsResponse> {\n return this.whoIs(nodeKey);\n }\n\n /** Look up the owner of an IP address with a specific protocol (\"tcp\" or \"udp\"). */\n async whoIsProto(proto: string, remoteAddr: string): Promise<WhoIsResponse> {\n return this.doWhoIs(`proto=${encodeURIComponent(proto)}&addr=${encodeURIComponent(remoteAddr)}`);\n }\n\n // --- Cert ---\n\n /** Get a TLS certificate and private key for the given domain. */\n async certPair(domain: string): Promise<{ cert: Buffer; key: Buffer }> {\n return this.certPairWithValidity(domain, 0);\n }\n\n /** Get a TLS certificate with minimum validity duration (in seconds). */\n async certPairWithValidity(\n domain: string,\n minValiditySecs: number,\n ): Promise<{ cert: Buffer; key: Buffer }> {\n const body = await this.get200(\n `/localapi/v0/cert/${encodeURIComponent(domain)}?type=pair&min_validity=${minValiditySecs}s`,\n );\n // Response is key PEM then cert PEM, separated by \"--\\n--\"\n const delimiter = Buffer.from(\"--\\n--\");\n const pos = body.indexOf(delimiter);\n if (pos === -1) {\n throw new Error(\"unexpected cert response: no delimiter\");\n }\n const split = pos + 3; // include \"--\\n\"\n const key = body.subarray(0, split);\n const cert = body.subarray(split);\n return { cert, key };\n }\n\n // --- Config ---\n\n /**\n * Get the current serve configuration.\n *\n * The returned ServeConfig has its ETag field populated from the\n * HTTP Etag response header.\n */\n async getServeConfig(): Promise<ServeConfig> {\n const resp = await this.doRequest(\"GET\", \"/localapi/v0/serve-config\");\n if (resp.status !== 200) {\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n if (resp.status === 403) throw new AccessDeniedError(msg);\n if (resp.status === 412) throw new PreconditionsFailedError(msg);\n throw new HttpError(resp.status, msg);\n }\n const config = ServeConfigSchema.parse(parseJSON(resp.body.toString(\"utf-8\"))) as ServeConfig;\n config.ETag = (resp.headers[\"etag\"] as string) ?? \"\";\n return config;\n }\n\n /**\n * Set the serve configuration.\n *\n * The ETag field on the config is sent as the If-Match header\n * for conditional updates.\n */\n async setServeConfig(config: ServeConfig): Promise<void> {\n const headers: Record<string, string> = {};\n if (config.ETag) headers[\"If-Match\"] = config.ETag;\n const body = JSON.stringify(config, jsonReplacer);\n await this.doRequestNice(\"POST\", \"/localapi/v0/serve-config\", body, headers);\n }\n\n /** Close the underlying transport and release resources. */\n destroy(): void {\n this.transport.destroy();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,MAAa,eAAe,MAAc,OAAgB,YAAkC;AAC1F,KAAI,OAAO,UAAU,YAAY,SAAS,UAAU,CAAC,OAAO,cAAc,MAAM,IAAI,UAAU,KAAK,QAAQ,OAAO,CAChH,QAAO,OAAO,QAAQ,OAAO;AAE/B,QAAO;;;AAIT,MAAa,aAAa,QAAgB,KAAK,MAAM,KAAK,YAAY;;AAGtE,MAAa,gBAAgB,MAAc,UACzC,OAAO,UAAU,WAAW,KAAK,QAAQ,MAAM,UAAU,CAAC,GAAG;;;;;AClB/D,IAAa,iBAAb,cAAoC,MAAM;CACxC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;AAKhB,IAAa,oBAAb,cAAuC,eAAe;CACpD,YAAY,SAAiB;AAC3B,QAAM,kBAAkB,UAAU;AAClC,OAAK,OAAO;;;;AAKhB,IAAa,2BAAb,cAA8C,eAAe;CAC3D,YAAY,SAAiB;AAC3B,QAAM,yBAAyB,UAAU;AACzC,OAAK,OAAO;;;;AAKhB,IAAa,oBAAb,cAAuC,eAAe;CACpD,YAAY,SAAiB;AAC3B,QAAM,mBAAmB,UAAU;AACnC,OAAK,OAAO;;;;AAKhB,IAAa,kBAAb,cAAqC,eAAe;CAClD,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;AAKhB,IAAa,wBAAb,cAA2C,gBAAgB;CACzD,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;AAKhB,IAAa,YAAb,cAA+B,eAAe;CAC5C,AAAgB;CAEhB,YAAY,QAAgB,SAAiB;AAC3C,QAAM,QAAQ,OAAO,IAAI,UAAU;AACnC,OAAK,OAAO;AACZ,OAAK,SAAS;;;;AAKlB,SAAgB,qBAAqB,MAAkC;AACrE,KAAI;AAEF,SADa,KAAK,MAAM,KAAK,EAChB;SACP;AACN;;;;;;AC3DJ,MAAa,iBAAiB;AAC9B,MAAa,sBAAsB;;AAQnC,SAAgB,oBAA4B;AAC1C,4BAAc,KAAK,SACjB,QAAO;AAGT,QAAO;;;AAIT,eAAsB,uBAA0D;AAC9E,4BAAc,KAAK,SACjB;CAIF,MAAM,SAAS,MAAM,wBAAwB;AAC7C,KAAI,OAAQ,QAAO;AAGnB,QAAO,yBAAyB;;AAGlC,MAAM,qCAAsBA,4BAAS;AAErC,eAAe,yBAA4D;AACzE,KAAI;EACF,MAAM,MAAM,QAAQ,UAAU;AAC9B,MAAI,QAAQ,OAAW,QAAO;EAE9B,MAAM,EAAE,QAAQ,WAAW,MAAM,UAAU,QAAQ;GACjD;GACA;GACA,KAAK;GACL;GACA;GACA;GACD,CAAC;AACF,SAAO,gBAAgB,OAAO;SACxB;AACN;;;;AAKJ,SAAgB,gBAAgB,QAA0C;CACxE,MAAM,SAAS;AACf,MAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,EAAE;EACrC,MAAM,MAAM,KAAK,QAAQ,OAAO;AAChC,MAAI,QAAQ,GAAI;EAChB,MAAM,OAAO,KAAK,MAAM,MAAM,GAAc;EAC5C,MAAM,OAAO,KAAK,QAAQ,IAAI;AAC9B,MAAI,SAAS,GAAI;EACjB,MAAM,UAAU,KAAK,MAAM,GAAG,KAAK;EACnC,MAAM,QAAQ,KAAK,MAAM,OAAO,EAAE;EAClC,MAAM,OAAO,SAAS,SAAS,GAAG;AAClC,MAAI,CAAC,MAAM,KAAK,CACd,QAAO;GAAE;GAAM;GAAO;;;AAM5B,eAAe,wBACb,YAAY,sBACuB;AACnC,KAAI;EAEF,MAAM,UAAU,yDADM,WAAW,UAAU,EACF,QAAQ;EACjD,MAAM,OAAO,SAAS,SAAS,GAAG;AAClC,MAAI,MAAM,KAAK,CAAE,QAAO;AAIxB,SAAO;GAAE;GAAM,QAFE,yDADM,WAAW,iBAAiB,OAAO,EACf,QAAQ,EAC5B,MAAM;GACP;SAChB;AACN;;;;;;;;;ACzEJ,eAAe,oBAAoB,eAA2D;AAC5F,KAAI,cAAe,QAAO;AAC1B,QAAO,sBAAsB;;;;;;;;AAS/B,IAAa,YAAb,MAAuB;CACrB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,YAAY,OAAyB,EAAE,EAAE;AACvC,OAAK,aAAa,KAAK,cAAc,mBAAmB;AACxD,OAAK,gBAAgB,KAAK,iBAAiB;AAG3C,OAAK,QAAQ,IAAIC,UAAK,MAAM;GAC1B,WAAW;GACX,gBAAgB;GACjB,CAAC;;CAGJ,MAAM,QACJ,QACA,MACA,MACA,cAC8E;EAC9E,MAAM,eAAe,MAAM,oBAAoB,KAAK,cAAc;AAElE,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,UAAkC;IACtC,MAAM;IACN,iBAAiB,OAAO,oBAAoB;IAC5C,GAAG;IACJ;AAED,OAAI,aAEF,SAAQ,mBAAmB,SADd,OAAO,KAAK,IAAI,aAAa,QAAQ,CAAC,SAAS,SAAS;GAIvE,MAAM,UAA+B;IACnC;IACA;IACA;IACA,OAAO,KAAK;IACb;AAED,OAAI,cAAc;AAChB,YAAQ,OAAO;AACf,YAAQ,OAAO,aAAa;SAE5B,SAAQ,aAAa,KAAK;GAG5B,MAAM,MAAMA,UAAK,QAAQ,UAAU,QAAQ;IACzC,MAAM,SAAmB,EAAE;AAC3B,QAAI,GAAG,SAAS,UAAkB,OAAO,KAAK,MAAM,CAAC;AACrD,QAAI,GAAG,aAAa;AAClB,aAAQ;MACN,QAAQ,IAAI,cAAc;MAC1B,MAAM,OAAO,OAAO,OAAO;MAC3B,SAAS,IAAI;MACd,CAAC;MACF;AACF,QAAI,GAAG,SAAS,OAAO;KACvB;AAEF,OAAI,GAAG,SAAS,OAAO;AAEvB,OAAI,SAAS,OACX,KAAI,MAAM,KAAK;AAEjB,OAAI,KAAK;IACT;;CAGJ,UAAgB;AACd,OAAK,MAAM,SAAS;;;;;;ACjGxB,MAAM,WAAmC,SACvCC,MAAE,MAAM,KAAK,CAAC,SAAS,CAAC,WAAU,MAAK,KAAK,EAAE,CAAC;AAEjD,MAAM,SAAiC,QACrCA,MAAE,OAAOA,MAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,WAAU,MAAK,KAAK,EAAE,CAAC;AAE7D,MAAM,QAAQA,MAAE,MAAM,CAACA,MAAE,QAAQ,CAAC,KAAK,EAAEA,MAAE,QAAQ,CAAC,CAAC,CAAC,WAAU,MAAK,OAAO,EAAE,CAAC;;;;;;AAO/E,MAAa,iBAAiBA,MAAE,OAAO;CAErC,SAASA,MAAE,QAAQ,CAAC,SAAS;CAE7B,aAAaA,MAAE,QAAQ,CAAC,SAAS;CAEjC,MAAMA,MAAE,QAAQ,CAAC,SAAS;CAQ1B,UAAUA,MAAE,QAAQ,CAAC,SAAS;CAK9B,UAAUA,MAAE,QAAQ,CAAC,SAAS;CAC9B,WAAWA,MAAE,QAAQ,CAAC,SAAS;CAU/B,UAAU,MAAM,SAAS;CAC1B,CAAC;;;;;;;;;AAWF,MAAa,mBAAmBA,MAAE,OAAO;CACvC,IAAIA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAC1B,WAAWA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAEjC,UAAUA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAKhC,SAASA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAE/B,IAAIA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAC1B,QAAQ,MAAM,QAAQ,GAAG;CAKzB,iBAAiB,MAAM,SAAS;CAEhC,cAAc,QAAQA,MAAE,QAAQ,CAAC;CAEjC,YAAY,QAAQA,MAAE,QAAQ,CAAC;CAK/B,MAAM,QAAQA,MAAE,QAAQ,CAAC;CAMzB,eAAe,QAAQA,MAAE,QAAQ,CAAC;CAElC,OAAO,QAAQA,MAAE,QAAQ,CAAC;CAE1B,SAASA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAE/B,OAAOA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAE7B,WAAWA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CACjC,SAAS,MAAM,QAAQ,GAAG;CAC1B,SAAS,MAAM,QAAQ,GAAG;CAE1B,SAASA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAE/B,WAAWA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAEjC,UAAUA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAEhC,eAAeA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAErC,QAAQA,MAAE,SAAS,CAAC,QAAQ,MAAM;CAElC,UAAUA,MAAE,SAAS,CAAC,QAAQ,MAAM;CAEpC,gBAAgBA,MAAE,SAAS,CAAC,QAAQ,MAAM;CAQ1C,QAAQA,MAAE,SAAS,CAAC,QAAQ,MAAM;CAElC,YAAY,QAAQA,MAAE,QAAQ,CAAC;CAE/B,gBAAgB,MAAM,QAAQ,GAAG;CAEjC,qBAAqBA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAa3C,cAAc,QAAQA,MAAE,QAAQ,CAAC;CAEjC,QAAQ,MAAM,QAAQA,MAAE,SAAS,CAAC,CAAC;CAEnC,aAAa,QAAQA,MAAE,QAAQ,CAAC;CAOhC,YAAYA,MAAE,SAAS,CAAC,SAAS;CAKjC,cAAcA,MAAE,SAAS,CAAC,QAAQ,MAAM;CAKxC,aAAaA,MAAE,SAAS,CAAC,QAAQ,MAAM;CAKvC,UAAUA,MAAE,SAAS,CAAC,QAAQ,MAAM;CAMpC,SAASA,MAAE,SAAS,CAAC,SAAS;CAK9B,WAAWA,MAAE,QAAQ,CAAC,SAAS;CAC/B,UAAU,eAAe,SAAS;CACnC,CAAC;;AAIF,MAAa,uBAAuBA,MAAE,OAAO;CAE3C,IAAIA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAE1B,QAAQA,MAAE,SAAS,CAAC,QAAQ,MAAM;CAElC,cAAc,QAAQA,MAAE,QAAQ,CAAC;CAClC,CAAC;;AAIF,MAAa,sBAAsBA,MAAE,OAAO;CAE1C,MAAMA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAQ5B,gBAAgBA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAMtC,iBAAiBA,MAAE,SAAS,CAAC,QAAQ,MAAM;CAC5C,CAAC;;;;;;AAQF,MAAa,oBAAoBA,MAAE,OAAO;CACxC,IAAI,MAAM,QAAQ,GAAG;CAErB,WAAWA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAEjC,aAAaA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CACnC,eAAeA,MAAE,QAAQ,CAAC,SAAS;CACpC,CAAC;;;;;;;AASF,MAAa,sBAAsBA,MAAE,OAAO;CAE1C,eAAeA,MAAE,SAAS,CAAC,SAAS;CAMpC,eAAeA,MAAE,QAAQ,CAAC,SAAS;CAMnC,sBAAsBA,MAAE,SAAS,CAAC,SAAS;CAO3C,QAAQA,MAAE,SAAS,CAAC,SAAS;CAK7B,WAAWA,MAAE,QAAQ,CAAC,SAAS;CAE/B,YAAYA,MAAE,QAAQ,CAAC,SAAS;CACjC,CAAC;;AAIF,MAAa,eAAeA,MAAE,OAAO;CAEnC,SAASA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAK/B,KAAKA,MAAE,SAAS,CAAC,QAAQ,MAAM;CAM/B,cAAcA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAEpC,aAAaA,MAAE,SAAS,CAAC,SAAS;CAElC,SAASA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAE/B,cAAc,QAAQA,MAAE,QAAQ,CAAC;CACjC,MAAM,iBAAiB,SAAS,EAAE,CAAC;CAKnC,gBAAgB,qBAAqB,SAAS;CAM9C,QAAQ,QAAQA,MAAE,QAAQ,CAAC;CAM3B,gBAAgBA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAKtC,gBAAgB,oBAAoB,SAAS;CAQ7C,aAAa,QAAQA,MAAE,QAAQ,CAAC;CAEhC,MAAM,MAAM,iBAAiB;CAK7B,MAAM,MAAM,kBAAkB;CAM9B,eAAe,oBAAoB,SAAS;CAC7C,CAAC;;AAIF,MAAa,gBAAgBA,MAAE,OAAO;CAgBpC,OAAOA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAM7B,MAAMA,MAAE,QAAQ,CAAC,QAAQ,EAAE;CAK3B,aAAaA,MAAE,QAAQ,CAAC,SAAS;CAClC,CAAC;;AAIF,MAAa,gBAAgBA,MAAE,OAAO;CAKpC,uBAAuBA,MAAE,SAAS,CAAC,SAAS;CAE5C,aAAaA,MAAE,SAAS,CAAC,SAAS;CAKlC,WAAWA,MAAE,SAAS,CAAC,SAAS;CAEhC,YAAYA,MAAE,SAAS,CAAC,SAAS;CAKjC,eAAeA,MAAE,SAAS,CAAC,SAAS;CAKpC,aAAaA,MAAE,SAAS,CAAC,SAAS;CAKlC,MAAMA,MAAE,SAAS,CAAC,SAAS;CAK3B,KAAKA,MAAE,SAAS,CAAC,SAAS;CAK1B,KAAKA,MAAE,SAAS,CAAC,SAAS;CAU1B,eAAe,MAAM,SAAS;CAE9B,UAAUA,MAAE,QAAQ,CAAC,SAAS;CAW9B,aAAa,MAAMA,MAAE,QAAQ,CAAC;CAS9B,cAAcA,MAAE,QAAQ,CAAC,SAAS;CACnC,CAAC;;;;;;AAQF,MAAa,gBAAgBA,MAAE,OAAO;CAOpC,cAAcA,MAAE,QAAQ,CAAC,SAAS;CAKlC,QAAQA,MAAE,QAAQ,CAAC,SAAS;CAK5B,OAAO,MAAM,SAAS;CAKtB,iBAAiB,MAAM,SAAS;CAOhC,cAAc,MAAM,SAAS;CAK7B,iBAAiBA,MAAE,QAAQ,CAAC,SAAS;CACtC,CAAC;;;;;;;AASF,MAAa,iBAAiBA,MAAE,OAAO;CAErC,YAAYA,MAAE,QAAQ,CAAC,SAAS;CAEhC,eAAeA,MAAE,QAAQ,CAAC,SAAS;CAEnC,cAAcA,MAAE,QAAQ,CAAC,SAAS;CAElC,IAAIA,MAAE,QAAQ,CAAC,SAAS;CAaxB,WAAWA,MAAE,QAAQ,CAAC,SAAS;CAE/B,WAAWA,MAAE,SAAS,CAAC,SAAS;CAEhC,KAAKA,MAAE,QAAQ,CAAC,SAAS;CAEzB,QAAQA,MAAE,QAAQ,CAAC,SAAS;CAE5B,eAAeA,MAAE,QAAQ,CAAC,SAAS;CAEnC,gBAAgBA,MAAE,QAAQ,CAAC,SAAS;CAEpC,KAAKA,MAAE,QAAQ,CAAC,SAAS;CAEzB,SAASA,MAAE,SAAS,CAAC,SAAS;CAE9B,SAASA,MAAE,QAAQ,CAAC,SAAS;CAE7B,aAAaA,MAAE,QAAQ,CAAC,SAAS;CAEjC,iBAAiBA,MAAE,QAAQ,CAAC,SAAS;CAErC,UAAUA,MAAE,QAAQ,CAAC,SAAS;CAE9B,WAAWA,MAAE,SAAS,CAAC,SAAS;CAEhC,YAAYA,MAAE,SAAS,CAAC,SAAS;CAEjC,iBAAiBA,MAAE,SAAS,CAAC,SAAS;CAUtC,aAAaA,MAAE,SAAS,CAAC,SAAS;CAElC,gBAAgBA,MAAE,SAAS,CAAC,SAAS;CAErC,cAAcA,MAAE,SAAS,CAAC,SAAS;CAEnC,SAASA,MAAE,QAAQ,CAAC,SAAS;CAE7B,QAAQA,MAAE,QAAQ,CAAC,SAAS;CAE5B,WAAWA,MAAE,QAAQ,CAAC,SAAS;CAE/B,WAAWA,MAAE,QAAQ,CAAC,SAAS;CAE/B,aAAa,QAAQA,MAAE,QAAQ,CAAC;CAEhC,aAAa,QAAQA,MAAE,QAAQ,CAAC;CAEhC,SAAS,QAAQA,MAAE,QAAQ,CAAC;CAE5B,UAAU,QAAQ,cAAc;CAChC,SAAS,cAAc,SAAS;CAEhC,aAAa,QAAQA,MAAE,QAAQ,CAAC;CAChC,OAAOA,MAAE,QAAQ,CAAC,SAAS;CAE3B,WAAWA,MAAE,SAAS,CAAC,SAAS;CAEhC,iBAAiBA,MAAE,SAAS,CAAC,SAAS;CAEtC,cAAcA,MAAE,SAAS,CAAC,SAAS;CAEnC,cAAcA,MAAE,QAAQ,CAAC,SAAS;CAElC,YAAYA,MAAE,QAAQ,CAAC,SAAS;CAMhC,UAAU,eAAe,SAAS;CAElC,KAAK,cAAc,SAAS;CAQ5B,gBAAgBA,MAAE,SAAS,CAAC,SAAS;CACtC,CAAC;;AAIF,MAAa,iBAAiBA,MAAE,OAAO;CAcrC,MAAMA,MAAE,QAAQ,CAAC,SAAS;CAW1B,qBAAqB,QAAQA,MAAE,QAAQ,CAAC;CAOxC,iBAAiBA,MAAE,SAAS,CAAC,SAAS;CACvC,CAAC;;AAIF,MAAa,aAAaA,MAAE,OAAO;CACjC,IAAI,MAAM,QAAQ,GAAG;CACrB,UAAUA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAOhC,MAAMA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAM5B,MAAM,MAAM,QAAQ,GAAG;CAEvB,QAAQ,MAAM,SAAS;CACvB,KAAKA,MAAE,QAAQ,CAAC,QAAQ,GAAG;CAE3B,WAAWA,MAAE,QAAQ,CAAC,SAAS;CAC/B,cAAcA,MAAE,QAAQ,CAAC,SAAS;CAClC,SAASA,MAAE,QAAQ,CAAC,SAAS;CAC7B,UAAUA,MAAE,QAAQ,CAAC,SAAS;CAE9B,WAAW,QAAQA,MAAE,QAAQ,CAAC;CAQ9B,YAAY,QAAQA,MAAE,QAAQ,CAAC;CAE/B,WAAW,QAAQA,MAAE,QAAQ,CAAC;CAa9B,MAAMA,MAAE,QAAQ,CAAC,SAAS;CAQ1B,UAAU,MAAM,SAAS;CACzB,UAAU,eAAe,SAAS;CAClC,SAASA,MAAE,QAAQ,CAAC,SAAS;CAE7B,KAAK,MAAM,SAAS;CAUpB,MAAM,QAAQA,MAAE,QAAQ,CAAC;CAOzB,eAAe,QAAQA,MAAE,QAAQ,CAAC;CAOlC,UAAUA,MAAE,QAAQ,CAAC,SAAS;CAM9B,QAAQA,MAAE,SAAS,CAAC,SAAS;CAE7B,mBAAmBA,MAAE,SAAS,CAAC,SAAS;CAUxC,cAAc,QAAQA,MAAE,QAAQ,CAAC;CAuBjC,QAAQ,MAAM,QAAQA,MAAE,SAAS,CAAC,CAAC;CASnC,qBAAqBA,MAAE,SAAS,CAAC,SAAS;CAE1C,cAAcA,MAAE,QAAQ,CAAC,SAAS;CAElC,sBAAsBA,MAAE,QAAQ,CAAC,SAAS;CAE1C,qBAAqBA,MAAE,QAAQ,CAAC,SAAS;CAOzC,SAASA,MAAE,SAAS,CAAC,SAAS;CAgB9B,+BAA+BA,MAAE,QAAQ,CAAC,SAAS;CAgBnD,+BAA+BA,MAAE,QAAQ,CAAC,SAAS;CAMnD,iBAAiBA,MAAE,SAAS,CAAC,SAAS;CAMtC,UAAUA,MAAE,SAAS,CAAC,SAAS;CAK/B,sBAAsB,QAAQ,eAAe;CAC9C,CAAC;;;;;AAOF,MAAa,uBAAuBA,MAAE,OAAO;CAO3C,OAAOA,MAAE,SAAS,CAAC,SAAS;CAO5B,MAAMA,MAAE,SAAS,CAAC,SAAS;CAQ3B,YAAYA,MAAE,QAAQ,CAAC,SAAS;CAOhC,cAAcA,MAAE,QAAQ,CAAC,SAAS;CAOlC,eAAe,MAAM,SAAS;CAC/B,CAAC;;AAIF,MAAa,oBAAoBA,MAAE,OAAO;CAExC,MAAMA,MAAE,QAAQ,CAAC,SAAS;CAE1B,OAAOA,MAAE,QAAQ,CAAC,SAAS;CAE3B,MAAMA,MAAE,QAAQ,CAAC,SAAS;CAE1B,eAAe,QAAQA,MAAE,QAAQ,CAAC;CAUlC,UAAUA,MAAE,QAAQ,CAAC,SAAS;CAC/B,CAAC;;AAIF,MAAa,wBAAwBA,MAAE,OAAO,EAE5C,UAAU,MAAM,kBAAkB,EACnC,CAAC;;;;;;;AASF,MAAa,sBAAsBA,MAAE,OAAO;CAK1C,KAAK,MAAM,qBAAqB;CAKhC,KAAK,MAAM,sBAAsB;CAEjC,KAAKA,MAAE,SAAS,CAAC,SAAS;CAC3B,CAAC;AAGF,MAAM,kBAAgCA,MAAE,WAAW,kBAAkB;;;;;AAKrE,MAAa,oBAAoBA,MAAE,OAAO;CAKxC,KAAK,MAAM,qBAAqB;CAKhC,KAAK,MAAM,sBAAsB;CAKjC,UAAU,MAAM,oBAAoB;CAKpC,aAAa,MAAMA,MAAE,SAAS,CAAC;CAS/B,YAAY,MAAM,gBAAgB;CACnC,CAAC;;;;;AAeF,MAAa,sBAAsBA,MAAE,OAAO;CAC1C,MAAM,WAAW,SAAS,EAAE,CAAC;CAC7B,aAAa,kBAAkB,SAAS,EAAE,CAAC;CAK3C,QAAQ,MAAM,QAAQA,MAAE,SAAS,CAAC,CAAC;CACpC,CAAC;;AAIF,MAAa,uBAAuBA,MAAE,OAAO;CAC3C,MAAMA,MAAE,QAAQ;CAChB,gBAAgBA,MAAE,QAAQ;CAC1B,iBAAiBA,MAAE,SAAS;CAC7B,CAAC;;;;;;;;;ACt9BF,IAAa,SAAb,MAAoB;CAClB,AAAiB;CAEjB,YAAY,OAAyB,EAAE,EAAE;AACvC,OAAK,YAAY,IAAI,UAAU,KAAK;;CAGtC,MAAc,UACZ,QACA,MACA,MACA,SACmG;AACnG,MAAI;AACF,UAAO,MAAM,KAAK,UAAU,QAAQ,QAAQ,MAAM,MAAM,QAAQ;WACzD,KAAc;GACrB,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,OAAI,IAAI,SAAS,eAAe,IAAI,IAAI,SAAS,SAAS,CACxD,OAAM,IAAI,sBAAsB,IAAI;AAEtC,SAAM,IAAI,gBAAgB,IAAI;;;CAIlC,MAAc,cACZ,QACA,MACA,MACA,SACiB;EACjB,MAAM,OAAO,MAAM,KAAK,UAAU,QAAQ,MAAM,MAAM,QAAQ;AAC9D,MAAI,KAAK,UAAU,OAAO,KAAK,SAAS,IACtC,QAAO,KAAK;EAGd,MAAM,UAAU,KAAK,KAAK,SAAS,QAAQ;EAC3C,MAAM,MAAM,qBAAqB,QAAQ,IAAI;AAE7C,MAAI,KAAK,WAAW,IAAK,OAAM,IAAI,kBAAkB,IAAI;AACzD,MAAI,KAAK,WAAW,IAAK,OAAM,IAAI,yBAAyB,IAAI;AAChE,QAAM,IAAI,UAAU,KAAK,QAAQ,IAAI;;CAGvC,MAAc,OAAO,MAA+B;AAClD,SAAO,KAAK,cAAc,OAAO,KAAK;;CAGxC,MAAc,QAAQ,MAAc,MAAyC;AAC3E,SAAO,KAAK,cAAc,QAAQ,MAAM,KAAK;;;CAM/C,MAAM,SAA0B;EAC9B,MAAM,OAAO,MAAM,KAAK,OAAO,sBAAsB;AACrD,SAAO,aAAa,MAAM,UAAU,KAAK,SAAS,QAAQ,CAAC,CAAC;;;CAI9D,MAAM,qBAAsC;EAC1C,MAAM,OAAO,MAAM,KAAK,OAAO,kCAAkC;AACjE,SAAO,aAAa,MAAM,UAAU,KAAK,SAAS,QAAQ,CAAC,CAAC;;CAK9D,MAAc,QAAQ,QAAwC;EAC5D,MAAM,OAAO,MAAM,KAAK,UACtB,OACA,sBAAsB,SACvB;AACD,MAAI,KAAK,WAAW,IAClB,OAAM,IAAI,kBAAkB,OAAO;AAErC,MAAI,KAAK,WAAW,KAAK;GACvB,MAAM,UAAU,KAAK,KAAK,SAAS,QAAQ;GAC3C,MAAM,MAAM,qBAAqB,QAAQ,IAAI;AAC7C,OAAI,KAAK,WAAW,IAAK,OAAM,IAAI,kBAAkB,IAAI;AACzD,SAAM,IAAI,UAAU,KAAK,QAAQ,IAAI;;AAEvC,SAAO,oBAAoB,MAAM,UAAU,KAAK,KAAK,SAAS,QAAQ,CAAC,CAAC;;;CAI1E,MAAM,MAAM,YAA4C;AACtD,SAAO,KAAK,QAAQ,QAAQ,mBAAmB,WAAW,GAAG;;;CAI/D,MAAM,aAAa,SAAyC;AAC1D,SAAO,KAAK,MAAM,QAAQ;;;CAI5B,MAAM,WAAW,OAAe,YAA4C;AAC1E,SAAO,KAAK,QAAQ,SAAS,mBAAmB,MAAM,CAAC,QAAQ,mBAAmB,WAAW,GAAG;;;CAMlG,MAAM,SAAS,QAAwD;AACrE,SAAO,KAAK,qBAAqB,QAAQ,EAAE;;;CAI7C,MAAM,qBACJ,QACA,iBACwC;EACxC,MAAM,OAAO,MAAM,KAAK,OACtB,qBAAqB,mBAAmB,OAAO,CAAC,0BAA0B,gBAAgB,GAC3F;EAED,MAAM,YAAY,OAAO,KAAK,SAAS;EACvC,MAAM,MAAM,KAAK,QAAQ,UAAU;AACnC,MAAI,QAAQ,GACV,OAAM,IAAI,MAAM,yCAAyC;EAE3D,MAAM,QAAQ,MAAM;EACpB,MAAM,MAAM,KAAK,SAAS,GAAG,MAAM;AAEnC,SAAO;GAAE,MADI,KAAK,SAAS,MAAM;GAClB;GAAK;;;;;;;;CAWtB,MAAM,iBAAuC;EAC3C,MAAM,OAAO,MAAM,KAAK,UAAU,OAAO,4BAA4B;AACrE,MAAI,KAAK,WAAW,KAAK;GACvB,MAAM,UAAU,KAAK,KAAK,SAAS,QAAQ;GAC3C,MAAM,MAAM,qBAAqB,QAAQ,IAAI;AAC7C,OAAI,KAAK,WAAW,IAAK,OAAM,IAAI,kBAAkB,IAAI;AACzD,OAAI,KAAK,WAAW,IAAK,OAAM,IAAI,yBAAyB,IAAI;AAChE,SAAM,IAAI,UAAU,KAAK,QAAQ,IAAI;;EAEvC,MAAM,SAAS,kBAAkB,MAAM,UAAU,KAAK,KAAK,SAAS,QAAQ,CAAC,CAAC;AAC9E,SAAO,OAAQ,KAAK,QAAQ,WAAsB;AAClD,SAAO;;;;;;;;CAST,MAAM,eAAe,QAAoC;EACvD,MAAM,UAAkC,EAAE;AAC1C,MAAI,OAAO,KAAM,SAAQ,cAAc,OAAO;EAC9C,MAAM,OAAO,KAAK,UAAU,QAAQ,aAAa;AACjD,QAAM,KAAK,cAAc,QAAQ,6BAA6B,MAAM,QAAQ;;;CAI9E,UAAgB;AACd,OAAK,UAAU,SAAS"}
|
package/dist/index.d.cts
CHANGED
|
@@ -106,7 +106,7 @@ declare const StatusSchema: z.ZodObject<{
|
|
|
106
106
|
HaveNodeKey: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
107
107
|
AuthURL: z.ZodDefault<z.ZodString>;
|
|
108
108
|
TailscaleIPs: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>, z.ZodTransform<string[], string[] | null | undefined>>;
|
|
109
|
-
Self: z.
|
|
109
|
+
Self: z.ZodPrefault<z.ZodObject<{
|
|
110
110
|
ID: z.ZodDefault<z.ZodString>;
|
|
111
111
|
PublicKey: z.ZodDefault<z.ZodString>;
|
|
112
112
|
HostName: z.ZodDefault<z.ZodString>;
|
|
@@ -153,7 +153,7 @@ declare const StatusSchema: z.ZodObject<{
|
|
|
153
153
|
Longitude: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
154
154
|
Priority: z.ZodOptional<z.ZodNullable<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodBigInt]>, z.ZodTransform<bigint, number | bigint>>>>;
|
|
155
155
|
}, z.core.$strip>>>;
|
|
156
|
-
}, z.core.$strip
|
|
156
|
+
}, z.core.$strip>>;
|
|
157
157
|
ExitNodeStatus: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
158
158
|
ID: z.ZodDefault<z.ZodString>;
|
|
159
159
|
Online: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -508,7 +508,7 @@ type ServeConfig = z.infer<typeof ServeConfigSchema> & {
|
|
|
508
508
|
* In successful whois responses, Node and UserProfile are never nil.
|
|
509
509
|
*/
|
|
510
510
|
declare const WhoIsResponseSchema: z.ZodObject<{
|
|
511
|
-
Node: z.
|
|
511
|
+
Node: z.ZodPrefault<z.ZodObject<{
|
|
512
512
|
ID: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodBigInt]>, z.ZodTransform<bigint, number | bigint>>>;
|
|
513
513
|
StableID: z.ZodDefault<z.ZodString>;
|
|
514
514
|
Name: z.ZodDefault<z.ZodString>;
|
|
@@ -639,13 +639,13 @@ declare const WhoIsResponseSchema: z.ZodObject<{
|
|
|
639
639
|
Addr?: string | null | undefined;
|
|
640
640
|
UseWithExitNode?: boolean | null | undefined;
|
|
641
641
|
}[] | null | undefined>>;
|
|
642
|
-
}, z.core.$strip
|
|
643
|
-
UserProfile: z.
|
|
642
|
+
}, z.core.$strip>>;
|
|
643
|
+
UserProfile: z.ZodPrefault<z.ZodObject<{
|
|
644
644
|
ID: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodBigInt]>, z.ZodTransform<bigint, number | bigint>>>;
|
|
645
645
|
LoginName: z.ZodDefault<z.ZodString>;
|
|
646
646
|
DisplayName: z.ZodDefault<z.ZodString>;
|
|
647
647
|
ProfilePicURL: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
648
|
-
}, z.core.$strip
|
|
648
|
+
}, z.core.$strip>>;
|
|
649
649
|
CapMap: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodUnknown>>>, z.ZodTransform<unknown[], unknown[] | null | undefined>>>>>, z.ZodTransform<Record<string, unknown[]>, Record<string, unknown[]> | null | undefined>>;
|
|
650
650
|
}, z.core.$strip>;
|
|
651
651
|
type WhoIsResponse = z.infer<typeof WhoIsResponseSchema>;
|
package/dist/index.d.mts
CHANGED
|
@@ -106,7 +106,7 @@ declare const StatusSchema: z.ZodObject<{
|
|
|
106
106
|
HaveNodeKey: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
107
107
|
AuthURL: z.ZodDefault<z.ZodString>;
|
|
108
108
|
TailscaleIPs: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>, z.ZodTransform<string[], string[] | null | undefined>>;
|
|
109
|
-
Self: z.
|
|
109
|
+
Self: z.ZodPrefault<z.ZodObject<{
|
|
110
110
|
ID: z.ZodDefault<z.ZodString>;
|
|
111
111
|
PublicKey: z.ZodDefault<z.ZodString>;
|
|
112
112
|
HostName: z.ZodDefault<z.ZodString>;
|
|
@@ -153,7 +153,7 @@ declare const StatusSchema: z.ZodObject<{
|
|
|
153
153
|
Longitude: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
154
154
|
Priority: z.ZodOptional<z.ZodNullable<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodBigInt]>, z.ZodTransform<bigint, number | bigint>>>>;
|
|
155
155
|
}, z.core.$strip>>>;
|
|
156
|
-
}, z.core.$strip
|
|
156
|
+
}, z.core.$strip>>;
|
|
157
157
|
ExitNodeStatus: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
158
158
|
ID: z.ZodDefault<z.ZodString>;
|
|
159
159
|
Online: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -508,7 +508,7 @@ type ServeConfig = z.infer<typeof ServeConfigSchema> & {
|
|
|
508
508
|
* In successful whois responses, Node and UserProfile are never nil.
|
|
509
509
|
*/
|
|
510
510
|
declare const WhoIsResponseSchema: z.ZodObject<{
|
|
511
|
-
Node: z.
|
|
511
|
+
Node: z.ZodPrefault<z.ZodObject<{
|
|
512
512
|
ID: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodBigInt]>, z.ZodTransform<bigint, number | bigint>>>;
|
|
513
513
|
StableID: z.ZodDefault<z.ZodString>;
|
|
514
514
|
Name: z.ZodDefault<z.ZodString>;
|
|
@@ -639,13 +639,13 @@ declare const WhoIsResponseSchema: z.ZodObject<{
|
|
|
639
639
|
Addr?: string | null | undefined;
|
|
640
640
|
UseWithExitNode?: boolean | null | undefined;
|
|
641
641
|
}[] | null | undefined>>;
|
|
642
|
-
}, z.core.$strip
|
|
643
|
-
UserProfile: z.
|
|
642
|
+
}, z.core.$strip>>;
|
|
643
|
+
UserProfile: z.ZodPrefault<z.ZodObject<{
|
|
644
644
|
ID: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodBigInt]>, z.ZodTransform<bigint, number | bigint>>>;
|
|
645
645
|
LoginName: z.ZodDefault<z.ZodString>;
|
|
646
646
|
DisplayName: z.ZodDefault<z.ZodString>;
|
|
647
647
|
ProfilePicURL: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
648
|
-
}, z.core.$strip
|
|
648
|
+
}, z.core.$strip>>;
|
|
649
649
|
CapMap: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodUnknown>>>, z.ZodTransform<unknown[], unknown[] | null | undefined>>>>>, z.ZodTransform<Record<string, unknown[]>, Record<string, unknown[]> | null | undefined>>;
|
|
650
650
|
}, z.core.$strip>;
|
|
651
651
|
type WhoIsResponse = z.infer<typeof WhoIsResponseSchema>;
|
package/dist/index.mjs
CHANGED
|
@@ -325,7 +325,7 @@ const StatusSchema = z.object({
|
|
|
325
325
|
HaveNodeKey: z.boolean().nullish(),
|
|
326
326
|
AuthURL: z.string().default(""),
|
|
327
327
|
TailscaleIPs: goSlice(z.string()),
|
|
328
|
-
Self: PeerStatusSchema.
|
|
328
|
+
Self: PeerStatusSchema.prefault({}),
|
|
329
329
|
ExitNodeStatus: ExitNodeStatusSchema.nullish(),
|
|
330
330
|
Health: goSlice(z.string()),
|
|
331
331
|
MagicDNSSuffix: z.string().default(""),
|
|
@@ -512,8 +512,8 @@ const ServeConfigSchema = z.object({
|
|
|
512
512
|
* In successful whois responses, Node and UserProfile are never nil.
|
|
513
513
|
*/
|
|
514
514
|
const WhoIsResponseSchema = z.object({
|
|
515
|
-
Node: NodeSchema.
|
|
516
|
-
UserProfile: UserProfileSchema.
|
|
515
|
+
Node: NodeSchema.prefault({}),
|
|
516
|
+
UserProfile: UserProfileSchema.prefault({}),
|
|
517
517
|
CapMap: goMap(goSlice(z.unknown()))
|
|
518
518
|
});
|
|
519
519
|
/** Alias for TailnetStatus for backward compatibility. */
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../ts/src/json.ts","../ts/src/errors.ts","../ts/src/safesocket.ts","../ts/src/transport.ts","../ts/src/types.ts","../ts/src/client.ts"],"sourcesContent":["declare global {\n interface JSON {\n rawJSON(value: string): unknown;\n }\n}\n\n/** JSON reviver that converts large integers to BigInt to avoid precision loss. */\nexport const jsonReviver = (_key: string, value: unknown, context?: { source?: string }) => {\n if (typeof value === \"number\" && context?.source && !Number.isSafeInteger(value) && /^-?\\d+$/.test(context.source)) {\n return BigInt(context.source);\n }\n return value;\n};\n\n/** Parse a JSON string using {@link jsonReviver} for BigInt-safe integer handling. */\nexport const parseJSON = (str: string) => JSON.parse(str, jsonReviver);\n\n/** JSON replacer that serializes BigInt values as raw JSON numbers. */\nexport const jsonReplacer = (_key: string, value: unknown) =>\n typeof value === \"bigint\" ? JSON.rawJSON(value.toString()) : value;\n","/** Base error for all Tailscale Local API errors. */\nexport class TailscaleError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TailscaleError\";\n }\n}\n\n/** Raised when the server returns HTTP 403. */\nexport class AccessDeniedError extends TailscaleError {\n constructor(message: string) {\n super(`Access denied: ${message}`);\n this.name = \"AccessDeniedError\";\n }\n}\n\n/** Raised when the server returns HTTP 412. */\nexport class PreconditionsFailedError extends TailscaleError {\n constructor(message: string) {\n super(`Preconditions failed: ${message}`);\n this.name = \"PreconditionsFailedError\";\n }\n}\n\n/** Raised when a WhoIs lookup returns HTTP 404. */\nexport class PeerNotFoundError extends TailscaleError {\n constructor(message: string) {\n super(`Peer not found: ${message}`);\n this.name = \"PeerNotFoundError\";\n }\n}\n\n/** Raised when the connection to tailscaled fails. */\nexport class ConnectionError extends TailscaleError {\n constructor(message: string) {\n super(message);\n this.name = \"ConnectionError\";\n }\n}\n\n/** Raised when tailscaled is not running. */\nexport class DaemonNotRunningError extends ConnectionError {\n constructor(message: string) {\n super(message);\n this.name = \"DaemonNotRunningError\";\n }\n}\n\n/** Raised for unexpected HTTP status codes. */\nexport class HttpError extends TailscaleError {\n public readonly status: number;\n\n constructor(status: number, message: string) {\n super(`HTTP ${status}: ${message}`);\n this.name = \"HttpError\";\n this.status = status;\n }\n}\n\n/** Extract error message from a JSON body like Go's errorMessageFromBody. */\nexport function errorMessageFromBody(body: string): string | undefined {\n try {\n const data = JSON.parse(body);\n return data?.error;\n } catch {\n return undefined;\n }\n}\n","import { execFile } from \"node:child_process\";\nimport { readFile, readlink } from \"node:fs/promises\";\nimport { platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nexport const LOCAL_API_HOST = \"local-tailscaled.sock\";\nexport const CURRENT_CAP_VERSION = 131;\n\nexport interface PortAndToken {\n port: number;\n token: string;\n}\n\n/** Return the default socket path for the current platform. */\nexport function defaultSocketPath(): string {\n if (platform() === \"darwin\") {\n return \"/var/run/tailscaled.socket\";\n }\n // Linux and other Unix\n return \"/var/run/tailscale/tailscaled.sock\";\n}\n\n/** Attempt to discover macOS TCP port and token for tailscaled. */\nexport async function localTcpPortAndToken(): Promise<PortAndToken | undefined> {\n if (platform() !== \"darwin\") {\n return undefined;\n }\n\n // Try lsof method first (macOS GUI app)\n const result = await readMacosSameUserProof();\n if (result) return result;\n\n // Try filesystem method (macOS system extension)\n return readMacsysSameUserProof();\n}\n\nconst execFileP = promisify(execFile);\n\nasync function readMacosSameUserProof(): Promise<PortAndToken | undefined> {\n try {\n const uid = process.getuid?.();\n if (uid === undefined) return undefined;\n\n const { stdout: output } = await execFileP(\"lsof\", [\n \"-n\",\n \"-a\",\n `-u${uid}`,\n \"-c\",\n \"IPNExtension\",\n \"-F\",\n ]);\n return parseLsofOutput(output);\n } catch {\n return undefined;\n }\n}\n\n/** Parse lsof -F output looking for sameuserproof-PORT-TOKEN. */\nexport function parseLsofOutput(output: string): PortAndToken | undefined {\n const needle = \".tailscale.ipn.macos/sameuserproof-\";\n for (const line of output.split(\"\\n\")) {\n const idx = line.indexOf(needle);\n if (idx === -1) continue;\n const rest = line.slice(idx + needle.length);\n const dash = rest.indexOf(\"-\");\n if (dash === -1) continue;\n const portStr = rest.slice(0, dash);\n const token = rest.slice(dash + 1);\n const port = parseInt(portStr, 10);\n if (!isNaN(port)) {\n return { port, token };\n }\n }\n return undefined;\n}\n\nasync function readMacsysSameUserProof(\n sharedDir = \"/Library/Tailscale\",\n): Promise<PortAndToken | undefined> {\n try {\n const portPath = join(sharedDir, \"ipnport\");\n const portStr = await readlink(portPath, \"utf-8\");\n const port = parseInt(portStr, 10);\n if (isNaN(port)) return undefined;\n const tokenPath = join(sharedDir, `sameuserproof-${port}`);\n const tokenRaw = await readFile(tokenPath, \"utf-8\");\n const token = tokenRaw.trim();\n return { port, token };\n } catch {\n return undefined;\n }\n}\n","import * as http from \"node:http\";\nimport {\n CURRENT_CAP_VERSION,\n LOCAL_API_HOST,\n type PortAndToken,\n defaultSocketPath,\n localTcpPortAndToken,\n} from \"./safesocket.js\";\n\nexport interface TransportOptions {\n socketPath?: string;\n useSocketOnly?: boolean;\n}\n\n/**\n * Discover TCP port and token for this request.\n */\nasync function resolvePortAndToken(useSocketOnly: boolean): Promise<PortAndToken | undefined> {\n if (useSocketOnly) return undefined;\n return localTcpPortAndToken();\n}\n\n/**\n * HTTP transport that connects to tailscaled.\n * Reuses connections via Node.js http.Agent keep-alive.\n * Port and token are discovered per-request (matching Go's behavior),\n * so the client adapts to daemon restarts and late starts.\n */\nexport class Transport {\n private readonly socketPath: string;\n private readonly useSocketOnly: boolean;\n private readonly agent: http.Agent;\n\n constructor(opts: TransportOptions = {}) {\n this.socketPath = opts.socketPath ?? defaultSocketPath();\n this.useSocketOnly = opts.useSocketOnly ?? false;\n\n // Single agent with keep-alive — pools connections by host:port key\n this.agent = new http.Agent({\n keepAlive: true,\n keepAliveMsecs: 60_000,\n });\n }\n\n async request(\n method: string,\n path: string,\n body?: Buffer | string,\n extraHeaders?: Record<string, string>,\n ): Promise<{ status: number; body: Buffer; headers: http.IncomingHttpHeaders }> {\n const portAndToken = await resolvePortAndToken(this.useSocketOnly);\n\n return new Promise((resolve, reject) => {\n const headers: Record<string, string> = {\n Host: LOCAL_API_HOST,\n \"Tailscale-Cap\": String(CURRENT_CAP_VERSION),\n ...extraHeaders,\n };\n\n if (portAndToken) {\n const cred = Buffer.from(`:${portAndToken.token}`).toString(\"base64\");\n headers[\"Authorization\"] = `Basic ${cred}`;\n }\n\n const options: http.RequestOptions = {\n method,\n path,\n headers,\n agent: this.agent,\n };\n\n if (portAndToken) {\n options.host = \"127.0.0.1\";\n options.port = portAndToken.port;\n } else {\n options.socketPath = this.socketPath;\n }\n\n const req = http.request(options, (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n resolve({\n status: res.statusCode ?? 0,\n body: Buffer.concat(chunks),\n headers: res.headers,\n });\n });\n res.on(\"error\", reject);\n });\n\n req.on(\"error\", reject);\n\n if (body !== undefined) {\n req.write(body);\n }\n req.end();\n });\n }\n\n destroy(): void {\n this.agent.destroy();\n }\n}\n","// Code generated by cmd/typegen; DO NOT EDIT.\n\nimport { z } from \"zod\";\n\nconst goSlice = <T extends z.ZodTypeAny>(item: T) =>\n z.array(item).nullish().transform(v => v ?? []);\n\nconst goMap = <V extends z.ZodTypeAny>(val: V) =>\n z.record(z.string(), val).nullish().transform(v => v ?? {});\n\nconst int64 = z.union([z.number().int(), z.bigint()]).transform(v => BigInt(v));\n\n/**\n * Location represents geographical location data about a\n * Tailscale host. Location is optional and only set if\n * explicitly declared by a node.\n */\nexport const LocationSchema = z.object({\n /** User friendly country name, with proper capitalization (\"Canada\") */\n Country: z.string().nullish(),\n /** ISO 3166-1 alpha-2 in upper case (\"CA\") */\n CountryCode: z.string().nullish(),\n /** User friendly city name, with proper capitalization (\"Squamish\") */\n City: z.string().nullish(),\n /**\n * CityCode is a short code representing the city in upper case.\n * CityCode is used to disambiguate a city from another location\n * with the same city name. It uniquely identifies a particular\n * geographical location, within the tailnet.\n * IATA, ICAO or ISO 3166-2 codes are recommended (\"YSE\")\n */\n CityCode: z.string().nullish(),\n /**\n * Latitude, Longitude are optional geographical coordinates of the node, in degrees.\n * No particular accuracy level is promised; the coordinates may simply be the center of the city or country.\n */\n Latitude: z.number().nullish(),\n Longitude: z.number().nullish(),\n /**\n * Priority determines the order of use of an exit node when a\n * location based preference matches more than one exit node,\n * the node with the highest priority wins. Nodes of equal\n * probability may be selected arbitrarily.\n * \n * A value of 0 means the exit node does not have a priority\n * preference. A negative int is not allowed.\n */\n Priority: int64.nullish(),\n});\nexport type Location = z.infer<typeof LocationSchema>;\n\n/**\n * PeerStatus describes a peer node and its current state.\n * WARNING: The fields in PeerStatus are merged by the AddPeer method in the StatusBuilder.\n * When adding a new field to PeerStatus, you must update AddPeer to handle merging\n * the new field. The AddPeer function is responsible for combining multiple updates\n * to the same peer, and any new field that is not merged properly may lead to\n * inconsistencies or lost data in the peer status.\n */\nexport const PeerStatusSchema = z.object({\n ID: z.string().default(\"\"),\n PublicKey: z.string().default(\"\"),\n /** HostInfo's Hostname (not a DNS name or necessarily unique) */\n HostName: z.string().default(\"\"),\n /**\n * DNSName is the Peer's FQDN. It ends with a dot.\n * It has the form \"host.<MagicDNSSuffix>.\"\n */\n DNSName: z.string().default(\"\"),\n /** HostInfo.OS */\n OS: z.string().default(\"\"),\n UserID: int64.default(0n),\n /**\n * AltSharerUserID is the user who shared this node\n * if it's different than UserID. Otherwise it's zero.\n */\n AltSharerUserID: int64.nullish(),\n /** TailscaleIPs are the IP addresses assigned to the node. */\n TailscaleIPs: goSlice(z.string()),\n /** AllowedIPs are IP addresses allowed to route to this node. */\n AllowedIPs: goSlice(z.string()),\n /**\n * Tags are the list of ACL tags applied to this node.\n * See tailscale.com/tailcfg#Node.Tags for more information.\n */\n Tags: goSlice(z.string()),\n /**\n * PrimaryRoutes are the routes this node is currently the primary\n * subnet router for, as determined by the control plane. It does\n * not include the IPs in TailscaleIPs.\n */\n PrimaryRoutes: goSlice(z.string()),\n /** Endpoints: */\n Addrs: goSlice(z.string()),\n /** one of Addrs, or unique if roaming */\n CurAddr: z.string().default(\"\"),\n /** DERP region */\n Relay: z.string().default(\"\"),\n /** peer relay address (ip:port:vni) */\n PeerRelay: z.string().default(\"\"),\n RxBytes: int64.default(0n),\n TxBytes: int64.default(0n),\n /** time registered with tailcontrol */\n Created: z.string().default(\"\"),\n /** time last packet sent */\n LastWrite: z.string().default(\"\"),\n /** last seen to tailcontrol; only present if offline */\n LastSeen: z.string().default(\"\"),\n /** with local wireguard */\n LastHandshake: z.string().default(\"\"),\n /** whether node is connected to the control plane */\n Online: z.boolean().default(false),\n /** true if this is the currently selected exit node. */\n ExitNode: z.boolean().default(false),\n /** true if this node can be an exit node (offered && approved) */\n ExitNodeOption: z.boolean().default(false),\n /**\n * Active is whether the node was recently active. The\n * definition is somewhat undefined but has historically and\n * currently means that there was some packet sent to this\n * peer in the past two minutes. That definition is subject to\n * change.\n */\n Active: z.boolean().default(false),\n /** PeerAPIURL are the URLs of the node's PeerAPI servers. */\n PeerAPIURL: goSlice(z.string()),\n /** TaildropTargetStatus represents the node's eligibility to have files shared to it. */\n TaildropTarget: int64.default(0n),\n /** Reason why this peer cannot receive files. Empty if CanReceiveFiles=true */\n NoFileSharingReason: z.string().default(\"\"),\n /**\n * Capabilities are capabilities that the node has.\n * They're free-form strings, but should be in the form of URLs/URIs\n * such as:\n * \"https://tailscale.com/cap/is-admin\"\n * \"https://tailscale.com/cap/file-sharing\"\n * \"funnel\"\n * \n * Deprecated: use CapMap instead. See https://github.com/tailscale/tailscale/issues/11508\n * Every value is Capabilities is also a key in CapMap, even if it\n * has no values in that map.\n */\n Capabilities: goSlice(z.string()),\n /** CapMap is a map of capabilities to their values. */\n CapMap: goMap(goSlice(z.unknown())),\n /** SSH_HostKeys are the node's SSH host keys, if known. */\n sshHostKeys: goSlice(z.string()),\n /**\n * ShareeNode indicates this node exists in the netmap because\n * it's owned by a shared-to user and that node might connect\n * to us. These nodes should be hidden by \"tailscale status\"\n * etc by default.\n */\n ShareeNode: z.boolean().nullish(),\n /**\n * InNetworkMap means that this peer was seen in our latest network map.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InNetworkMap: z.boolean().default(false),\n /**\n * InMagicSock means that this peer is being tracked by magicsock.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InMagicSock: z.boolean().default(false),\n /**\n * InEngine means that this peer is tracked by the wireguard engine.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InEngine: z.boolean().default(false),\n /**\n * Expired means that this peer's node key has expired, based on either\n * information from control or optimisically set on the client if the\n * expiration time has passed.\n */\n Expired: z.boolean().nullish(),\n /**\n * KeyExpiry, if present, is the time at which the node key expired or\n * will expire.\n */\n KeyExpiry: z.string().nullish(),\n Location: LocationSchema.nullish(),\n});\nexport type PeerStatus = z.infer<typeof PeerStatusSchema>;\n\n/** ExitNodeStatus describes the current exit node. */\nexport const ExitNodeStatusSchema = z.object({\n /** ID is the exit node's ID. */\n ID: z.string().default(\"\"),\n /** Online is whether the exit node is alive. */\n Online: z.boolean().default(false),\n /** TailscaleIPs are the exit node's IP addresses assigned to the node. */\n TailscaleIPs: goSlice(z.string()),\n});\nexport type ExitNodeStatus = z.infer<typeof ExitNodeStatusSchema>;\n\n/** TailnetStatus is information about a Tailscale network (\"tailnet\"). */\nexport const TailnetStatusSchema = z.object({\n /** Name is the name of the network that's currently in use. */\n Name: z.string().default(\"\"),\n /**\n * MagicDNSSuffix is the network's MagicDNS suffix for nodes\n * in the network such as \"userfoo.tailscale.net\".\n * There are no surrounding dots.\n * MagicDNSSuffix should be populated regardless of whether a domain\n * has MagicDNS enabled.\n */\n MagicDNSSuffix: z.string().default(\"\"),\n /**\n * MagicDNSEnabled is whether or not the network has MagicDNS enabled.\n * Note that the current device may still not support MagicDNS if\n * `--accept-dns=false` was used.\n */\n MagicDNSEnabled: z.boolean().default(false),\n});\nexport type TailnetStatus = z.infer<typeof TailnetStatusSchema>;\n\n/**\n * A UserProfile is display-friendly data for a [User].\n * It includes the LoginName for display purposes but *not* the Provider.\n * It also includes derived data from one of the user's logins.\n */\nexport const UserProfileSchema = z.object({\n ID: int64.default(0n),\n /** \"alice@smith.com\"; for display purposes only (provider is not listed) */\n LoginName: z.string().default(\"\"),\n /** \"Alice Smith\" */\n DisplayName: z.string().default(\"\"),\n ProfilePicURL: z.string().nullish(),\n});\nexport type UserProfile = z.infer<typeof UserProfileSchema>;\n\n/**\n * ClientVersion is information about the latest client version that's available\n * for the client (and whether they're already running it).\n * \n * It does not include a URL to download the client, as that varies by platform.\n */\nexport const ClientVersionSchema = z.object({\n /** RunningLatest is true if the client is running the latest build. */\n RunningLatest: z.boolean().nullish(),\n /**\n * LatestVersion is the latest version.Short (\"1.34.2\") version available\n * for download for the client's platform and packaging type.\n * It won't be populated if RunningLatest is true.\n */\n LatestVersion: z.string().nullish(),\n /**\n * UrgentSecurityUpdate is set when the client is missing an important\n * security update. That update may be in LatestVersion or earlier.\n * UrgentSecurityUpdate should not be set if RunningLatest is false.\n */\n UrgentSecurityUpdate: z.boolean().nullish(),\n /**\n * Notify is whether the client should do an OS-specific notification about\n * a new version being available. This should not be populated if\n * RunningLatest is true. The client should not notify multiple times for\n * the same LatestVersion value.\n */\n Notify: z.boolean().nullish(),\n /**\n * NotifyURL is a URL to open in the browser when the user clicks on the\n * notification, when Notify is true.\n */\n NotifyURL: z.string().nullish(),\n /** NotifyText is the text to show in the notification, when Notify is true. */\n NotifyText: z.string().nullish(),\n});\nexport type ClientVersion = z.infer<typeof ClientVersionSchema>;\n\n/** Status represents the entire state of the IPN network. */\nexport const StatusSchema = z.object({\n /** Version is the daemon's long version (see version.Long). */\n Version: z.string().default(\"\"),\n /**\n * TUN is whether /dev/net/tun (or equivalent kernel interface) is being\n * used. If false, it's running in userspace mode.\n */\n TUN: z.boolean().default(false),\n /**\n * BackendState is an ipn.State string value:\n * \"NoState\", \"NeedsLogin\", \"NeedsMachineAuth\", \"Stopped\",\n * \"Starting\", \"Running\".\n */\n BackendState: z.string().default(\"\"),\n /** HaveNodeKey is whether the current profile has a node key configured. */\n HaveNodeKey: z.boolean().nullish(),\n /** current URL provided by control to authorize client */\n AuthURL: z.string().default(\"\"),\n /** Tailscale IP(s) assigned to this node */\n TailscaleIPs: goSlice(z.string()),\n Self: PeerStatusSchema.nullish(),\n /**\n * ExitNodeStatus describes the current exit node.\n * If nil, an exit node is not in use.\n */\n ExitNodeStatus: ExitNodeStatusSchema.nullish(),\n /**\n * Health contains health check problems.\n * Empty means everything is good. (or at least that no known\n * problems are detected)\n */\n Health: goSlice(z.string()),\n /**\n * This field is the legacy name of CurrentTailnet.MagicDNSSuffix.\n * \n * Deprecated: use CurrentTailnet.MagicDNSSuffix instead.\n */\n MagicDNSSuffix: z.string().default(\"\"),\n /**\n * CurrentTailnet is information about the tailnet that the node\n * is currently connected to. When not connected, this field is nil.\n */\n CurrentTailnet: TailnetStatusSchema.nullish(),\n /**\n * CertDomains are the set of DNS names for which the control\n * plane server will assist with provisioning TLS\n * certificates. See SetDNSRequest for dns-01 ACME challenges\n * for e.g. LetsEncrypt. These names are FQDNs without\n * trailing periods, and without any \"_acme-challenge.\" prefix.\n */\n CertDomains: goSlice(z.string()),\n /** Peer is the state of each peer, keyed by each peer's current public key. */\n Peer: goMap(PeerStatusSchema),\n /**\n * User contains profile information about UserIDs referenced by\n * PeerStatus.UserID, PeerStatus.AltSharerUserID, etc.\n */\n User: goMap(UserProfileSchema),\n /**\n * ClientVersion, when non-nil, contains information about the latest\n * version of the Tailscale client that's available. Depending on\n * the platform and client settings, it may not be available.\n */\n ClientVersion: ClientVersionSchema.nullish(),\n});\nexport type Status = z.infer<typeof StatusSchema>;\n\n/** Service represents a service running on a node. */\nexport const ServiceSchema = z.object({\n /**\n * Proto is the type of service. It's usually the constant TCP\n * or UDP (\"tcp\" or \"udp\"), but it can also be one of the\n * following meta service values:\n * \n * * \"peerapi4\": peerapi is available on IPv4; Port is the\n * port number that the peerapi is running on the\n * node's Tailscale IPv4 address.\n * * \"peerapi6\": peerapi is available on IPv6; Port is the\n * port number that the peerapi is running on the\n * node's Tailscale IPv6 address.\n * * \"peerapi-dns-proxy\": the local peerapi service supports\n * being a DNS proxy (when the node is an exit\n * node). For this service, the Port number must only be 1.\n */\n Proto: z.string().default(\"\"),\n /**\n * Port is the port number.\n * \n * For Proto \"peerapi-dns\", it must be 1.\n */\n Port: z.number().default(0),\n /**\n * Description is the textual description of the service,\n * usually the process name that's running.\n */\n Description: z.string().nullish(),\n});\nexport type Service = z.infer<typeof ServiceSchema>;\n\n/** NetInfo contains information about the host's network state. */\nexport const NetInfoSchema = z.object({\n /**\n * MappingVariesByDestIP says whether the host's NAT mappings\n * vary based on the destination IP.\n */\n MappingVariesByDestIP: z.boolean().nullish(),\n /** WorkingIPv6 is whether the host has IPv6 internet connectivity. */\n WorkingIPv6: z.boolean().nullish(),\n /**\n * OSHasIPv6 is whether the OS supports IPv6 at all, regardless of\n * whether IPv6 internet connectivity is available.\n */\n OSHasIPv6: z.boolean().nullish(),\n /** WorkingUDP is whether the host has UDP internet connectivity. */\n WorkingUDP: z.boolean().nullish(),\n /**\n * WorkingICMPv4 is whether ICMPv4 works.\n * Empty means not checked.\n */\n WorkingICMPv4: z.boolean().nullish(),\n /**\n * HavePortMap is whether we have an existing portmap open\n * (UPnP, PMP, or PCP).\n */\n HavePortMap: z.boolean().nullish(),\n /**\n * UPnP is whether UPnP appears present on the LAN.\n * Empty means not checked.\n */\n UPnP: z.boolean().nullish(),\n /**\n * PMP is whether NAT-PMP appears present on the LAN.\n * Empty means not checked.\n */\n PMP: z.boolean().nullish(),\n /**\n * PCP is whether PCP appears present on the LAN.\n * Empty means not checked.\n */\n PCP: z.boolean().nullish(),\n /**\n * PreferredDERP is this node's preferred (home) DERP region ID.\n * This is where the node expects to be contacted to begin a\n * peer-to-peer connection. The node might be be temporarily\n * connected to multiple DERP servers (to speak to other nodes\n * that are located elsewhere) but PreferredDERP is the region ID\n * that the node subscribes to traffic at.\n * Zero means disconnected or unknown.\n */\n PreferredDERP: int64.nullish(),\n /** LinkType is the current link type, if known. */\n LinkType: z.string().nullish(),\n /**\n * DERPLatency is the fastest recent time to reach various\n * DERP STUN servers, in seconds. The map key is the\n * \"regionID-v4\" or \"-v6\"; it was previously the DERP server's\n * STUN host:port.\n * \n * This should only be updated rarely, or when there's a\n * material change, as any change here also gets uploaded to\n * the control plane.\n */\n DERPLatency: goMap(z.number()),\n /**\n * FirewallMode encodes both which firewall mode was selected and why.\n * It is Linux-specific (at least as of 2023-08-19) and is meant to help\n * debug iptables-vs-nftables issues. The string is of the form\n * \"{nft,ift}-REASON\", like \"nft-forced\" or \"ipt-default\". Empty means\n * either not Linux or a configuration in which the host firewall rules\n * are not managed by tailscaled.\n */\n FirewallMode: z.string().nullish(),\n});\nexport type NetInfo = z.infer<typeof NetInfoSchema>;\n\n/**\n * TPMInfo contains information about a TPM 2.0 device present on a node.\n * All fields are read from TPM_CAP_TPM_PROPERTIES, see Part 2, section 6.13 of\n * https://trustedcomputinggroup.org/resource/tpm-library-specification/.\n */\nexport const TPMInfoSchema = z.object({\n /**\n * Manufacturer is a 4-letter code from section 4.1 of\n * https://trustedcomputinggroup.org/resource/vendor-id-registry/,\n * for example \"MSFT\" for Microsoft.\n * Read from TPM_PT_MANUFACTURER.\n */\n Manufacturer: z.string().nullish(),\n /**\n * Vendor is a vendor ID string, up to 16 characters.\n * Read from TPM_PT_VENDOR_STRING_*.\n */\n Vendor: z.string().nullish(),\n /**\n * Model is a vendor-defined TPM model.\n * Read from TPM_PT_VENDOR_TPM_TYPE.\n */\n Model: int64.nullish(),\n /**\n * FirmwareVersion is the version number of the firmware.\n * Read from TPM_PT_FIRMWARE_VERSION_*.\n */\n FirmwareVersion: int64.nullish(),\n /**\n * SpecRevision is the TPM 2.0 spec revision encoded as a single number. All\n * revisions can be found at\n * https://trustedcomputinggroup.org/resource/tpm-library-specification/.\n * Before revision 184, TCG used the \"01.83\" format for revision 183.\n */\n SpecRevision: int64.nullish(),\n /**\n * FamilyIndicator is the TPM spec family, like \"2.0\".\n * Read from TPM_PT_FAMILY_INDICATOR.\n */\n FamilyIndicator: z.string().nullish(),\n});\nexport type TPMInfo = z.infer<typeof TPMInfoSchema>;\n\n/**\n * Hostinfo contains a summary of a Tailscale host.\n * \n * Because it contains pointers (slices), this type should not be used\n * as a value type.\n */\nexport const HostinfoSchema = z.object({\n /** version of this code (in version.Long format) */\n IPNVersion: z.string().nullish(),\n /** logtail ID of frontend instance */\n FrontendLogID: z.string().nullish(),\n /** logtail ID of backend instance */\n BackendLogID: z.string().nullish(),\n /** operating system the client runs on (a version.OS value) */\n OS: z.string().nullish(),\n /**\n * OSVersion is the version of the OS, if available.\n * \n * For Android, it's like \"10\", \"11\", \"12\", etc. For iOS and macOS it's like\n * \"15.6.1\" or \"12.4.0\". For Windows it's like \"10.0.19044.1889\". For\n * FreeBSD it's like \"12.3-STABLE\".\n * \n * For Linux, prior to Tailscale 1.32, we jammed a bunch of fields into this\n * string on Linux, like \"Debian 10.4; kernel=xxx; container; env=kn\" and so\n * on. As of Tailscale 1.32, this is simply the kernel version on Linux, like\n * \"5.10.0-17-amd64\".\n */\n OSVersion: z.string().nullish(),\n /** best-effort whether the client is running in a container */\n Container: z.boolean().nullish(),\n /** a hostinfo.EnvType in string form */\n Env: z.string().nullish(),\n /** \"debian\", \"ubuntu\", \"nixos\", ... */\n Distro: z.string().nullish(),\n /** \"20.04\", ... */\n DistroVersion: z.string().nullish(),\n /** \"jammy\", \"bullseye\", ... */\n DistroCodeName: z.string().nullish(),\n /** App is used to disambiguate Tailscale clients that run using tsnet. */\n App: z.string().nullish(),\n /** if a desktop was detected on Linux */\n Desktop: z.boolean().nullish(),\n /** Tailscale package to disambiguate (\"choco\", \"appstore\", etc; \"\" for unknown) */\n Package: z.string().nullish(),\n /** mobile phone model (\"Pixel 3a\", \"iPhone12,3\") */\n DeviceModel: z.string().nullish(),\n /** macOS/iOS APNs device token for notifications (and Android in the future) */\n PushDeviceToken: z.string().nullish(),\n /** name of the host the client runs on */\n Hostname: z.string().nullish(),\n /** indicates whether the host is blocking incoming connections */\n ShieldsUp: z.boolean().nullish(),\n /** indicates this node exists in netmap because it's owned by a shared-to user */\n ShareeNode: z.boolean().nullish(),\n /** indicates that the user has opted out of sending logs and support */\n NoLogsNoSupport: z.boolean().nullish(),\n /**\n * WireIngress indicates that the node would like to be wired up server-side\n * (DNS, etc) to be able to use Tailscale Funnel, even if it's not currently\n * enabled. For example, the user might only use it for intermittent\n * foreground CLI serve sessions, for which they'd like it to work right\n * away, even if it's disabled most of the time. As an optimization, this is\n * only sent if IngressEnabled is false, as IngressEnabled implies that this\n * option is true.\n */\n WireIngress: z.boolean().nullish(),\n /** if the node has any funnel endpoint enabled */\n IngressEnabled: z.boolean().nullish(),\n /** indicates that the node has opted-in to admin-console-drive remote updates */\n AllowsUpdate: z.boolean().nullish(),\n /** the current host's machine type (uname -m) */\n Machine: z.string().nullish(),\n /** GOARCH value (of the built binary) */\n GoArch: z.string().nullish(),\n /** GOARM, GOAMD64, etc (of the built binary) */\n GoArchVar: z.string().nullish(),\n /** Go version binary was built with */\n GoVersion: z.string().nullish(),\n /** set of IP ranges this client can route */\n RoutableIPs: goSlice(z.string()),\n /** set of ACL tags this node wants to claim */\n RequestTags: goSlice(z.string()),\n /** MAC address(es) to send Wake-on-LAN packets to wake this node (lowercase hex w/ colons) */\n WoLMACs: goSlice(z.string()),\n /** services advertised by this machine */\n Services: goSlice(ServiceSchema),\n NetInfo: NetInfoSchema.nullish(),\n /** if advertised */\n sshHostKeys: goSlice(z.string()),\n Cloud: z.string().nullish(),\n /** if the client is running in userspace (netstack) mode */\n Userspace: z.boolean().nullish(),\n /** if the client's subnet router is running in userspace (netstack) mode */\n UserspaceRouter: z.boolean().nullish(),\n /** if the client is running the app-connector service */\n AppConnector: z.boolean().nullish(),\n /** opaque hash of the most recent list of tailnet services, change in hash indicates config should be fetched via c2n */\n ServicesHash: z.string().nullish(),\n /** the client’s selected exit node, empty when unselected. */\n ExitNodeID: z.string().nullish(),\n /**\n * Location represents geographical location data about a\n * Tailscale host. Location is optional and only set if\n * explicitly declared by a node.\n */\n Location: LocationSchema.nullish(),\n /** TPM device metadata, if available */\n TPM: TPMInfoSchema.nullish(),\n /**\n * StateEncrypted reports whether the node state is stored encrypted on\n * disk. The actual mechanism is platform-specific:\n * * Apple nodes use the Keychain\n * * Linux and Windows nodes use the TPM\n * * Android apps use EncryptedSharedPreferences\n */\n StateEncrypted: z.boolean().nullish(),\n});\nexport type Hostinfo = z.infer<typeof HostinfoSchema>;\n\n/** Resolver is the configuration for one DNS resolver. */\nexport const ResolverSchema = z.object({\n /**\n * Addr is the address of the DNS resolver, one of:\n * - A plain IP address for a \"classic\" UDP+TCP DNS resolver.\n * This is the common format as sent by the control plane.\n * - An IP:port, for tests.\n * - \"https://resolver.com/path\" for DNS over HTTPS; currently\n * as of 2022-09-08 only used for certain well-known resolvers\n * (see the publicdns package) for which the IP addresses to dial DoH are\n * known ahead of time, so bootstrap DNS resolution is not required.\n * - \"http://node-address:port/path\" for DNS over HTTP over WireGuard. This\n * is implemented in the PeerAPI for exit nodes and app connectors.\n * - [TODO] \"tls://resolver.com\" for DNS over TCP+TLS\n */\n Addr: z.string().nullish(),\n /**\n * BootstrapResolution is an optional suggested resolution for the\n * DoT/DoH resolver, if the resolver URL does not reference an IP\n * address directly.\n * BootstrapResolution may be empty, in which case clients should\n * look up the DoT/DoH server using their local \"classic\" DNS\n * resolver.\n * \n * As of 2022-09-08, BootstrapResolution is not yet used.\n */\n BootstrapResolution: goSlice(z.string()),\n /**\n * UseWithExitNode designates that this resolver should continue to be used when an\n * exit node is in use. Normally, DNS resolution is delegated to the exit node but\n * there are situations where it is preferable to still use a Split DNS server and/or\n * global DNS server instead of the exit node.\n */\n UseWithExitNode: z.boolean().nullish(),\n});\nexport type Resolver = z.infer<typeof ResolverSchema>;\n\n/** Node is a Tailscale device in a tailnet. */\nexport const NodeSchema = z.object({\n ID: int64.default(0n),\n StableID: z.string().default(\"\"),\n /**\n * Name is the FQDN of the node.\n * It is also the MagicDNS name for the node.\n * It has a trailing dot.\n * e.g. \"host.tail-scale.ts.net.\"\n */\n Name: z.string().default(\"\"),\n /**\n * User is the user who created the node. If ACL tags are in use for the\n * node then it doesn't reflect the ACL identity that the node is running\n * as.\n */\n User: int64.default(0n),\n /** Sharer, if non-zero, is the user who shared this node, if different than User. */\n Sharer: int64.nullish(),\n Key: z.string().default(\"\"),\n /** the zero value if this node does not expire */\n KeyExpiry: z.string().nullish(),\n KeySignature: z.string().nullish(),\n Machine: z.string().nullish(),\n DiscoKey: z.string().nullish(),\n /** Addresses are the IP addresses of this Node directly. */\n Addresses: goSlice(z.string()),\n /**\n * AllowedIPs are the IP ranges to route to this node.\n * \n * As of CapabilityVersion 112, this may be nil (null or undefined) on the wire\n * to mean the same as Addresses. Internally, it is always filled in with\n * its possibly-implicit value.\n */\n AllowedIPs: goSlice(z.string()),\n /** IP+port (public via STUN, and local LANs) */\n Endpoints: goSlice(z.string()),\n /**\n * LegacyDERPString is this node's home LegacyDERPString region ID integer, but shoved into an\n * IP:port string for legacy reasons. The IP address is always \"127.3.3.40\"\n * (a loopback address (127) followed by the digits over the letters DERP on\n * a QWERTY keyboard (3.3.40)). The \"port number\" is the home LegacyDERPString region ID\n * integer.\n * \n * Deprecated: HomeDERP has replaced this, but old servers might still send\n * this field. See tailscale/tailscale#14636. Do not use this field in code\n * other than in the upgradeNode func, which canonicalizes it to HomeDERP\n * if it arrives as a LegacyDERPString string on the wire.\n */\n DERP: z.string().nullish(),\n /**\n * HomeDERP is the modern version of the DERP string field, with just an\n * integer. The client advertises support for this as of capver 111.\n * \n * HomeDERP may be zero if not (yet) known, but ideally always be non-zero\n * for magicsock connectivity to function normally.\n */\n HomeDERP: int64.nullish(),\n Hostinfo: HostinfoSchema.nullish(),\n Created: z.string().nullish(),\n /** if non-zero, the node's capability version; old servers might not send */\n Cap: int64.nullish(),\n /**\n * Tags are the list of ACL tags applied to this node.\n * Tags take the form of `tag:<value>` where value starts\n * with a letter and only contains alphanumerics and dashes `-`.\n * Some valid tag examples:\n * `tag:prod`\n * `tag:database`\n * `tag:lab-1`\n */\n Tags: goSlice(z.string()),\n /**\n * PrimaryRoutes are the routes from AllowedIPs that this node\n * is currently the primary subnet router for, as determined\n * by the control plane. It does not include the self address\n * values from Addresses that are in AllowedIPs.\n */\n PrimaryRoutes: goSlice(z.string()),\n /**\n * LastSeen is when the node was last online. It is not\n * updated when Online is true. It is nil if the current\n * node doesn't have permission to know, or the node\n * has never been online.\n */\n LastSeen: z.string().nullish(),\n /**\n * Online is whether the node is currently connected to the\n * coordination server. A value of nil means unknown, or the\n * current node doesn't have permission to know.\n */\n Online: z.boolean().nullish(),\n /** TODO(crawshaw): replace with MachineStatus */\n MachineAuthorized: z.boolean().nullish(),\n /**\n * Capabilities are capabilities that the node has.\n * They're free-form strings, but should be in the form of URLs/URIs\n * such as:\n * \"https://tailscale.com/cap/is-admin\"\n * \"https://tailscale.com/cap/file-sharing\"\n * \n * Deprecated: use CapMap instead. See https://github.com/tailscale/tailscale/issues/11508\n */\n Capabilities: goSlice(z.string()),\n /**\n * CapMap is a map of capabilities to their optional argument/data values.\n * \n * It is valid for a capability to not have any argument/data values; such\n * capabilities can be tested for using the HasCap method. These type of\n * capabilities are used to indicate that a node has a capability, but there\n * is no additional data associated with it. These were previously\n * represented by the Capabilities field, but can now be represented by\n * CapMap with an empty value.\n * \n * See NodeCapability for more information on keys.\n * \n * Metadata about nodes can be transmitted in 3 ways:\n * 1. MapResponse.Node.CapMap describes attributes that affect behavior for\n * this node, such as which features have been enabled through the admin\n * panel and any associated configuration details.\n * 2. MapResponse.PacketFilter(s) describes access (both IP and application\n * based) that should be granted to peers.\n * 3. MapResponse.Peers[].CapMap describes attributes regarding a peer node,\n * such as which features the peer supports or if that peer is preferred\n * for a particular task vs other peers that could also be chosen.\n */\n CapMap: goMap(goSlice(z.unknown())),\n /**\n * UnsignedPeerAPIOnly means that this node is not signed nor subject to TKA\n * restrictions. However, in exchange for that privilege, it does not get\n * network access. It can only access this node's peerapi, which may not let\n * it do anything. It is the tailscaled client's job to double-check the\n * MapResponse's PacketFilter to verify that its AllowedIPs will not be\n * accepted by the packet filter.\n */\n UnsignedPeerAPIOnly: z.boolean().nullish(),\n /** MagicDNS base name (for normal non-shared-in nodes), FQDN (without trailing dot, for shared-in nodes), or Hostname (if no MagicDNS) */\n ComputedName: z.string().nullish(),\n /** either \"ComputedName\" or \"ComputedName (computedHostIfDifferent)\", if computedHostIfDifferent is set */\n ComputedNameWithHost: z.string().nullish(),\n /** DataPlaneAuditLogID is the per-node logtail ID used for data plane audit logging. */\n DataPlaneAuditLogID: z.string().nullish(),\n /**\n * Expired is whether this node's key has expired. Control may send\n * this; clients are only allowed to set this from false to true. On\n * the client, this is calculated client-side based on a timestamp sent\n * from control, to avoid clock skew issues.\n */\n Expired: z.boolean().nullish(),\n /**\n * SelfNodeV4MasqAddrForThisPeer is the IPv4 that this peer knows the current node as.\n * It may be empty if the peer knows the current node by its native\n * IPv4 address.\n * This field is only populated in a MapResponse for peers and not\n * for the current node.\n * \n * If set, it should be used to masquerade traffic originating from the\n * current node to this peer. The masquerade address is only relevant\n * for this peer and not for other peers.\n * \n * This only applies to traffic originating from the current node to the\n * peer or any of its subnets. Traffic originating from subnet routes will\n * not be masqueraded (e.g. in case of --snat-subnet-routes).\n */\n SelfNodeV4MasqAddrForThisPeer: z.string().nullish(),\n /**\n * SelfNodeV6MasqAddrForThisPeer is the IPv6 that this peer knows the current node as.\n * It may be empty if the peer knows the current node by its native\n * IPv6 address.\n * This field is only populated in a MapResponse for peers and not\n * for the current node.\n * \n * If set, it should be used to masquerade traffic originating from the\n * current node to this peer. The masquerade address is only relevant\n * for this peer and not for other peers.\n * \n * This only applies to traffic originating from the current node to the\n * peer or any of its subnets. Traffic originating from subnet routes will\n * not be masqueraded (e.g. in case of --snat-subnet-routes).\n */\n SelfNodeV6MasqAddrForThisPeer: z.string().nullish(),\n /**\n * IsWireGuardOnly indicates that this is a non-Tailscale WireGuard peer, it\n * is not expected to speak Disco or DERP, and it must have Endpoints in\n * order to be reachable.\n */\n IsWireGuardOnly: z.boolean().nullish(),\n /**\n * IsJailed indicates that this node is jailed and should not be allowed\n * initiate connections, however outbound connections to it should still be\n * allowed.\n */\n IsJailed: z.boolean().nullish(),\n /**\n * ExitNodeDNSResolvers is the list of DNS servers that should be used when this\n * node is marked IsWireGuardOnly and being used as an exit node.\n */\n ExitNodeDNSResolvers: goSlice(ResolverSchema),\n});\nexport type Node = z.infer<typeof NodeSchema>;\n\n/**\n * TCPPortHandler describes what to do when handling a TCP\n * connection.\n */\nexport const TCPPortHandlerSchema = z.object({\n /**\n * HTTPS, if true, means that tailscaled should handle this connection as an\n * HTTPS request as configured by ServeConfig.Web.\n * \n * It is mutually exclusive with TCPForward.\n */\n HTTPS: z.boolean().nullish(),\n /**\n * HTTP, if true, means that tailscaled should handle this connection as an\n * HTTP request as configured by ServeConfig.Web.\n * \n * It is mutually exclusive with TCPForward.\n */\n HTTP: z.boolean().nullish(),\n /**\n * TCPForward is the IP:port to forward TCP connections to.\n * Whether or not TLS is terminated by tailscaled depends on\n * TerminateTLS.\n * \n * It is mutually exclusive with HTTPS.\n */\n TCPForward: z.string().nullish(),\n /**\n * TerminateTLS, if non-empty, means that tailscaled should terminate the\n * TLS connections before forwarding them to TCPForward, permitting only the\n * SNI name with this value. It is only used if TCPForward is non-empty.\n * (the HTTPS mode uses ServeConfig.Web)\n */\n TerminateTLS: z.string().nullish(),\n /**\n * ProxyProtocol indicates whether to send a PROXY protocol header\n * before forwarding the connection to TCPForward.\n * \n * This is only valid if TCPForward is non-empty.\n */\n ProxyProtocol: int64.nullish(),\n});\nexport type TCPPortHandler = z.infer<typeof TCPPortHandlerSchema>;\n\n/** HTTPHandler is either a path or a proxy to serve. */\nexport const HTTPHandlerSchema = z.object({\n /** absolute path to directory or file to serve */\n Path: z.string().nullish(),\n /** http://localhost:3000/, localhost:3030, 3030 */\n Proxy: z.string().nullish(),\n /** plaintext to serve (primarily for testing) */\n Text: z.string().nullish(),\n /** peer capabilities to forward in grant header, e.g. example.com/cap/mon */\n AcceptAppCaps: goSlice(z.string()),\n /**\n * Redirect, if not empty, is the target URL to redirect requests to.\n * By default, we redirect with HTTP 302 (Found) status.\n * If Redirect starts with '<httpcode>:', then we use that status instead.\n * \n * The target URL supports the following expansion variables:\n * - ${HOST}: replaced with the request's Host header value\n * - ${REQUEST_URI}: replaced with the request's full URI (path and query string)\n */\n Redirect: z.string().nullish(),\n});\nexport type HTTPHandler = z.infer<typeof HTTPHandlerSchema>;\n\n/** WebServerConfig describes a web server's configuration. */\nexport const WebServerConfigSchema = z.object({\n /** mountPoint => handler */\n Handlers: goMap(HTTPHandlerSchema),\n});\nexport type WebServerConfig = z.infer<typeof WebServerConfigSchema>;\n\n/**\n * ServiceConfig contains the config information for a single service.\n * it contains a bool to indicate if the service is in Tun mode (L3 forwarding).\n * If the service is not in Tun mode, the service is configured by the L4 forwarding\n * (TCP ports) and/or the L7 forwarding (http handlers) information.\n */\nexport const ServiceConfigSchema = z.object({\n /**\n * TCP are the list of TCP port numbers that tailscaled should handle for\n * the Tailscale IP addresses. (not subnet routers, etc)\n */\n TCP: goMap(TCPPortHandlerSchema),\n /**\n * Web maps from \"$SNI_NAME:$PORT\" to a set of HTTP handlers\n * keyed by mount point (\"/\", \"/foo\", etc)\n */\n Web: goMap(WebServerConfigSchema),\n /** Tun determines if the service should be using L3 forwarding (Tun mode). */\n Tun: z.boolean().nullish(),\n});\nexport type ServiceConfig = z.infer<typeof ServiceConfigSchema>;\n\nconst _ServeConfigRef: z.ZodTypeAny = z.lazy(() => ServeConfigSchema);\n/**\n * ServeConfig is the JSON type stored in the StateStore for\n * StateKey \"_serve/$PROFILE_ID\" as returned by ServeConfigKey.\n */\nexport const ServeConfigSchema = z.object({\n /**\n * TCP are the list of TCP port numbers that tailscaled should handle for\n * the Tailscale IP addresses. (not subnet routers, etc)\n */\n TCP: goMap(TCPPortHandlerSchema),\n /**\n * Web maps from \"$SNI_NAME:$PORT\" to a set of HTTP handlers\n * keyed by mount point (\"/\", \"/foo\", etc)\n */\n Web: goMap(WebServerConfigSchema),\n /**\n * Services maps from service name (in the form \"svc:dns-label\") to a ServiceConfig.\n * Which describes the L3, L4, and L7 forwarding information for the service.\n */\n Services: goMap(ServiceConfigSchema),\n /**\n * AllowFunnel is the set of SNI:port values for which funnel\n * traffic is allowed, from trusted ingress peers.\n */\n AllowFunnel: goMap(z.boolean()),\n /**\n * Foreground is a map of an IPN Bus session ID to an alternate foreground serve config that's valid for the\n * life of that WatchIPNBus session ID. This allows the config to specify ephemeral configs that are used\n * in the CLI's foreground mode to ensure ungraceful shutdowns of either the client or the LocalBackend does not\n * expose ports that users are not aware of. In practice this contains any serve config set via 'tailscale\n * serve' command run without the '--bg' flag. ServeConfig contained by Foreground is not expected itself to contain\n * another Foreground block.\n */\n Foreground: goMap(_ServeConfigRef),\n});\nexport type ServeConfig = z.infer<typeof ServeConfigSchema> & {\n /**\n * ETag is the checksum of the serve config that's populated\n * by the LocalClient through the HTTP ETag header during a\n * GetServeConfig request and is translated to an If-Match header\n * during a SetServeConfig request.\n */\n ETag: string;\n};\n\n/**\n * WhoIsResponse is the JSON type returned by tailscaled debug server's /whois?ip=$IP handler.\n * In successful whois responses, Node and UserProfile are never nil.\n */\nexport const WhoIsResponseSchema = z.object({\n Node: NodeSchema.nullish(),\n UserProfile: UserProfileSchema.nullish(),\n /**\n * CapMap is a map of capabilities to their values.\n * See tailcfg.PeerCapMap and tailcfg.PeerCapability for details.\n */\n CapMap: goMap(goSlice(z.unknown())),\n});\nexport type WhoIsResponse = z.infer<typeof WhoIsResponseSchema>;\n\n/** Alias for TailnetStatus for backward compatibility. */\nexport const CurrentTailnetSchema = z.object({\n Name: z.string(),\n MagicDNSSuffix: z.string(),\n MagicDNSEnabled: z.boolean(),\n});\nexport type CurrentTailnet = z.infer<typeof CurrentTailnetSchema>;\n","import { parseJSON, jsonReplacer } from \"./json.js\";\nimport {\n AccessDeniedError,\n ConnectionError,\n DaemonNotRunningError,\n HttpError,\n PeerNotFoundError,\n PreconditionsFailedError,\n errorMessageFromBody,\n} from \"./errors.js\";\nimport { Transport, type TransportOptions } from \"./transport.js\";\nimport {\n ServeConfigSchema,\n StatusSchema,\n WhoIsResponseSchema,\n type ServeConfig,\n type Status,\n type WhoIsResponse,\n} from \"./types.js\";\n\n/**\n * Client for the Tailscale Local API.\n *\n * Connections are reused via HTTP keep-alive.\n */\nexport class Client {\n private readonly transport: Transport;\n\n constructor(opts: TransportOptions = {}) {\n this.transport = new Transport(opts);\n }\n\n private async doRequest(\n method: string,\n path: string,\n body?: Buffer | string,\n headers?: Record<string, string>,\n ): Promise<{ status: number; body: Buffer; headers: Record<string, string | string[] | undefined> }> {\n try {\n return await this.transport.request(method, path, body, headers);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"ECONNREFUSED\") || msg.includes(\"ENOENT\")) {\n throw new DaemonNotRunningError(msg);\n }\n throw new ConnectionError(msg);\n }\n }\n\n private async doRequestNice(\n method: string,\n path: string,\n body?: Buffer | string,\n headers?: Record<string, string>,\n ): Promise<Buffer> {\n const resp = await this.doRequest(method, path, body, headers);\n if (resp.status >= 200 && resp.status < 300) {\n return resp.body;\n }\n\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n\n if (resp.status === 403) throw new AccessDeniedError(msg);\n if (resp.status === 412) throw new PreconditionsFailedError(msg);\n throw new HttpError(resp.status, msg);\n }\n\n private async get200(path: string): Promise<Buffer> {\n return this.doRequestNice(\"GET\", path);\n }\n\n private async post200(path: string, body?: Buffer | string): Promise<Buffer> {\n return this.doRequestNice(\"POST\", path, body);\n }\n\n // --- Status ---\n\n /** Get the current tailscaled status. */\n async status(): Promise<Status> {\n const data = await this.get200(\"/localapi/v0/status\");\n return StatusSchema.parse(parseJSON(data.toString(\"utf-8\")));\n }\n\n /** Get the current tailscaled status without peer information. */\n async statusWithoutPeers(): Promise<Status> {\n const data = await this.get200(\"/localapi/v0/status?peers=false\");\n return StatusSchema.parse(parseJSON(data.toString(\"utf-8\")));\n }\n\n // --- WhoIs ---\n\n private async doWhoIs(params: string): Promise<WhoIsResponse> {\n const resp = await this.doRequest(\n \"GET\",\n `/localapi/v0/whois?${params}`,\n );\n if (resp.status === 404) {\n throw new PeerNotFoundError(params);\n }\n if (resp.status !== 200) {\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n if (resp.status === 403) throw new AccessDeniedError(msg);\n throw new HttpError(resp.status, msg);\n }\n return WhoIsResponseSchema.parse(parseJSON(resp.body.toString(\"utf-8\")));\n }\n\n /** Look up the owner of an IP address or IP:port. */\n async whoIs(remoteAddr: string): Promise<WhoIsResponse> {\n return this.doWhoIs(`addr=${encodeURIComponent(remoteAddr)}`);\n }\n\n /** Look up a peer by node key. */\n async whoIsNodeKey(nodeKey: string): Promise<WhoIsResponse> {\n return this.whoIs(nodeKey);\n }\n\n /** Look up the owner of an IP address with a specific protocol (\"tcp\" or \"udp\"). */\n async whoIsProto(proto: string, remoteAddr: string): Promise<WhoIsResponse> {\n return this.doWhoIs(`proto=${encodeURIComponent(proto)}&addr=${encodeURIComponent(remoteAddr)}`);\n }\n\n // --- Cert ---\n\n /** Get a TLS certificate and private key for the given domain. */\n async certPair(domain: string): Promise<{ cert: Buffer; key: Buffer }> {\n return this.certPairWithValidity(domain, 0);\n }\n\n /** Get a TLS certificate with minimum validity duration (in seconds). */\n async certPairWithValidity(\n domain: string,\n minValiditySecs: number,\n ): Promise<{ cert: Buffer; key: Buffer }> {\n const body = await this.get200(\n `/localapi/v0/cert/${encodeURIComponent(domain)}?type=pair&min_validity=${minValiditySecs}s`,\n );\n // Response is key PEM then cert PEM, separated by \"--\\n--\"\n const delimiter = Buffer.from(\"--\\n--\");\n const pos = body.indexOf(delimiter);\n if (pos === -1) {\n throw new Error(\"unexpected cert response: no delimiter\");\n }\n const split = pos + 3; // include \"--\\n\"\n const key = body.subarray(0, split);\n const cert = body.subarray(split);\n return { cert, key };\n }\n\n // --- Config ---\n\n /**\n * Get the current serve configuration.\n *\n * The returned ServeConfig has its ETag field populated from the\n * HTTP Etag response header.\n */\n async getServeConfig(): Promise<ServeConfig> {\n const resp = await this.doRequest(\"GET\", \"/localapi/v0/serve-config\");\n if (resp.status !== 200) {\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n if (resp.status === 403) throw new AccessDeniedError(msg);\n if (resp.status === 412) throw new PreconditionsFailedError(msg);\n throw new HttpError(resp.status, msg);\n }\n const config = ServeConfigSchema.parse(parseJSON(resp.body.toString(\"utf-8\"))) as ServeConfig;\n config.ETag = (resp.headers[\"etag\"] as string) ?? \"\";\n return config;\n }\n\n /**\n * Set the serve configuration.\n *\n * The ETag field on the config is sent as the If-Match header\n * for conditional updates.\n */\n async setServeConfig(config: ServeConfig): Promise<void> {\n const headers: Record<string, string> = {};\n if (config.ETag) headers[\"If-Match\"] = config.ETag;\n const body = JSON.stringify(config, jsonReplacer);\n await this.doRequestNice(\"POST\", \"/localapi/v0/serve-config\", body, headers);\n }\n\n /** Close the underlying transport and release resources. */\n destroy(): void {\n this.transport.destroy();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../ts/src/json.ts","../ts/src/errors.ts","../ts/src/safesocket.ts","../ts/src/transport.ts","../ts/src/types.ts","../ts/src/client.ts"],"sourcesContent":["declare global {\n interface JSON {\n rawJSON(value: string): unknown;\n }\n}\n\n/** JSON reviver that converts large integers to BigInt to avoid precision loss. */\nexport const jsonReviver = (_key: string, value: unknown, context?: { source?: string }) => {\n if (typeof value === \"number\" && context?.source && !Number.isSafeInteger(value) && /^-?\\d+$/.test(context.source)) {\n return BigInt(context.source);\n }\n return value;\n};\n\n/** Parse a JSON string using {@link jsonReviver} for BigInt-safe integer handling. */\nexport const parseJSON = (str: string) => JSON.parse(str, jsonReviver);\n\n/** JSON replacer that serializes BigInt values as raw JSON numbers. */\nexport const jsonReplacer = (_key: string, value: unknown) =>\n typeof value === \"bigint\" ? JSON.rawJSON(value.toString()) : value;\n","/** Base error for all Tailscale Local API errors. */\nexport class TailscaleError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TailscaleError\";\n }\n}\n\n/** Raised when the server returns HTTP 403. */\nexport class AccessDeniedError extends TailscaleError {\n constructor(message: string) {\n super(`Access denied: ${message}`);\n this.name = \"AccessDeniedError\";\n }\n}\n\n/** Raised when the server returns HTTP 412. */\nexport class PreconditionsFailedError extends TailscaleError {\n constructor(message: string) {\n super(`Preconditions failed: ${message}`);\n this.name = \"PreconditionsFailedError\";\n }\n}\n\n/** Raised when a WhoIs lookup returns HTTP 404. */\nexport class PeerNotFoundError extends TailscaleError {\n constructor(message: string) {\n super(`Peer not found: ${message}`);\n this.name = \"PeerNotFoundError\";\n }\n}\n\n/** Raised when the connection to tailscaled fails. */\nexport class ConnectionError extends TailscaleError {\n constructor(message: string) {\n super(message);\n this.name = \"ConnectionError\";\n }\n}\n\n/** Raised when tailscaled is not running. */\nexport class DaemonNotRunningError extends ConnectionError {\n constructor(message: string) {\n super(message);\n this.name = \"DaemonNotRunningError\";\n }\n}\n\n/** Raised for unexpected HTTP status codes. */\nexport class HttpError extends TailscaleError {\n public readonly status: number;\n\n constructor(status: number, message: string) {\n super(`HTTP ${status}: ${message}`);\n this.name = \"HttpError\";\n this.status = status;\n }\n}\n\n/** Extract error message from a JSON body like Go's errorMessageFromBody. */\nexport function errorMessageFromBody(body: string): string | undefined {\n try {\n const data = JSON.parse(body);\n return data?.error;\n } catch {\n return undefined;\n }\n}\n","import { execFile } from \"node:child_process\";\nimport { readFile, readlink } from \"node:fs/promises\";\nimport { platform } from \"node:os\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nexport const LOCAL_API_HOST = \"local-tailscaled.sock\";\nexport const CURRENT_CAP_VERSION = 131;\n\nexport interface PortAndToken {\n port: number;\n token: string;\n}\n\n/** Return the default socket path for the current platform. */\nexport function defaultSocketPath(): string {\n if (platform() === \"darwin\") {\n return \"/var/run/tailscaled.socket\";\n }\n // Linux and other Unix\n return \"/var/run/tailscale/tailscaled.sock\";\n}\n\n/** Attempt to discover macOS TCP port and token for tailscaled. */\nexport async function localTcpPortAndToken(): Promise<PortAndToken | undefined> {\n if (platform() !== \"darwin\") {\n return undefined;\n }\n\n // Try lsof method first (macOS GUI app)\n const result = await readMacosSameUserProof();\n if (result) return result;\n\n // Try filesystem method (macOS system extension)\n return readMacsysSameUserProof();\n}\n\nconst execFileP = promisify(execFile);\n\nasync function readMacosSameUserProof(): Promise<PortAndToken | undefined> {\n try {\n const uid = process.getuid?.();\n if (uid === undefined) return undefined;\n\n const { stdout: output } = await execFileP(\"lsof\", [\n \"-n\",\n \"-a\",\n `-u${uid}`,\n \"-c\",\n \"IPNExtension\",\n \"-F\",\n ]);\n return parseLsofOutput(output);\n } catch {\n return undefined;\n }\n}\n\n/** Parse lsof -F output looking for sameuserproof-PORT-TOKEN. */\nexport function parseLsofOutput(output: string): PortAndToken | undefined {\n const needle = \".tailscale.ipn.macos/sameuserproof-\";\n for (const line of output.split(\"\\n\")) {\n const idx = line.indexOf(needle);\n if (idx === -1) continue;\n const rest = line.slice(idx + needle.length);\n const dash = rest.indexOf(\"-\");\n if (dash === -1) continue;\n const portStr = rest.slice(0, dash);\n const token = rest.slice(dash + 1);\n const port = parseInt(portStr, 10);\n if (!isNaN(port)) {\n return { port, token };\n }\n }\n return undefined;\n}\n\nasync function readMacsysSameUserProof(\n sharedDir = \"/Library/Tailscale\",\n): Promise<PortAndToken | undefined> {\n try {\n const portPath = join(sharedDir, \"ipnport\");\n const portStr = await readlink(portPath, \"utf-8\");\n const port = parseInt(portStr, 10);\n if (isNaN(port)) return undefined;\n const tokenPath = join(sharedDir, `sameuserproof-${port}`);\n const tokenRaw = await readFile(tokenPath, \"utf-8\");\n const token = tokenRaw.trim();\n return { port, token };\n } catch {\n return undefined;\n }\n}\n","import * as http from \"node:http\";\nimport {\n CURRENT_CAP_VERSION,\n LOCAL_API_HOST,\n type PortAndToken,\n defaultSocketPath,\n localTcpPortAndToken,\n} from \"./safesocket.js\";\n\nexport interface TransportOptions {\n socketPath?: string;\n useSocketOnly?: boolean;\n}\n\n/**\n * Discover TCP port and token for this request.\n */\nasync function resolvePortAndToken(useSocketOnly: boolean): Promise<PortAndToken | undefined> {\n if (useSocketOnly) return undefined;\n return localTcpPortAndToken();\n}\n\n/**\n * HTTP transport that connects to tailscaled.\n * Reuses connections via Node.js http.Agent keep-alive.\n * Port and token are discovered per-request (matching Go's behavior),\n * so the client adapts to daemon restarts and late starts.\n */\nexport class Transport {\n private readonly socketPath: string;\n private readonly useSocketOnly: boolean;\n private readonly agent: http.Agent;\n\n constructor(opts: TransportOptions = {}) {\n this.socketPath = opts.socketPath ?? defaultSocketPath();\n this.useSocketOnly = opts.useSocketOnly ?? false;\n\n // Single agent with keep-alive — pools connections by host:port key\n this.agent = new http.Agent({\n keepAlive: true,\n keepAliveMsecs: 60_000,\n });\n }\n\n async request(\n method: string,\n path: string,\n body?: Buffer | string,\n extraHeaders?: Record<string, string>,\n ): Promise<{ status: number; body: Buffer; headers: http.IncomingHttpHeaders }> {\n const portAndToken = await resolvePortAndToken(this.useSocketOnly);\n\n return new Promise((resolve, reject) => {\n const headers: Record<string, string> = {\n Host: LOCAL_API_HOST,\n \"Tailscale-Cap\": String(CURRENT_CAP_VERSION),\n ...extraHeaders,\n };\n\n if (portAndToken) {\n const cred = Buffer.from(`:${portAndToken.token}`).toString(\"base64\");\n headers[\"Authorization\"] = `Basic ${cred}`;\n }\n\n const options: http.RequestOptions = {\n method,\n path,\n headers,\n agent: this.agent,\n };\n\n if (portAndToken) {\n options.host = \"127.0.0.1\";\n options.port = portAndToken.port;\n } else {\n options.socketPath = this.socketPath;\n }\n\n const req = http.request(options, (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n resolve({\n status: res.statusCode ?? 0,\n body: Buffer.concat(chunks),\n headers: res.headers,\n });\n });\n res.on(\"error\", reject);\n });\n\n req.on(\"error\", reject);\n\n if (body !== undefined) {\n req.write(body);\n }\n req.end();\n });\n }\n\n destroy(): void {\n this.agent.destroy();\n }\n}\n","// Code generated by cmd/typegen; DO NOT EDIT.\n\nimport { z } from \"zod\";\n\nconst goSlice = <T extends z.ZodTypeAny>(item: T) =>\n z.array(item).nullish().transform(v => v ?? []);\n\nconst goMap = <V extends z.ZodTypeAny>(val: V) =>\n z.record(z.string(), val).nullish().transform(v => v ?? {});\n\nconst int64 = z.union([z.number().int(), z.bigint()]).transform(v => BigInt(v));\n\n/**\n * Location represents geographical location data about a\n * Tailscale host. Location is optional and only set if\n * explicitly declared by a node.\n */\nexport const LocationSchema = z.object({\n /** User friendly country name, with proper capitalization (\"Canada\") */\n Country: z.string().nullish(),\n /** ISO 3166-1 alpha-2 in upper case (\"CA\") */\n CountryCode: z.string().nullish(),\n /** User friendly city name, with proper capitalization (\"Squamish\") */\n City: z.string().nullish(),\n /**\n * CityCode is a short code representing the city in upper case.\n * CityCode is used to disambiguate a city from another location\n * with the same city name. It uniquely identifies a particular\n * geographical location, within the tailnet.\n * IATA, ICAO or ISO 3166-2 codes are recommended (\"YSE\")\n */\n CityCode: z.string().nullish(),\n /**\n * Latitude, Longitude are optional geographical coordinates of the node, in degrees.\n * No particular accuracy level is promised; the coordinates may simply be the center of the city or country.\n */\n Latitude: z.number().nullish(),\n Longitude: z.number().nullish(),\n /**\n * Priority determines the order of use of an exit node when a\n * location based preference matches more than one exit node,\n * the node with the highest priority wins. Nodes of equal\n * probability may be selected arbitrarily.\n * \n * A value of 0 means the exit node does not have a priority\n * preference. A negative int is not allowed.\n */\n Priority: int64.nullish(),\n});\nexport type Location = z.infer<typeof LocationSchema>;\n\n/**\n * PeerStatus describes a peer node and its current state.\n * WARNING: The fields in PeerStatus are merged by the AddPeer method in the StatusBuilder.\n * When adding a new field to PeerStatus, you must update AddPeer to handle merging\n * the new field. The AddPeer function is responsible for combining multiple updates\n * to the same peer, and any new field that is not merged properly may lead to\n * inconsistencies or lost data in the peer status.\n */\nexport const PeerStatusSchema = z.object({\n ID: z.string().default(\"\"),\n PublicKey: z.string().default(\"\"),\n /** HostInfo's Hostname (not a DNS name or necessarily unique) */\n HostName: z.string().default(\"\"),\n /**\n * DNSName is the Peer's FQDN. It ends with a dot.\n * It has the form \"host.<MagicDNSSuffix>.\"\n */\n DNSName: z.string().default(\"\"),\n /** HostInfo.OS */\n OS: z.string().default(\"\"),\n UserID: int64.default(0n),\n /**\n * AltSharerUserID is the user who shared this node\n * if it's different than UserID. Otherwise it's zero.\n */\n AltSharerUserID: int64.nullish(),\n /** TailscaleIPs are the IP addresses assigned to the node. */\n TailscaleIPs: goSlice(z.string()),\n /** AllowedIPs are IP addresses allowed to route to this node. */\n AllowedIPs: goSlice(z.string()),\n /**\n * Tags are the list of ACL tags applied to this node.\n * See tailscale.com/tailcfg#Node.Tags for more information.\n */\n Tags: goSlice(z.string()),\n /**\n * PrimaryRoutes are the routes this node is currently the primary\n * subnet router for, as determined by the control plane. It does\n * not include the IPs in TailscaleIPs.\n */\n PrimaryRoutes: goSlice(z.string()),\n /** Endpoints: */\n Addrs: goSlice(z.string()),\n /** one of Addrs, or unique if roaming */\n CurAddr: z.string().default(\"\"),\n /** DERP region */\n Relay: z.string().default(\"\"),\n /** peer relay address (ip:port:vni) */\n PeerRelay: z.string().default(\"\"),\n RxBytes: int64.default(0n),\n TxBytes: int64.default(0n),\n /** time registered with tailcontrol */\n Created: z.string().default(\"\"),\n /** time last packet sent */\n LastWrite: z.string().default(\"\"),\n /** last seen to tailcontrol; only present if offline */\n LastSeen: z.string().default(\"\"),\n /** with local wireguard */\n LastHandshake: z.string().default(\"\"),\n /** whether node is connected to the control plane */\n Online: z.boolean().default(false),\n /** true if this is the currently selected exit node. */\n ExitNode: z.boolean().default(false),\n /** true if this node can be an exit node (offered && approved) */\n ExitNodeOption: z.boolean().default(false),\n /**\n * Active is whether the node was recently active. The\n * definition is somewhat undefined but has historically and\n * currently means that there was some packet sent to this\n * peer in the past two minutes. That definition is subject to\n * change.\n */\n Active: z.boolean().default(false),\n /** PeerAPIURL are the URLs of the node's PeerAPI servers. */\n PeerAPIURL: goSlice(z.string()),\n /** TaildropTargetStatus represents the node's eligibility to have files shared to it. */\n TaildropTarget: int64.default(0n),\n /** Reason why this peer cannot receive files. Empty if CanReceiveFiles=true */\n NoFileSharingReason: z.string().default(\"\"),\n /**\n * Capabilities are capabilities that the node has.\n * They're free-form strings, but should be in the form of URLs/URIs\n * such as:\n * \"https://tailscale.com/cap/is-admin\"\n * \"https://tailscale.com/cap/file-sharing\"\n * \"funnel\"\n * \n * Deprecated: use CapMap instead. See https://github.com/tailscale/tailscale/issues/11508\n * Every value is Capabilities is also a key in CapMap, even if it\n * has no values in that map.\n */\n Capabilities: goSlice(z.string()),\n /** CapMap is a map of capabilities to their values. */\n CapMap: goMap(goSlice(z.unknown())),\n /** SSH_HostKeys are the node's SSH host keys, if known. */\n sshHostKeys: goSlice(z.string()),\n /**\n * ShareeNode indicates this node exists in the netmap because\n * it's owned by a shared-to user and that node might connect\n * to us. These nodes should be hidden by \"tailscale status\"\n * etc by default.\n */\n ShareeNode: z.boolean().nullish(),\n /**\n * InNetworkMap means that this peer was seen in our latest network map.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InNetworkMap: z.boolean().default(false),\n /**\n * InMagicSock means that this peer is being tracked by magicsock.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InMagicSock: z.boolean().default(false),\n /**\n * InEngine means that this peer is tracked by the wireguard engine.\n * In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.\n */\n InEngine: z.boolean().default(false),\n /**\n * Expired means that this peer's node key has expired, based on either\n * information from control or optimisically set on the client if the\n * expiration time has passed.\n */\n Expired: z.boolean().nullish(),\n /**\n * KeyExpiry, if present, is the time at which the node key expired or\n * will expire.\n */\n KeyExpiry: z.string().nullish(),\n Location: LocationSchema.nullish(),\n});\nexport type PeerStatus = z.infer<typeof PeerStatusSchema>;\n\n/** ExitNodeStatus describes the current exit node. */\nexport const ExitNodeStatusSchema = z.object({\n /** ID is the exit node's ID. */\n ID: z.string().default(\"\"),\n /** Online is whether the exit node is alive. */\n Online: z.boolean().default(false),\n /** TailscaleIPs are the exit node's IP addresses assigned to the node. */\n TailscaleIPs: goSlice(z.string()),\n});\nexport type ExitNodeStatus = z.infer<typeof ExitNodeStatusSchema>;\n\n/** TailnetStatus is information about a Tailscale network (\"tailnet\"). */\nexport const TailnetStatusSchema = z.object({\n /** Name is the name of the network that's currently in use. */\n Name: z.string().default(\"\"),\n /**\n * MagicDNSSuffix is the network's MagicDNS suffix for nodes\n * in the network such as \"userfoo.tailscale.net\".\n * There are no surrounding dots.\n * MagicDNSSuffix should be populated regardless of whether a domain\n * has MagicDNS enabled.\n */\n MagicDNSSuffix: z.string().default(\"\"),\n /**\n * MagicDNSEnabled is whether or not the network has MagicDNS enabled.\n * Note that the current device may still not support MagicDNS if\n * `--accept-dns=false` was used.\n */\n MagicDNSEnabled: z.boolean().default(false),\n});\nexport type TailnetStatus = z.infer<typeof TailnetStatusSchema>;\n\n/**\n * A UserProfile is display-friendly data for a [User].\n * It includes the LoginName for display purposes but *not* the Provider.\n * It also includes derived data from one of the user's logins.\n */\nexport const UserProfileSchema = z.object({\n ID: int64.default(0n),\n /** \"alice@smith.com\"; for display purposes only (provider is not listed) */\n LoginName: z.string().default(\"\"),\n /** \"Alice Smith\" */\n DisplayName: z.string().default(\"\"),\n ProfilePicURL: z.string().nullish(),\n});\nexport type UserProfile = z.infer<typeof UserProfileSchema>;\n\n/**\n * ClientVersion is information about the latest client version that's available\n * for the client (and whether they're already running it).\n * \n * It does not include a URL to download the client, as that varies by platform.\n */\nexport const ClientVersionSchema = z.object({\n /** RunningLatest is true if the client is running the latest build. */\n RunningLatest: z.boolean().nullish(),\n /**\n * LatestVersion is the latest version.Short (\"1.34.2\") version available\n * for download for the client's platform and packaging type.\n * It won't be populated if RunningLatest is true.\n */\n LatestVersion: z.string().nullish(),\n /**\n * UrgentSecurityUpdate is set when the client is missing an important\n * security update. That update may be in LatestVersion or earlier.\n * UrgentSecurityUpdate should not be set if RunningLatest is false.\n */\n UrgentSecurityUpdate: z.boolean().nullish(),\n /**\n * Notify is whether the client should do an OS-specific notification about\n * a new version being available. This should not be populated if\n * RunningLatest is true. The client should not notify multiple times for\n * the same LatestVersion value.\n */\n Notify: z.boolean().nullish(),\n /**\n * NotifyURL is a URL to open in the browser when the user clicks on the\n * notification, when Notify is true.\n */\n NotifyURL: z.string().nullish(),\n /** NotifyText is the text to show in the notification, when Notify is true. */\n NotifyText: z.string().nullish(),\n});\nexport type ClientVersion = z.infer<typeof ClientVersionSchema>;\n\n/** Status represents the entire state of the IPN network. */\nexport const StatusSchema = z.object({\n /** Version is the daemon's long version (see version.Long). */\n Version: z.string().default(\"\"),\n /**\n * TUN is whether /dev/net/tun (or equivalent kernel interface) is being\n * used. If false, it's running in userspace mode.\n */\n TUN: z.boolean().default(false),\n /**\n * BackendState is an ipn.State string value:\n * \"NoState\", \"NeedsLogin\", \"NeedsMachineAuth\", \"Stopped\",\n * \"Starting\", \"Running\".\n */\n BackendState: z.string().default(\"\"),\n /** HaveNodeKey is whether the current profile has a node key configured. */\n HaveNodeKey: z.boolean().nullish(),\n /** current URL provided by control to authorize client */\n AuthURL: z.string().default(\"\"),\n /** Tailscale IP(s) assigned to this node */\n TailscaleIPs: goSlice(z.string()),\n Self: PeerStatusSchema.prefault({}),\n /**\n * ExitNodeStatus describes the current exit node.\n * If nil, an exit node is not in use.\n */\n ExitNodeStatus: ExitNodeStatusSchema.nullish(),\n /**\n * Health contains health check problems.\n * Empty means everything is good. (or at least that no known\n * problems are detected)\n */\n Health: goSlice(z.string()),\n /**\n * This field is the legacy name of CurrentTailnet.MagicDNSSuffix.\n * \n * Deprecated: use CurrentTailnet.MagicDNSSuffix instead.\n */\n MagicDNSSuffix: z.string().default(\"\"),\n /**\n * CurrentTailnet is information about the tailnet that the node\n * is currently connected to. When not connected, this field is nil.\n */\n CurrentTailnet: TailnetStatusSchema.nullish(),\n /**\n * CertDomains are the set of DNS names for which the control\n * plane server will assist with provisioning TLS\n * certificates. See SetDNSRequest for dns-01 ACME challenges\n * for e.g. LetsEncrypt. These names are FQDNs without\n * trailing periods, and without any \"_acme-challenge.\" prefix.\n */\n CertDomains: goSlice(z.string()),\n /** Peer is the state of each peer, keyed by each peer's current public key. */\n Peer: goMap(PeerStatusSchema),\n /**\n * User contains profile information about UserIDs referenced by\n * PeerStatus.UserID, PeerStatus.AltSharerUserID, etc.\n */\n User: goMap(UserProfileSchema),\n /**\n * ClientVersion, when non-nil, contains information about the latest\n * version of the Tailscale client that's available. Depending on\n * the platform and client settings, it may not be available.\n */\n ClientVersion: ClientVersionSchema.nullish(),\n});\nexport type Status = z.infer<typeof StatusSchema>;\n\n/** Service represents a service running on a node. */\nexport const ServiceSchema = z.object({\n /**\n * Proto is the type of service. It's usually the constant TCP\n * or UDP (\"tcp\" or \"udp\"), but it can also be one of the\n * following meta service values:\n * \n * * \"peerapi4\": peerapi is available on IPv4; Port is the\n * port number that the peerapi is running on the\n * node's Tailscale IPv4 address.\n * * \"peerapi6\": peerapi is available on IPv6; Port is the\n * port number that the peerapi is running on the\n * node's Tailscale IPv6 address.\n * * \"peerapi-dns-proxy\": the local peerapi service supports\n * being a DNS proxy (when the node is an exit\n * node). For this service, the Port number must only be 1.\n */\n Proto: z.string().default(\"\"),\n /**\n * Port is the port number.\n * \n * For Proto \"peerapi-dns\", it must be 1.\n */\n Port: z.number().default(0),\n /**\n * Description is the textual description of the service,\n * usually the process name that's running.\n */\n Description: z.string().nullish(),\n});\nexport type Service = z.infer<typeof ServiceSchema>;\n\n/** NetInfo contains information about the host's network state. */\nexport const NetInfoSchema = z.object({\n /**\n * MappingVariesByDestIP says whether the host's NAT mappings\n * vary based on the destination IP.\n */\n MappingVariesByDestIP: z.boolean().nullish(),\n /** WorkingIPv6 is whether the host has IPv6 internet connectivity. */\n WorkingIPv6: z.boolean().nullish(),\n /**\n * OSHasIPv6 is whether the OS supports IPv6 at all, regardless of\n * whether IPv6 internet connectivity is available.\n */\n OSHasIPv6: z.boolean().nullish(),\n /** WorkingUDP is whether the host has UDP internet connectivity. */\n WorkingUDP: z.boolean().nullish(),\n /**\n * WorkingICMPv4 is whether ICMPv4 works.\n * Empty means not checked.\n */\n WorkingICMPv4: z.boolean().nullish(),\n /**\n * HavePortMap is whether we have an existing portmap open\n * (UPnP, PMP, or PCP).\n */\n HavePortMap: z.boolean().nullish(),\n /**\n * UPnP is whether UPnP appears present on the LAN.\n * Empty means not checked.\n */\n UPnP: z.boolean().nullish(),\n /**\n * PMP is whether NAT-PMP appears present on the LAN.\n * Empty means not checked.\n */\n PMP: z.boolean().nullish(),\n /**\n * PCP is whether PCP appears present on the LAN.\n * Empty means not checked.\n */\n PCP: z.boolean().nullish(),\n /**\n * PreferredDERP is this node's preferred (home) DERP region ID.\n * This is where the node expects to be contacted to begin a\n * peer-to-peer connection. The node might be be temporarily\n * connected to multiple DERP servers (to speak to other nodes\n * that are located elsewhere) but PreferredDERP is the region ID\n * that the node subscribes to traffic at.\n * Zero means disconnected or unknown.\n */\n PreferredDERP: int64.nullish(),\n /** LinkType is the current link type, if known. */\n LinkType: z.string().nullish(),\n /**\n * DERPLatency is the fastest recent time to reach various\n * DERP STUN servers, in seconds. The map key is the\n * \"regionID-v4\" or \"-v6\"; it was previously the DERP server's\n * STUN host:port.\n * \n * This should only be updated rarely, or when there's a\n * material change, as any change here also gets uploaded to\n * the control plane.\n */\n DERPLatency: goMap(z.number()),\n /**\n * FirewallMode encodes both which firewall mode was selected and why.\n * It is Linux-specific (at least as of 2023-08-19) and is meant to help\n * debug iptables-vs-nftables issues. The string is of the form\n * \"{nft,ift}-REASON\", like \"nft-forced\" or \"ipt-default\". Empty means\n * either not Linux or a configuration in which the host firewall rules\n * are not managed by tailscaled.\n */\n FirewallMode: z.string().nullish(),\n});\nexport type NetInfo = z.infer<typeof NetInfoSchema>;\n\n/**\n * TPMInfo contains information about a TPM 2.0 device present on a node.\n * All fields are read from TPM_CAP_TPM_PROPERTIES, see Part 2, section 6.13 of\n * https://trustedcomputinggroup.org/resource/tpm-library-specification/.\n */\nexport const TPMInfoSchema = z.object({\n /**\n * Manufacturer is a 4-letter code from section 4.1 of\n * https://trustedcomputinggroup.org/resource/vendor-id-registry/,\n * for example \"MSFT\" for Microsoft.\n * Read from TPM_PT_MANUFACTURER.\n */\n Manufacturer: z.string().nullish(),\n /**\n * Vendor is a vendor ID string, up to 16 characters.\n * Read from TPM_PT_VENDOR_STRING_*.\n */\n Vendor: z.string().nullish(),\n /**\n * Model is a vendor-defined TPM model.\n * Read from TPM_PT_VENDOR_TPM_TYPE.\n */\n Model: int64.nullish(),\n /**\n * FirmwareVersion is the version number of the firmware.\n * Read from TPM_PT_FIRMWARE_VERSION_*.\n */\n FirmwareVersion: int64.nullish(),\n /**\n * SpecRevision is the TPM 2.0 spec revision encoded as a single number. All\n * revisions can be found at\n * https://trustedcomputinggroup.org/resource/tpm-library-specification/.\n * Before revision 184, TCG used the \"01.83\" format for revision 183.\n */\n SpecRevision: int64.nullish(),\n /**\n * FamilyIndicator is the TPM spec family, like \"2.0\".\n * Read from TPM_PT_FAMILY_INDICATOR.\n */\n FamilyIndicator: z.string().nullish(),\n});\nexport type TPMInfo = z.infer<typeof TPMInfoSchema>;\n\n/**\n * Hostinfo contains a summary of a Tailscale host.\n * \n * Because it contains pointers (slices), this type should not be used\n * as a value type.\n */\nexport const HostinfoSchema = z.object({\n /** version of this code (in version.Long format) */\n IPNVersion: z.string().nullish(),\n /** logtail ID of frontend instance */\n FrontendLogID: z.string().nullish(),\n /** logtail ID of backend instance */\n BackendLogID: z.string().nullish(),\n /** operating system the client runs on (a version.OS value) */\n OS: z.string().nullish(),\n /**\n * OSVersion is the version of the OS, if available.\n * \n * For Android, it's like \"10\", \"11\", \"12\", etc. For iOS and macOS it's like\n * \"15.6.1\" or \"12.4.0\". For Windows it's like \"10.0.19044.1889\". For\n * FreeBSD it's like \"12.3-STABLE\".\n * \n * For Linux, prior to Tailscale 1.32, we jammed a bunch of fields into this\n * string on Linux, like \"Debian 10.4; kernel=xxx; container; env=kn\" and so\n * on. As of Tailscale 1.32, this is simply the kernel version on Linux, like\n * \"5.10.0-17-amd64\".\n */\n OSVersion: z.string().nullish(),\n /** best-effort whether the client is running in a container */\n Container: z.boolean().nullish(),\n /** a hostinfo.EnvType in string form */\n Env: z.string().nullish(),\n /** \"debian\", \"ubuntu\", \"nixos\", ... */\n Distro: z.string().nullish(),\n /** \"20.04\", ... */\n DistroVersion: z.string().nullish(),\n /** \"jammy\", \"bullseye\", ... */\n DistroCodeName: z.string().nullish(),\n /** App is used to disambiguate Tailscale clients that run using tsnet. */\n App: z.string().nullish(),\n /** if a desktop was detected on Linux */\n Desktop: z.boolean().nullish(),\n /** Tailscale package to disambiguate (\"choco\", \"appstore\", etc; \"\" for unknown) */\n Package: z.string().nullish(),\n /** mobile phone model (\"Pixel 3a\", \"iPhone12,3\") */\n DeviceModel: z.string().nullish(),\n /** macOS/iOS APNs device token for notifications (and Android in the future) */\n PushDeviceToken: z.string().nullish(),\n /** name of the host the client runs on */\n Hostname: z.string().nullish(),\n /** indicates whether the host is blocking incoming connections */\n ShieldsUp: z.boolean().nullish(),\n /** indicates this node exists in netmap because it's owned by a shared-to user */\n ShareeNode: z.boolean().nullish(),\n /** indicates that the user has opted out of sending logs and support */\n NoLogsNoSupport: z.boolean().nullish(),\n /**\n * WireIngress indicates that the node would like to be wired up server-side\n * (DNS, etc) to be able to use Tailscale Funnel, even if it's not currently\n * enabled. For example, the user might only use it for intermittent\n * foreground CLI serve sessions, for which they'd like it to work right\n * away, even if it's disabled most of the time. As an optimization, this is\n * only sent if IngressEnabled is false, as IngressEnabled implies that this\n * option is true.\n */\n WireIngress: z.boolean().nullish(),\n /** if the node has any funnel endpoint enabled */\n IngressEnabled: z.boolean().nullish(),\n /** indicates that the node has opted-in to admin-console-drive remote updates */\n AllowsUpdate: z.boolean().nullish(),\n /** the current host's machine type (uname -m) */\n Machine: z.string().nullish(),\n /** GOARCH value (of the built binary) */\n GoArch: z.string().nullish(),\n /** GOARM, GOAMD64, etc (of the built binary) */\n GoArchVar: z.string().nullish(),\n /** Go version binary was built with */\n GoVersion: z.string().nullish(),\n /** set of IP ranges this client can route */\n RoutableIPs: goSlice(z.string()),\n /** set of ACL tags this node wants to claim */\n RequestTags: goSlice(z.string()),\n /** MAC address(es) to send Wake-on-LAN packets to wake this node (lowercase hex w/ colons) */\n WoLMACs: goSlice(z.string()),\n /** services advertised by this machine */\n Services: goSlice(ServiceSchema),\n NetInfo: NetInfoSchema.nullish(),\n /** if advertised */\n sshHostKeys: goSlice(z.string()),\n Cloud: z.string().nullish(),\n /** if the client is running in userspace (netstack) mode */\n Userspace: z.boolean().nullish(),\n /** if the client's subnet router is running in userspace (netstack) mode */\n UserspaceRouter: z.boolean().nullish(),\n /** if the client is running the app-connector service */\n AppConnector: z.boolean().nullish(),\n /** opaque hash of the most recent list of tailnet services, change in hash indicates config should be fetched via c2n */\n ServicesHash: z.string().nullish(),\n /** the client’s selected exit node, empty when unselected. */\n ExitNodeID: z.string().nullish(),\n /**\n * Location represents geographical location data about a\n * Tailscale host. Location is optional and only set if\n * explicitly declared by a node.\n */\n Location: LocationSchema.nullish(),\n /** TPM device metadata, if available */\n TPM: TPMInfoSchema.nullish(),\n /**\n * StateEncrypted reports whether the node state is stored encrypted on\n * disk. The actual mechanism is platform-specific:\n * * Apple nodes use the Keychain\n * * Linux and Windows nodes use the TPM\n * * Android apps use EncryptedSharedPreferences\n */\n StateEncrypted: z.boolean().nullish(),\n});\nexport type Hostinfo = z.infer<typeof HostinfoSchema>;\n\n/** Resolver is the configuration for one DNS resolver. */\nexport const ResolverSchema = z.object({\n /**\n * Addr is the address of the DNS resolver, one of:\n * - A plain IP address for a \"classic\" UDP+TCP DNS resolver.\n * This is the common format as sent by the control plane.\n * - An IP:port, for tests.\n * - \"https://resolver.com/path\" for DNS over HTTPS; currently\n * as of 2022-09-08 only used for certain well-known resolvers\n * (see the publicdns package) for which the IP addresses to dial DoH are\n * known ahead of time, so bootstrap DNS resolution is not required.\n * - \"http://node-address:port/path\" for DNS over HTTP over WireGuard. This\n * is implemented in the PeerAPI for exit nodes and app connectors.\n * - [TODO] \"tls://resolver.com\" for DNS over TCP+TLS\n */\n Addr: z.string().nullish(),\n /**\n * BootstrapResolution is an optional suggested resolution for the\n * DoT/DoH resolver, if the resolver URL does not reference an IP\n * address directly.\n * BootstrapResolution may be empty, in which case clients should\n * look up the DoT/DoH server using their local \"classic\" DNS\n * resolver.\n * \n * As of 2022-09-08, BootstrapResolution is not yet used.\n */\n BootstrapResolution: goSlice(z.string()),\n /**\n * UseWithExitNode designates that this resolver should continue to be used when an\n * exit node is in use. Normally, DNS resolution is delegated to the exit node but\n * there are situations where it is preferable to still use a Split DNS server and/or\n * global DNS server instead of the exit node.\n */\n UseWithExitNode: z.boolean().nullish(),\n});\nexport type Resolver = z.infer<typeof ResolverSchema>;\n\n/** Node is a Tailscale device in a tailnet. */\nexport const NodeSchema = z.object({\n ID: int64.default(0n),\n StableID: z.string().default(\"\"),\n /**\n * Name is the FQDN of the node.\n * It is also the MagicDNS name for the node.\n * It has a trailing dot.\n * e.g. \"host.tail-scale.ts.net.\"\n */\n Name: z.string().default(\"\"),\n /**\n * User is the user who created the node. If ACL tags are in use for the\n * node then it doesn't reflect the ACL identity that the node is running\n * as.\n */\n User: int64.default(0n),\n /** Sharer, if non-zero, is the user who shared this node, if different than User. */\n Sharer: int64.nullish(),\n Key: z.string().default(\"\"),\n /** the zero value if this node does not expire */\n KeyExpiry: z.string().nullish(),\n KeySignature: z.string().nullish(),\n Machine: z.string().nullish(),\n DiscoKey: z.string().nullish(),\n /** Addresses are the IP addresses of this Node directly. */\n Addresses: goSlice(z.string()),\n /**\n * AllowedIPs are the IP ranges to route to this node.\n * \n * As of CapabilityVersion 112, this may be nil (null or undefined) on the wire\n * to mean the same as Addresses. Internally, it is always filled in with\n * its possibly-implicit value.\n */\n AllowedIPs: goSlice(z.string()),\n /** IP+port (public via STUN, and local LANs) */\n Endpoints: goSlice(z.string()),\n /**\n * LegacyDERPString is this node's home LegacyDERPString region ID integer, but shoved into an\n * IP:port string for legacy reasons. The IP address is always \"127.3.3.40\"\n * (a loopback address (127) followed by the digits over the letters DERP on\n * a QWERTY keyboard (3.3.40)). The \"port number\" is the home LegacyDERPString region ID\n * integer.\n * \n * Deprecated: HomeDERP has replaced this, but old servers might still send\n * this field. See tailscale/tailscale#14636. Do not use this field in code\n * other than in the upgradeNode func, which canonicalizes it to HomeDERP\n * if it arrives as a LegacyDERPString string on the wire.\n */\n DERP: z.string().nullish(),\n /**\n * HomeDERP is the modern version of the DERP string field, with just an\n * integer. The client advertises support for this as of capver 111.\n * \n * HomeDERP may be zero if not (yet) known, but ideally always be non-zero\n * for magicsock connectivity to function normally.\n */\n HomeDERP: int64.nullish(),\n Hostinfo: HostinfoSchema.nullish(),\n Created: z.string().nullish(),\n /** if non-zero, the node's capability version; old servers might not send */\n Cap: int64.nullish(),\n /**\n * Tags are the list of ACL tags applied to this node.\n * Tags take the form of `tag:<value>` where value starts\n * with a letter and only contains alphanumerics and dashes `-`.\n * Some valid tag examples:\n * `tag:prod`\n * `tag:database`\n * `tag:lab-1`\n */\n Tags: goSlice(z.string()),\n /**\n * PrimaryRoutes are the routes from AllowedIPs that this node\n * is currently the primary subnet router for, as determined\n * by the control plane. It does not include the self address\n * values from Addresses that are in AllowedIPs.\n */\n PrimaryRoutes: goSlice(z.string()),\n /**\n * LastSeen is when the node was last online. It is not\n * updated when Online is true. It is nil if the current\n * node doesn't have permission to know, or the node\n * has never been online.\n */\n LastSeen: z.string().nullish(),\n /**\n * Online is whether the node is currently connected to the\n * coordination server. A value of nil means unknown, or the\n * current node doesn't have permission to know.\n */\n Online: z.boolean().nullish(),\n /** TODO(crawshaw): replace with MachineStatus */\n MachineAuthorized: z.boolean().nullish(),\n /**\n * Capabilities are capabilities that the node has.\n * They're free-form strings, but should be in the form of URLs/URIs\n * such as:\n * \"https://tailscale.com/cap/is-admin\"\n * \"https://tailscale.com/cap/file-sharing\"\n * \n * Deprecated: use CapMap instead. See https://github.com/tailscale/tailscale/issues/11508\n */\n Capabilities: goSlice(z.string()),\n /**\n * CapMap is a map of capabilities to their optional argument/data values.\n * \n * It is valid for a capability to not have any argument/data values; such\n * capabilities can be tested for using the HasCap method. These type of\n * capabilities are used to indicate that a node has a capability, but there\n * is no additional data associated with it. These were previously\n * represented by the Capabilities field, but can now be represented by\n * CapMap with an empty value.\n * \n * See NodeCapability for more information on keys.\n * \n * Metadata about nodes can be transmitted in 3 ways:\n * 1. MapResponse.Node.CapMap describes attributes that affect behavior for\n * this node, such as which features have been enabled through the admin\n * panel and any associated configuration details.\n * 2. MapResponse.PacketFilter(s) describes access (both IP and application\n * based) that should be granted to peers.\n * 3. MapResponse.Peers[].CapMap describes attributes regarding a peer node,\n * such as which features the peer supports or if that peer is preferred\n * for a particular task vs other peers that could also be chosen.\n */\n CapMap: goMap(goSlice(z.unknown())),\n /**\n * UnsignedPeerAPIOnly means that this node is not signed nor subject to TKA\n * restrictions. However, in exchange for that privilege, it does not get\n * network access. It can only access this node's peerapi, which may not let\n * it do anything. It is the tailscaled client's job to double-check the\n * MapResponse's PacketFilter to verify that its AllowedIPs will not be\n * accepted by the packet filter.\n */\n UnsignedPeerAPIOnly: z.boolean().nullish(),\n /** MagicDNS base name (for normal non-shared-in nodes), FQDN (without trailing dot, for shared-in nodes), or Hostname (if no MagicDNS) */\n ComputedName: z.string().nullish(),\n /** either \"ComputedName\" or \"ComputedName (computedHostIfDifferent)\", if computedHostIfDifferent is set */\n ComputedNameWithHost: z.string().nullish(),\n /** DataPlaneAuditLogID is the per-node logtail ID used for data plane audit logging. */\n DataPlaneAuditLogID: z.string().nullish(),\n /**\n * Expired is whether this node's key has expired. Control may send\n * this; clients are only allowed to set this from false to true. On\n * the client, this is calculated client-side based on a timestamp sent\n * from control, to avoid clock skew issues.\n */\n Expired: z.boolean().nullish(),\n /**\n * SelfNodeV4MasqAddrForThisPeer is the IPv4 that this peer knows the current node as.\n * It may be empty if the peer knows the current node by its native\n * IPv4 address.\n * This field is only populated in a MapResponse for peers and not\n * for the current node.\n * \n * If set, it should be used to masquerade traffic originating from the\n * current node to this peer. The masquerade address is only relevant\n * for this peer and not for other peers.\n * \n * This only applies to traffic originating from the current node to the\n * peer or any of its subnets. Traffic originating from subnet routes will\n * not be masqueraded (e.g. in case of --snat-subnet-routes).\n */\n SelfNodeV4MasqAddrForThisPeer: z.string().nullish(),\n /**\n * SelfNodeV6MasqAddrForThisPeer is the IPv6 that this peer knows the current node as.\n * It may be empty if the peer knows the current node by its native\n * IPv6 address.\n * This field is only populated in a MapResponse for peers and not\n * for the current node.\n * \n * If set, it should be used to masquerade traffic originating from the\n * current node to this peer. The masquerade address is only relevant\n * for this peer and not for other peers.\n * \n * This only applies to traffic originating from the current node to the\n * peer or any of its subnets. Traffic originating from subnet routes will\n * not be masqueraded (e.g. in case of --snat-subnet-routes).\n */\n SelfNodeV6MasqAddrForThisPeer: z.string().nullish(),\n /**\n * IsWireGuardOnly indicates that this is a non-Tailscale WireGuard peer, it\n * is not expected to speak Disco or DERP, and it must have Endpoints in\n * order to be reachable.\n */\n IsWireGuardOnly: z.boolean().nullish(),\n /**\n * IsJailed indicates that this node is jailed and should not be allowed\n * initiate connections, however outbound connections to it should still be\n * allowed.\n */\n IsJailed: z.boolean().nullish(),\n /**\n * ExitNodeDNSResolvers is the list of DNS servers that should be used when this\n * node is marked IsWireGuardOnly and being used as an exit node.\n */\n ExitNodeDNSResolvers: goSlice(ResolverSchema),\n});\nexport type Node = z.infer<typeof NodeSchema>;\n\n/**\n * TCPPortHandler describes what to do when handling a TCP\n * connection.\n */\nexport const TCPPortHandlerSchema = z.object({\n /**\n * HTTPS, if true, means that tailscaled should handle this connection as an\n * HTTPS request as configured by ServeConfig.Web.\n * \n * It is mutually exclusive with TCPForward.\n */\n HTTPS: z.boolean().nullish(),\n /**\n * HTTP, if true, means that tailscaled should handle this connection as an\n * HTTP request as configured by ServeConfig.Web.\n * \n * It is mutually exclusive with TCPForward.\n */\n HTTP: z.boolean().nullish(),\n /**\n * TCPForward is the IP:port to forward TCP connections to.\n * Whether or not TLS is terminated by tailscaled depends on\n * TerminateTLS.\n * \n * It is mutually exclusive with HTTPS.\n */\n TCPForward: z.string().nullish(),\n /**\n * TerminateTLS, if non-empty, means that tailscaled should terminate the\n * TLS connections before forwarding them to TCPForward, permitting only the\n * SNI name with this value. It is only used if TCPForward is non-empty.\n * (the HTTPS mode uses ServeConfig.Web)\n */\n TerminateTLS: z.string().nullish(),\n /**\n * ProxyProtocol indicates whether to send a PROXY protocol header\n * before forwarding the connection to TCPForward.\n * \n * This is only valid if TCPForward is non-empty.\n */\n ProxyProtocol: int64.nullish(),\n});\nexport type TCPPortHandler = z.infer<typeof TCPPortHandlerSchema>;\n\n/** HTTPHandler is either a path or a proxy to serve. */\nexport const HTTPHandlerSchema = z.object({\n /** absolute path to directory or file to serve */\n Path: z.string().nullish(),\n /** http://localhost:3000/, localhost:3030, 3030 */\n Proxy: z.string().nullish(),\n /** plaintext to serve (primarily for testing) */\n Text: z.string().nullish(),\n /** peer capabilities to forward in grant header, e.g. example.com/cap/mon */\n AcceptAppCaps: goSlice(z.string()),\n /**\n * Redirect, if not empty, is the target URL to redirect requests to.\n * By default, we redirect with HTTP 302 (Found) status.\n * If Redirect starts with '<httpcode>:', then we use that status instead.\n * \n * The target URL supports the following expansion variables:\n * - ${HOST}: replaced with the request's Host header value\n * - ${REQUEST_URI}: replaced with the request's full URI (path and query string)\n */\n Redirect: z.string().nullish(),\n});\nexport type HTTPHandler = z.infer<typeof HTTPHandlerSchema>;\n\n/** WebServerConfig describes a web server's configuration. */\nexport const WebServerConfigSchema = z.object({\n /** mountPoint => handler */\n Handlers: goMap(HTTPHandlerSchema),\n});\nexport type WebServerConfig = z.infer<typeof WebServerConfigSchema>;\n\n/**\n * ServiceConfig contains the config information for a single service.\n * it contains a bool to indicate if the service is in Tun mode (L3 forwarding).\n * If the service is not in Tun mode, the service is configured by the L4 forwarding\n * (TCP ports) and/or the L7 forwarding (http handlers) information.\n */\nexport const ServiceConfigSchema = z.object({\n /**\n * TCP are the list of TCP port numbers that tailscaled should handle for\n * the Tailscale IP addresses. (not subnet routers, etc)\n */\n TCP: goMap(TCPPortHandlerSchema),\n /**\n * Web maps from \"$SNI_NAME:$PORT\" to a set of HTTP handlers\n * keyed by mount point (\"/\", \"/foo\", etc)\n */\n Web: goMap(WebServerConfigSchema),\n /** Tun determines if the service should be using L3 forwarding (Tun mode). */\n Tun: z.boolean().nullish(),\n});\nexport type ServiceConfig = z.infer<typeof ServiceConfigSchema>;\n\nconst _ServeConfigRef: z.ZodTypeAny = z.lazy(() => ServeConfigSchema);\n/**\n * ServeConfig is the JSON type stored in the StateStore for\n * StateKey \"_serve/$PROFILE_ID\" as returned by ServeConfigKey.\n */\nexport const ServeConfigSchema = z.object({\n /**\n * TCP are the list of TCP port numbers that tailscaled should handle for\n * the Tailscale IP addresses. (not subnet routers, etc)\n */\n TCP: goMap(TCPPortHandlerSchema),\n /**\n * Web maps from \"$SNI_NAME:$PORT\" to a set of HTTP handlers\n * keyed by mount point (\"/\", \"/foo\", etc)\n */\n Web: goMap(WebServerConfigSchema),\n /**\n * Services maps from service name (in the form \"svc:dns-label\") to a ServiceConfig.\n * Which describes the L3, L4, and L7 forwarding information for the service.\n */\n Services: goMap(ServiceConfigSchema),\n /**\n * AllowFunnel is the set of SNI:port values for which funnel\n * traffic is allowed, from trusted ingress peers.\n */\n AllowFunnel: goMap(z.boolean()),\n /**\n * Foreground is a map of an IPN Bus session ID to an alternate foreground serve config that's valid for the\n * life of that WatchIPNBus session ID. This allows the config to specify ephemeral configs that are used\n * in the CLI's foreground mode to ensure ungraceful shutdowns of either the client or the LocalBackend does not\n * expose ports that users are not aware of. In practice this contains any serve config set via 'tailscale\n * serve' command run without the '--bg' flag. ServeConfig contained by Foreground is not expected itself to contain\n * another Foreground block.\n */\n Foreground: goMap(_ServeConfigRef),\n});\nexport type ServeConfig = z.infer<typeof ServeConfigSchema> & {\n /**\n * ETag is the checksum of the serve config that's populated\n * by the LocalClient through the HTTP ETag header during a\n * GetServeConfig request and is translated to an If-Match header\n * during a SetServeConfig request.\n */\n ETag: string;\n};\n\n/**\n * WhoIsResponse is the JSON type returned by tailscaled debug server's /whois?ip=$IP handler.\n * In successful whois responses, Node and UserProfile are never nil.\n */\nexport const WhoIsResponseSchema = z.object({\n Node: NodeSchema.prefault({}),\n UserProfile: UserProfileSchema.prefault({}),\n /**\n * CapMap is a map of capabilities to their values.\n * See tailcfg.PeerCapMap and tailcfg.PeerCapability for details.\n */\n CapMap: goMap(goSlice(z.unknown())),\n});\nexport type WhoIsResponse = z.infer<typeof WhoIsResponseSchema>;\n\n/** Alias for TailnetStatus for backward compatibility. */\nexport const CurrentTailnetSchema = z.object({\n Name: z.string(),\n MagicDNSSuffix: z.string(),\n MagicDNSEnabled: z.boolean(),\n});\nexport type CurrentTailnet = z.infer<typeof CurrentTailnetSchema>;\n","import { parseJSON, jsonReplacer } from \"./json.js\";\nimport {\n AccessDeniedError,\n ConnectionError,\n DaemonNotRunningError,\n HttpError,\n PeerNotFoundError,\n PreconditionsFailedError,\n errorMessageFromBody,\n} from \"./errors.js\";\nimport { Transport, type TransportOptions } from \"./transport.js\";\nimport {\n ServeConfigSchema,\n StatusSchema,\n WhoIsResponseSchema,\n type ServeConfig,\n type Status,\n type WhoIsResponse,\n} from \"./types.js\";\n\n/**\n * Client for the Tailscale Local API.\n *\n * Connections are reused via HTTP keep-alive.\n */\nexport class Client {\n private readonly transport: Transport;\n\n constructor(opts: TransportOptions = {}) {\n this.transport = new Transport(opts);\n }\n\n private async doRequest(\n method: string,\n path: string,\n body?: Buffer | string,\n headers?: Record<string, string>,\n ): Promise<{ status: number; body: Buffer; headers: Record<string, string | string[] | undefined> }> {\n try {\n return await this.transport.request(method, path, body, headers);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"ECONNREFUSED\") || msg.includes(\"ENOENT\")) {\n throw new DaemonNotRunningError(msg);\n }\n throw new ConnectionError(msg);\n }\n }\n\n private async doRequestNice(\n method: string,\n path: string,\n body?: Buffer | string,\n headers?: Record<string, string>,\n ): Promise<Buffer> {\n const resp = await this.doRequest(method, path, body, headers);\n if (resp.status >= 200 && resp.status < 300) {\n return resp.body;\n }\n\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n\n if (resp.status === 403) throw new AccessDeniedError(msg);\n if (resp.status === 412) throw new PreconditionsFailedError(msg);\n throw new HttpError(resp.status, msg);\n }\n\n private async get200(path: string): Promise<Buffer> {\n return this.doRequestNice(\"GET\", path);\n }\n\n private async post200(path: string, body?: Buffer | string): Promise<Buffer> {\n return this.doRequestNice(\"POST\", path, body);\n }\n\n // --- Status ---\n\n /** Get the current tailscaled status. */\n async status(): Promise<Status> {\n const data = await this.get200(\"/localapi/v0/status\");\n return StatusSchema.parse(parseJSON(data.toString(\"utf-8\")));\n }\n\n /** Get the current tailscaled status without peer information. */\n async statusWithoutPeers(): Promise<Status> {\n const data = await this.get200(\"/localapi/v0/status?peers=false\");\n return StatusSchema.parse(parseJSON(data.toString(\"utf-8\")));\n }\n\n // --- WhoIs ---\n\n private async doWhoIs(params: string): Promise<WhoIsResponse> {\n const resp = await this.doRequest(\n \"GET\",\n `/localapi/v0/whois?${params}`,\n );\n if (resp.status === 404) {\n throw new PeerNotFoundError(params);\n }\n if (resp.status !== 200) {\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n if (resp.status === 403) throw new AccessDeniedError(msg);\n throw new HttpError(resp.status, msg);\n }\n return WhoIsResponseSchema.parse(parseJSON(resp.body.toString(\"utf-8\")));\n }\n\n /** Look up the owner of an IP address or IP:port. */\n async whoIs(remoteAddr: string): Promise<WhoIsResponse> {\n return this.doWhoIs(`addr=${encodeURIComponent(remoteAddr)}`);\n }\n\n /** Look up a peer by node key. */\n async whoIsNodeKey(nodeKey: string): Promise<WhoIsResponse> {\n return this.whoIs(nodeKey);\n }\n\n /** Look up the owner of an IP address with a specific protocol (\"tcp\" or \"udp\"). */\n async whoIsProto(proto: string, remoteAddr: string): Promise<WhoIsResponse> {\n return this.doWhoIs(`proto=${encodeURIComponent(proto)}&addr=${encodeURIComponent(remoteAddr)}`);\n }\n\n // --- Cert ---\n\n /** Get a TLS certificate and private key for the given domain. */\n async certPair(domain: string): Promise<{ cert: Buffer; key: Buffer }> {\n return this.certPairWithValidity(domain, 0);\n }\n\n /** Get a TLS certificate with minimum validity duration (in seconds). */\n async certPairWithValidity(\n domain: string,\n minValiditySecs: number,\n ): Promise<{ cert: Buffer; key: Buffer }> {\n const body = await this.get200(\n `/localapi/v0/cert/${encodeURIComponent(domain)}?type=pair&min_validity=${minValiditySecs}s`,\n );\n // Response is key PEM then cert PEM, separated by \"--\\n--\"\n const delimiter = Buffer.from(\"--\\n--\");\n const pos = body.indexOf(delimiter);\n if (pos === -1) {\n throw new Error(\"unexpected cert response: no delimiter\");\n }\n const split = pos + 3; // include \"--\\n\"\n const key = body.subarray(0, split);\n const cert = body.subarray(split);\n return { cert, key };\n }\n\n // --- Config ---\n\n /**\n * Get the current serve configuration.\n *\n * The returned ServeConfig has its ETag field populated from the\n * HTTP Etag response header.\n */\n async getServeConfig(): Promise<ServeConfig> {\n const resp = await this.doRequest(\"GET\", \"/localapi/v0/serve-config\");\n if (resp.status !== 200) {\n const bodyStr = resp.body.toString(\"utf-8\");\n const msg = errorMessageFromBody(bodyStr) ?? bodyStr;\n if (resp.status === 403) throw new AccessDeniedError(msg);\n if (resp.status === 412) throw new PreconditionsFailedError(msg);\n throw new HttpError(resp.status, msg);\n }\n const config = ServeConfigSchema.parse(parseJSON(resp.body.toString(\"utf-8\"))) as ServeConfig;\n config.ETag = (resp.headers[\"etag\"] as string) ?? \"\";\n return config;\n }\n\n /**\n * Set the serve configuration.\n *\n * The ETag field on the config is sent as the If-Match header\n * for conditional updates.\n */\n async setServeConfig(config: ServeConfig): Promise<void> {\n const headers: Record<string, string> = {};\n if (config.ETag) headers[\"If-Match\"] = config.ETag;\n const body = JSON.stringify(config, jsonReplacer);\n await this.doRequestNice(\"POST\", \"/localapi/v0/serve-config\", body, headers);\n }\n\n /** Close the underlying transport and release resources. */\n destroy(): void {\n this.transport.destroy();\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/ts/src/types.ts
CHANGED
|
@@ -288,7 +288,7 @@ export const StatusSchema = z.object({
|
|
|
288
288
|
AuthURL: z.string().default(""),
|
|
289
289
|
/** Tailscale IP(s) assigned to this node */
|
|
290
290
|
TailscaleIPs: goSlice(z.string()),
|
|
291
|
-
Self: PeerStatusSchema.
|
|
291
|
+
Self: PeerStatusSchema.prefault({}),
|
|
292
292
|
/**
|
|
293
293
|
* ExitNodeStatus describes the current exit node.
|
|
294
294
|
* If nil, an exit node is not in use.
|
|
@@ -990,8 +990,8 @@ export type ServeConfig = z.infer<typeof ServeConfigSchema> & {
|
|
|
990
990
|
* In successful whois responses, Node and UserProfile are never nil.
|
|
991
991
|
*/
|
|
992
992
|
export const WhoIsResponseSchema = z.object({
|
|
993
|
-
Node: NodeSchema.
|
|
994
|
-
UserProfile: UserProfileSchema.
|
|
993
|
+
Node: NodeSchema.prefault({}),
|
|
994
|
+
UserProfile: UserProfileSchema.prefault({}),
|
|
995
995
|
/**
|
|
996
996
|
* CapMap is a map of capabilities to their values.
|
|
997
997
|
* See tailcfg.PeerCapMap and tailcfg.PeerCapability for details.
|