tesla-fleet-api 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +269 -1
- package/dist/_virtual/_rolldown/runtime.cjs +10 -0
- package/dist/bluetooth/adapters/shared.cjs +38 -0
- package/dist/bluetooth/adapters/shared.mjs +36 -0
- package/dist/bluetooth/async.cjs +121 -0
- package/dist/bluetooth/async.d.cts +10 -0
- package/dist/bluetooth/async.d.mts +10 -0
- package/dist/bluetooth/async.mjs +116 -0
- package/dist/bluetooth/broadcast.cjs +153 -0
- package/dist/bluetooth/broadcast.d.cts +59 -0
- package/dist/bluetooth/broadcast.d.mts +59 -0
- package/dist/bluetooth/broadcast.mjs +151 -0
- package/dist/bluetooth/constants.cjs +96 -0
- package/dist/bluetooth/constants.d.cts +68 -0
- package/dist/bluetooth/constants.d.mts +68 -0
- package/dist/bluetooth/constants.mjs +70 -0
- package/dist/bluetooth/framing.cjs +125 -0
- package/dist/bluetooth/framing.d.cts +45 -0
- package/dist/bluetooth/framing.d.mts +45 -0
- package/dist/bluetooth/framing.mjs +122 -0
- package/dist/bluetooth/homey.cjs +165 -0
- package/dist/bluetooth/homey.d.cts +84 -0
- package/dist/bluetooth/homey.d.mts +84 -0
- package/dist/bluetooth/homey.mjs +159 -0
- package/dist/bluetooth/names.cjs +24 -0
- package/dist/bluetooth/names.d.cts +10 -0
- package/dist/bluetooth/names.d.mts +10 -0
- package/dist/bluetooth/names.mjs +20 -0
- package/dist/bluetooth/noble.cjs +178 -0
- package/dist/bluetooth/noble.d.cts +58 -0
- package/dist/bluetooth/noble.d.mts +58 -0
- package/dist/bluetooth/noble.mjs +177 -0
- package/dist/bluetooth/streamglue.cjs +157 -0
- package/dist/bluetooth/streamglue.d.cts +37 -0
- package/dist/bluetooth/streamglue.d.mts +37 -0
- package/dist/bluetooth/streamglue.mjs +154 -0
- package/dist/bluetooth/teslabluetooth.cjs +67 -0
- package/dist/bluetooth/teslabluetooth.d.cts +41 -0
- package/dist/bluetooth/teslabluetooth.d.mts +41 -0
- package/dist/bluetooth/teslabluetooth.mjs +66 -0
- package/dist/bluetooth/transport.cjs +10 -0
- package/dist/bluetooth/transport.d.cts +97 -0
- package/dist/bluetooth/transport.d.mts +97 -0
- package/dist/bluetooth/transport.mjs +10 -0
- package/dist/bluetooth/vehiclebluetooth.cjs +1272 -0
- package/dist/bluetooth/vehiclebluetooth.d.cts +495 -0
- package/dist/bluetooth/vehiclebluetooth.d.mts +495 -0
- package/dist/bluetooth/vehiclebluetooth.mjs +1270 -0
- package/dist/bluetooth/verify.cjs +93 -0
- package/dist/bluetooth/verify.d.cts +35 -0
- package/dist/bluetooth/verify.d.mts +35 -0
- package/dist/bluetooth/verify.mjs +91 -0
- package/dist/bluetooth.cjs +52 -0
- package/dist/bluetooth.d.cts +10 -0
- package/dist/bluetooth.d.mts +10 -0
- package/dist/bluetooth.mjs +9 -0
- package/dist/commands.cjs +1080 -85
- package/dist/commands.d.cts +585 -54
- package/dist/commands.d.mts +583 -51
- package/dist/commands.mjs +1084 -93
- package/dist/const.cjs +22 -0
- package/dist/const.d.cts +21 -0
- package/dist/const.d.mts +21 -0
- package/dist/const.mjs +20 -1
- package/dist/energy.cjs +24 -19
- package/dist/energy.d.cts +21 -15
- package/dist/energy.d.mts +21 -15
- package/dist/energy.mjs +24 -19
- package/dist/energyisland.cjs +38 -0
- package/dist/energyisland.mjs +37 -0
- package/dist/energyparsers.cjs +118 -0
- package/dist/energyparsers.d.cts +39 -0
- package/dist/energyparsers.d.mts +39 -0
- package/dist/energyparsers.mjs +116 -0
- package/dist/energyspecific.cjs +375 -17
- package/dist/energyspecific.d.cts +199 -19
- package/dist/energyspecific.d.mts +197 -16
- package/dist/energyspecific.mjs +370 -18
- package/dist/exceptions.cjs +3 -2
- package/dist/exceptions.d.cts +3 -2
- package/dist/exceptions.d.mts +3 -2
- package/dist/exceptions.mjs +3 -2
- package/dist/funnel.cjs +297 -0
- package/dist/funnel.d.cts +118 -0
- package/dist/funnel.d.mts +118 -0
- package/dist/funnel.mjs +292 -0
- package/dist/index.cjs +61 -0
- package/dist/index.d.cts +22 -8
- package/dist/index.d.mts +22 -8
- package/dist/index.mjs +19 -6
- package/dist/log.cjs +75 -0
- package/dist/log.d.cts +4 -0
- package/dist/log.d.mts +4 -0
- package/dist/log.mjs +71 -0
- package/dist/oauth.cjs +143 -0
- package/dist/oauth.d.cts +69 -0
- package/dist/oauth.d.mts +69 -0
- package/dist/oauth.mjs +143 -0
- package/dist/powerwall/client.cjs +818 -0
- package/dist/powerwall/client.d.cts +297 -0
- package/dist/powerwall/client.d.mts +297 -0
- package/dist/powerwall/client.mjs +807 -0
- package/dist/powerwall/components.cjs +188 -0
- package/dist/powerwall/components.d.cts +45 -0
- package/dist/powerwall/components.d.mts +45 -0
- package/dist/powerwall/components.mjs +182 -0
- package/dist/powerwall/energysite.cjs +216 -0
- package/dist/powerwall/energysite.d.cts +104 -0
- package/dist/powerwall/energysite.d.mts +104 -0
- package/dist/powerwall/energysite.mjs +214 -0
- package/dist/powerwall/exceptions.cjs +59 -0
- package/dist/powerwall/exceptions.d.cts +38 -0
- package/dist/powerwall/exceptions.d.mts +38 -0
- package/dist/powerwall/exceptions.mjs +51 -0
- package/dist/powerwall/gateway.cjs +102 -0
- package/dist/powerwall/gateway.d.cts +57 -0
- package/dist/powerwall/gateway.d.mts +57 -0
- package/dist/powerwall/gateway.mjs +101 -0
- package/dist/powerwall/merge.cjs +163 -0
- package/dist/powerwall/merge.d.cts +63 -0
- package/dist/powerwall/merge.d.mts +63 -0
- package/dist/powerwall/merge.mjs +159 -0
- package/dist/powerwall/models.d.cts +95 -0
- package/dist/powerwall/models.d.mts +95 -0
- package/dist/powerwall/queries.cjs +17 -0
- package/dist/powerwall/queries.mjs +10 -0
- package/dist/powerwall/transport.cjs +232 -0
- package/dist/powerwall/transport.mjs +227 -0
- package/dist/powerwall.cjs +47 -0
- package/dist/powerwall.d.cts +9 -0
- package/dist/powerwall.d.mts +9 -0
- package/dist/powerwall.mjs +8 -0
- package/dist/router/base.cjs +203 -0
- package/dist/router/base.d.cts +112 -0
- package/dist/router/base.d.mts +112 -0
- package/dist/router/base.mjs +200 -0
- package/dist/router/energysite.cjs +65 -0
- package/dist/router/energysite.d.cts +47 -0
- package/dist/router/energysite.d.mts +47 -0
- package/dist/router/energysite.mjs +60 -0
- package/dist/router.cjs +13 -0
- package/dist/router.d.cts +3 -1
- package/dist/router.d.mts +3 -1
- package/dist/router.mjs +3 -1
- package/dist/signing/crypto.cjs +28 -0
- package/dist/signing/crypto.mjs +26 -1
- package/dist/signing/energy.cjs +123 -0
- package/dist/signing/energy.d.cts +45 -0
- package/dist/signing/energy.d.mts +45 -0
- package/dist/signing/energy.mjs +120 -0
- package/dist/signing/jws.cjs +229 -0
- package/dist/signing/jws.d.cts +45 -0
- package/dist/signing/jws.d.mts +45 -0
- package/dist/signing/jws.mjs +211 -0
- package/dist/signing/keys.cjs +191 -0
- package/dist/signing/keys.d.cts +21 -0
- package/dist/signing/keys.d.mts +21 -0
- package/dist/signing/keys.mjs +182 -0
- package/dist/signing/session.cjs +104 -30
- package/dist/signing/session.d.cts +60 -23
- package/dist/signing/session.d.mts +60 -23
- package/dist/signing/session.mjs +105 -31
- package/dist/tariff.cjs +27 -0
- package/dist/tariff.d.cts +11 -0
- package/dist/tariff.d.mts +11 -0
- package/dist/tariff.mjs +27 -1
- package/dist/teslafleetapi.cjs +256 -55
- package/dist/teslafleetapi.d.cts +110 -14
- package/dist/teslafleetapi.d.mts +110 -14
- package/dist/teslafleetapi.mjs +258 -57
- package/dist/teslemetry.cjs +152 -5
- package/dist/teslemetry.d.cts +95 -5
- package/dist/teslemetry.d.mts +93 -3
- package/dist/teslemetry.mjs +151 -6
- package/dist/teslemetryenergy.cjs +287 -0
- package/dist/teslemetryenergy.d.cts +154 -0
- package/dist/teslemetryenergy.d.mts +154 -0
- package/dist/teslemetryenergy.mjs +282 -0
- package/dist/teslemetryvehicle.cjs +407 -0
- package/dist/teslemetryvehicle.d.cts +238 -0
- package/dist/teslemetryvehicle.d.mts +238 -0
- package/dist/teslemetryvehicle.mjs +405 -0
- package/dist/tessie.cjs +42 -5
- package/dist/tessie.d.cts +51 -5
- package/dist/tessie.d.mts +49 -3
- package/dist/tessie.mjs +42 -5
- package/dist/tessievehicle.cjs +514 -0
- package/dist/tessievehicle.d.cts +333 -0
- package/dist/tessievehicle.d.mts +333 -0
- package/dist/tessievehicle.mjs +513 -0
- package/dist/types/live_status.d.cts +16 -16
- package/dist/types/live_status.d.mts +16 -16
- package/dist/types/responses.d.cts +5 -1
- package/dist/types/responses.d.mts +5 -1
- package/dist/types/vehicle.d.cts +7 -1
- package/dist/types/vehicle.d.mts +7 -1
- package/dist/types.d.cts +3 -3
- package/dist/types.d.mts +3 -3
- package/dist/util.cjs +41 -0
- package/dist/util.d.cts +18 -0
- package/dist/util.d.mts +18 -0
- package/dist/util.mjs +40 -0
- package/dist/vehicle.cjs +79 -36
- package/dist/vehicle.d.cts +42 -26
- package/dist/vehicle.d.mts +42 -26
- package/dist/vehicle.mjs +80 -37
- package/dist/vehiclesigned.cjs +20 -5
- package/dist/vehiclesigned.d.cts +14 -5
- package/dist/vehiclesigned.d.mts +14 -5
- package/dist/vehiclesigned.mjs +20 -5
- package/dist/vehiclespecific.cjs +126 -15
- package/dist/vehiclespecific.d.cts +82 -18
- package/dist/vehiclespecific.d.mts +82 -18
- package/dist/vehiclespecific.mjs +126 -15
- package/package.json +56 -3
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const require_const = require("./const.cjs");
|
|
2
|
+
const require_exceptions = require("./exceptions.cjs");
|
|
3
|
+
// src/energyparsers.ts
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
/** The first present key's value. Tests presence, so a legal falsy value is never read as missing. */
|
|
8
|
+
function field(payload, ...keys) {
|
|
9
|
+
for (const key of keys) if (Object.hasOwn(payload, key)) return payload[key];
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Type a raw value against an enum. A recognized number or member name
|
|
14
|
+
* (case-insensitive) becomes the member's number. A present but unrecognized
|
|
15
|
+
* value stays as received, because only an absent field means `null`.
|
|
16
|
+
*/
|
|
17
|
+
function normalizeEnum(members, value) {
|
|
18
|
+
if (typeof value === "string") {
|
|
19
|
+
const name = value.trim().toUpperCase();
|
|
20
|
+
return Object.hasOwn(members, name) ? members[name] : value;
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function parseClient(payload) {
|
|
25
|
+
const roles = field(payload, "roles");
|
|
26
|
+
return {
|
|
27
|
+
public_key: field(payload, "public_key", "publicKey"),
|
|
28
|
+
state: normalizeEnum(require_const.AuthorizedClientState, field(payload, "state", "authorized_client_state")),
|
|
29
|
+
roles: Array.isArray(roles) ? roles.map((role) => normalizeEnum(require_const.AuthorizationRole, role)) : null,
|
|
30
|
+
verification: normalizeEnum(require_const.AuthorizedVerificationType, field(payload, "verification")),
|
|
31
|
+
raw: payload
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function authorizedClientsList(payload) {
|
|
35
|
+
if (payload === null || payload === void 0) throw new require_exceptions.InvalidResponse("authorized_clients response body was null");
|
|
36
|
+
if (Array.isArray(payload)) return payload;
|
|
37
|
+
if (!isRecord(payload)) throw new require_exceptions.InvalidResponse(String(payload));
|
|
38
|
+
const value = field(isRecord(payload.response) ? payload.response : payload, "authorized_clients", "clients");
|
|
39
|
+
if (!Array.isArray(value)) throw new require_exceptions.InvalidResponse(payload);
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse a raw `list_authorized_clients` reply: a bare list,
|
|
44
|
+
* `{response: {authorized_clients: [...]}}` or `{response: {clients: [...]}}`.
|
|
45
|
+
* @throws InvalidResponse for a null body or any other shape.
|
|
46
|
+
*/
|
|
47
|
+
function parse_authorized_clients(payload) {
|
|
48
|
+
return {
|
|
49
|
+
clients: authorizedClientsList(payload).filter(isRecord).map(parseClient),
|
|
50
|
+
raw: payload
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const OCTET = "(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])";
|
|
54
|
+
const DOTTED_QUAD = new RegExp(`^${OCTET}\\.${OCTET}\\.${OCTET}\\.${OCTET}$`);
|
|
55
|
+
/**
|
|
56
|
+
* Decode a `networking_status` ipv4 field. The live API serves a strict dotted
|
|
57
|
+
* quad; the big-endian uint32 form is accepted too. `0.0.0.0` and
|
|
58
|
+
* `255.255.255.255` are never a usable host, in either form. Anything else
|
|
59
|
+
* decodes to `null`, so one bad field cannot fail the lookup.
|
|
60
|
+
*/
|
|
61
|
+
function decodeIpv4(value) {
|
|
62
|
+
if (typeof value === "number") {
|
|
63
|
+
if (!Number.isInteger(value) || value <= 0 || value >= 4294967295) return null;
|
|
64
|
+
return [
|
|
65
|
+
value >>> 24,
|
|
66
|
+
value >>> 16 & 255,
|
|
67
|
+
value >>> 8 & 255,
|
|
68
|
+
value & 255
|
|
69
|
+
].join(".");
|
|
70
|
+
}
|
|
71
|
+
if (typeof value === "string" && DOTTED_QUAD.test(value) && value !== "0.0.0.0" && value !== "255.255.255.255") return value;
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Whether `address` is a dotted-quad IPv4 address in a private range (RFC 1918) or
|
|
76
|
+
* the link-local range: the only places a gateway's LAN address can be. Anything
|
|
77
|
+
* else, including a hostname or a `host:port`, is not.
|
|
78
|
+
*/
|
|
79
|
+
function is_lan_address(address) {
|
|
80
|
+
if (!DOTTED_QUAD.test(address)) return false;
|
|
81
|
+
const [first, second] = address.split(".").map(Number);
|
|
82
|
+
return first === 10 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168 || first === 169 && second === 254;
|
|
83
|
+
}
|
|
84
|
+
function interfaceAddress(networkInterface) {
|
|
85
|
+
if (!isRecord(networkInterface)) return null;
|
|
86
|
+
const ipv4 = networkInterface.ipv4_config;
|
|
87
|
+
return isRecord(ipv4) ? decodeIpv4(ipv4.address) : null;
|
|
88
|
+
}
|
|
89
|
+
function networkingStatusBody(payload) {
|
|
90
|
+
if (payload === null || payload === void 0) throw new require_exceptions.InvalidResponse("networking_status response body was null");
|
|
91
|
+
if (!isRecord(payload)) throw new require_exceptions.InvalidResponse(String(payload));
|
|
92
|
+
if (!Object.hasOwn(payload, "response")) return payload;
|
|
93
|
+
if (!isRecord(payload.response)) throw new require_exceptions.InvalidResponse(payload);
|
|
94
|
+
return payload.response;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Pick the gateway's LAN IPv4 address from a `networking_status` reply, with or
|
|
98
|
+
* without the `response` envelope. Considers `eth` and `wifi` only, never `gsm`:
|
|
99
|
+
* an interface with `active_route` wins, then the first with any decodable address.
|
|
100
|
+
* @returns The address, or `null` when a well-formed reply has no usable interface.
|
|
101
|
+
* @throws InvalidResponse for a null body or a reply of another shape.
|
|
102
|
+
*/
|
|
103
|
+
function parse_gateway_address(body) {
|
|
104
|
+
const status = networkingStatusBody(body);
|
|
105
|
+
const interfaces = [status.eth, status.wifi];
|
|
106
|
+
for (const networkInterface of interfaces) if (isRecord(networkInterface) && networkInterface.active_route) {
|
|
107
|
+
const address = interfaceAddress(networkInterface);
|
|
108
|
+
if (address !== null) return address;
|
|
109
|
+
}
|
|
110
|
+
for (const networkInterface of interfaces) {
|
|
111
|
+
const address = interfaceAddress(networkInterface);
|
|
112
|
+
if (address !== null) return address;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
exports.is_lan_address = is_lan_address;
|
|
117
|
+
exports.parse_authorized_clients = parse_authorized_clients;
|
|
118
|
+
exports.parse_gateway_address = parse_gateway_address;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type Json = Record<string, unknown>;
|
|
2
|
+
/** A value typed against an enum: its number, or as received when unrecognized, or `null` when absent. */
|
|
3
|
+
export type EnumValue = number | string | null;
|
|
4
|
+
/**
|
|
5
|
+
* One entry from a `list_authorized_clients` reply. Tesla has not published this
|
|
6
|
+
* schema, so anything else on an entry is available through `raw`.
|
|
7
|
+
*/
|
|
8
|
+
export type AuthorizedClient = {
|
|
9
|
+
public_key: string | null;
|
|
10
|
+
state: EnumValue;
|
|
11
|
+
roles: EnumValue[] | null;
|
|
12
|
+
verification: EnumValue;
|
|
13
|
+
raw: Json;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A parsed `list_authorized_clients` reply. A null body or an unknown shape
|
|
17
|
+
* raises `InvalidResponse` rather than reading as "no clients": the endpoint
|
|
18
|
+
* intermittently answers HTTP 200 with a null body. A genuinely empty list
|
|
19
|
+
* parses to `clients: []`.
|
|
20
|
+
*/
|
|
21
|
+
export type AuthorizedClients = {
|
|
22
|
+
clients: AuthorizedClient[];
|
|
23
|
+
raw: unknown;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Parse a raw `list_authorized_clients` reply: a bare list,
|
|
27
|
+
* `{response: {authorized_clients: [...]}}` or `{response: {clients: [...]}}`.
|
|
28
|
+
* @throws InvalidResponse for a null body or any other shape.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parse_authorized_clients(payload: unknown): AuthorizedClients;
|
|
31
|
+
/**
|
|
32
|
+
* Pick the gateway's LAN IPv4 address from a `networking_status` reply, with or
|
|
33
|
+
* without the `response` envelope. Considers `eth` and `wifi` only, never `gsm`:
|
|
34
|
+
* an interface with `active_route` wins, then the first with any decodable address.
|
|
35
|
+
* @returns The address, or `null` when a well-formed reply has no usable interface.
|
|
36
|
+
* @throws InvalidResponse for a null body or a reply of another shape.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parse_gateway_address(body: unknown): string | null;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type Json = Record<string, unknown>;
|
|
2
|
+
/** A value typed against an enum: its number, or as received when unrecognized, or `null` when absent. */
|
|
3
|
+
export type EnumValue = number | string | null;
|
|
4
|
+
/**
|
|
5
|
+
* One entry from a `list_authorized_clients` reply. Tesla has not published this
|
|
6
|
+
* schema, so anything else on an entry is available through `raw`.
|
|
7
|
+
*/
|
|
8
|
+
export type AuthorizedClient = {
|
|
9
|
+
public_key: string | null;
|
|
10
|
+
state: EnumValue;
|
|
11
|
+
roles: EnumValue[] | null;
|
|
12
|
+
verification: EnumValue;
|
|
13
|
+
raw: Json;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A parsed `list_authorized_clients` reply. A null body or an unknown shape
|
|
17
|
+
* raises `InvalidResponse` rather than reading as "no clients": the endpoint
|
|
18
|
+
* intermittently answers HTTP 200 with a null body. A genuinely empty list
|
|
19
|
+
* parses to `clients: []`.
|
|
20
|
+
*/
|
|
21
|
+
export type AuthorizedClients = {
|
|
22
|
+
clients: AuthorizedClient[];
|
|
23
|
+
raw: unknown;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Parse a raw `list_authorized_clients` reply: a bare list,
|
|
27
|
+
* `{response: {authorized_clients: [...]}}` or `{response: {clients: [...]}}`.
|
|
28
|
+
* @throws InvalidResponse for a null body or any other shape.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parse_authorized_clients(payload: unknown): AuthorizedClients;
|
|
31
|
+
/**
|
|
32
|
+
* Pick the gateway's LAN IPv4 address from a `networking_status` reply, with or
|
|
33
|
+
* without the `response` envelope. Considers `eth` and `wifi` only, never `gsm`:
|
|
34
|
+
* an interface with `active_route` wins, then the first with any decodable address.
|
|
35
|
+
* @returns The address, or `null` when a well-formed reply has no usable interface.
|
|
36
|
+
* @throws InvalidResponse for a null body or a reply of another shape.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parse_gateway_address(body: unknown): string | null;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { AuthorizationRole, AuthorizedClientState, AuthorizedVerificationType } from "./const.mjs";
|
|
2
|
+
import { InvalidResponse } from "./exceptions.mjs";
|
|
3
|
+
// src/energyparsers.ts
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
/** The first present key's value. Tests presence, so a legal falsy value is never read as missing. */
|
|
8
|
+
function field(payload, ...keys) {
|
|
9
|
+
for (const key of keys) if (Object.hasOwn(payload, key)) return payload[key];
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Type a raw value against an enum. A recognized number or member name
|
|
14
|
+
* (case-insensitive) becomes the member's number. A present but unrecognized
|
|
15
|
+
* value stays as received, because only an absent field means `null`.
|
|
16
|
+
*/
|
|
17
|
+
function normalizeEnum(members, value) {
|
|
18
|
+
if (typeof value === "string") {
|
|
19
|
+
const name = value.trim().toUpperCase();
|
|
20
|
+
return Object.hasOwn(members, name) ? members[name] : value;
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function parseClient(payload) {
|
|
25
|
+
const roles = field(payload, "roles");
|
|
26
|
+
return {
|
|
27
|
+
public_key: field(payload, "public_key", "publicKey"),
|
|
28
|
+
state: normalizeEnum(AuthorizedClientState, field(payload, "state", "authorized_client_state")),
|
|
29
|
+
roles: Array.isArray(roles) ? roles.map((role) => normalizeEnum(AuthorizationRole, role)) : null,
|
|
30
|
+
verification: normalizeEnum(AuthorizedVerificationType, field(payload, "verification")),
|
|
31
|
+
raw: payload
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function authorizedClientsList(payload) {
|
|
35
|
+
if (payload === null || payload === void 0) throw new InvalidResponse("authorized_clients response body was null");
|
|
36
|
+
if (Array.isArray(payload)) return payload;
|
|
37
|
+
if (!isRecord(payload)) throw new InvalidResponse(String(payload));
|
|
38
|
+
const value = field(isRecord(payload.response) ? payload.response : payload, "authorized_clients", "clients");
|
|
39
|
+
if (!Array.isArray(value)) throw new InvalidResponse(payload);
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse a raw `list_authorized_clients` reply: a bare list,
|
|
44
|
+
* `{response: {authorized_clients: [...]}}` or `{response: {clients: [...]}}`.
|
|
45
|
+
* @throws InvalidResponse for a null body or any other shape.
|
|
46
|
+
*/
|
|
47
|
+
function parse_authorized_clients(payload) {
|
|
48
|
+
return {
|
|
49
|
+
clients: authorizedClientsList(payload).filter(isRecord).map(parseClient),
|
|
50
|
+
raw: payload
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const OCTET = "(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])";
|
|
54
|
+
const DOTTED_QUAD = new RegExp(`^${OCTET}\\.${OCTET}\\.${OCTET}\\.${OCTET}$`);
|
|
55
|
+
/**
|
|
56
|
+
* Decode a `networking_status` ipv4 field. The live API serves a strict dotted
|
|
57
|
+
* quad; the big-endian uint32 form is accepted too. `0.0.0.0` and
|
|
58
|
+
* `255.255.255.255` are never a usable host, in either form. Anything else
|
|
59
|
+
* decodes to `null`, so one bad field cannot fail the lookup.
|
|
60
|
+
*/
|
|
61
|
+
function decodeIpv4(value) {
|
|
62
|
+
if (typeof value === "number") {
|
|
63
|
+
if (!Number.isInteger(value) || value <= 0 || value >= 4294967295) return null;
|
|
64
|
+
return [
|
|
65
|
+
value >>> 24,
|
|
66
|
+
value >>> 16 & 255,
|
|
67
|
+
value >>> 8 & 255,
|
|
68
|
+
value & 255
|
|
69
|
+
].join(".");
|
|
70
|
+
}
|
|
71
|
+
if (typeof value === "string" && DOTTED_QUAD.test(value) && value !== "0.0.0.0" && value !== "255.255.255.255") return value;
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Whether `address` is a dotted-quad IPv4 address in a private range (RFC 1918) or
|
|
76
|
+
* the link-local range: the only places a gateway's LAN address can be. Anything
|
|
77
|
+
* else, including a hostname or a `host:port`, is not.
|
|
78
|
+
*/
|
|
79
|
+
function is_lan_address(address) {
|
|
80
|
+
if (!DOTTED_QUAD.test(address)) return false;
|
|
81
|
+
const [first, second] = address.split(".").map(Number);
|
|
82
|
+
return first === 10 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168 || first === 169 && second === 254;
|
|
83
|
+
}
|
|
84
|
+
function interfaceAddress(networkInterface) {
|
|
85
|
+
if (!isRecord(networkInterface)) return null;
|
|
86
|
+
const ipv4 = networkInterface.ipv4_config;
|
|
87
|
+
return isRecord(ipv4) ? decodeIpv4(ipv4.address) : null;
|
|
88
|
+
}
|
|
89
|
+
function networkingStatusBody(payload) {
|
|
90
|
+
if (payload === null || payload === void 0) throw new InvalidResponse("networking_status response body was null");
|
|
91
|
+
if (!isRecord(payload)) throw new InvalidResponse(String(payload));
|
|
92
|
+
if (!Object.hasOwn(payload, "response")) return payload;
|
|
93
|
+
if (!isRecord(payload.response)) throw new InvalidResponse(payload);
|
|
94
|
+
return payload.response;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Pick the gateway's LAN IPv4 address from a `networking_status` reply, with or
|
|
98
|
+
* without the `response` envelope. Considers `eth` and `wifi` only, never `gsm`:
|
|
99
|
+
* an interface with `active_route` wins, then the first with any decodable address.
|
|
100
|
+
* @returns The address, or `null` when a well-formed reply has no usable interface.
|
|
101
|
+
* @throws InvalidResponse for a null body or a reply of another shape.
|
|
102
|
+
*/
|
|
103
|
+
function parse_gateway_address(body) {
|
|
104
|
+
const status = networkingStatusBody(body);
|
|
105
|
+
const interfaces = [status.eth, status.wifi];
|
|
106
|
+
for (const networkInterface of interfaces) if (isRecord(networkInterface) && networkInterface.active_route) {
|
|
107
|
+
const address = interfaceAddress(networkInterface);
|
|
108
|
+
if (address !== null) return address;
|
|
109
|
+
}
|
|
110
|
+
for (const networkInterface of interfaces) {
|
|
111
|
+
const address = interfaceAddress(networkInterface);
|
|
112
|
+
if (address !== null) return address;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
export { is_lan_address, parse_authorized_clients, parse_gateway_address };
|