tesla-fleet-api 0.3.0 → 0.5.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.
Files changed (342) hide show
  1. package/README.md +284 -1
  2. package/dist/_virtual/_rolldown/runtime.cjs +32 -0
  3. package/dist/_virtual/_rolldown/runtime.mjs +12 -0
  4. package/dist/bluetooth/adapters/shared.cjs +38 -0
  5. package/dist/bluetooth/adapters/shared.mjs +36 -0
  6. package/dist/bluetooth/async.cjs +121 -0
  7. package/dist/bluetooth/async.d.cts +10 -0
  8. package/dist/bluetooth/async.d.mts +10 -0
  9. package/dist/bluetooth/async.mjs +116 -0
  10. package/dist/bluetooth/broadcast.cjs +153 -0
  11. package/dist/bluetooth/broadcast.d.cts +59 -0
  12. package/dist/bluetooth/broadcast.d.mts +59 -0
  13. package/dist/bluetooth/broadcast.mjs +151 -0
  14. package/dist/bluetooth/constants.cjs +99 -0
  15. package/dist/bluetooth/constants.d.cts +70 -0
  16. package/dist/bluetooth/constants.d.mts +70 -0
  17. package/dist/bluetooth/constants.mjs +72 -0
  18. package/dist/bluetooth/esphome.cjs +292 -0
  19. package/dist/bluetooth/esphome.d.cts +83 -0
  20. package/dist/bluetooth/esphome.d.mts +83 -0
  21. package/dist/bluetooth/esphome.mjs +291 -0
  22. package/dist/bluetooth/framing.cjs +125 -0
  23. package/dist/bluetooth/framing.d.cts +45 -0
  24. package/dist/bluetooth/framing.d.mts +45 -0
  25. package/dist/bluetooth/framing.mjs +122 -0
  26. package/dist/bluetooth/homey.cjs +155 -0
  27. package/dist/bluetooth/homey.d.cts +75 -0
  28. package/dist/bluetooth/homey.d.mts +75 -0
  29. package/dist/bluetooth/homey.mjs +150 -0
  30. package/dist/bluetooth/names.cjs +24 -0
  31. package/dist/bluetooth/names.d.cts +10 -0
  32. package/dist/bluetooth/names.d.mts +10 -0
  33. package/dist/bluetooth/names.mjs +20 -0
  34. package/dist/bluetooth/noble.cjs +178 -0
  35. package/dist/bluetooth/noble.d.cts +58 -0
  36. package/dist/bluetooth/noble.d.mts +58 -0
  37. package/dist/bluetooth/noble.mjs +177 -0
  38. package/dist/bluetooth/streamglue.cjs +157 -0
  39. package/dist/bluetooth/streamglue.d.cts +37 -0
  40. package/dist/bluetooth/streamglue.d.mts +37 -0
  41. package/dist/bluetooth/streamglue.mjs +154 -0
  42. package/dist/bluetooth/teslabluetooth.cjs +67 -0
  43. package/dist/bluetooth/teslabluetooth.d.cts +41 -0
  44. package/dist/bluetooth/teslabluetooth.d.mts +41 -0
  45. package/dist/bluetooth/teslabluetooth.mjs +66 -0
  46. package/dist/bluetooth/transport.cjs +10 -0
  47. package/dist/bluetooth/transport.d.cts +95 -0
  48. package/dist/bluetooth/transport.d.mts +95 -0
  49. package/dist/bluetooth/transport.mjs +10 -0
  50. package/dist/bluetooth/vehiclebluetooth.cjs +1272 -0
  51. package/dist/bluetooth/vehiclebluetooth.d.cts +495 -0
  52. package/dist/bluetooth/vehiclebluetooth.d.mts +495 -0
  53. package/dist/bluetooth/vehiclebluetooth.mjs +1270 -0
  54. package/dist/bluetooth/verify.cjs +93 -0
  55. package/dist/bluetooth/verify.d.cts +35 -0
  56. package/dist/bluetooth/verify.d.mts +35 -0
  57. package/dist/bluetooth/verify.mjs +91 -0
  58. package/dist/bluetooth.cjs +53 -0
  59. package/dist/bluetooth.d.cts +10 -0
  60. package/dist/bluetooth.d.mts +10 -0
  61. package/dist/bluetooth.mjs +9 -0
  62. package/dist/charging.cjs +53 -0
  63. package/dist/charging.d.cts +31 -0
  64. package/dist/charging.d.mts +30 -0
  65. package/dist/charging.mjs +53 -0
  66. package/dist/commands.cjs +1797 -0
  67. package/dist/commands.d.cts +881 -0
  68. package/dist/commands.d.mts +881 -0
  69. package/dist/commands.mjs +1791 -0
  70. package/dist/const.cjs +322 -0
  71. package/dist/const.d.cts +308 -0
  72. package/dist/const.d.mts +308 -0
  73. package/dist/const.mjs +282 -0
  74. package/dist/energy.cjs +142 -0
  75. package/dist/energy.d.cts +101 -0
  76. package/dist/energy.d.mts +100 -0
  77. package/dist/energy.mjs +142 -0
  78. package/dist/energyisland.cjs +38 -0
  79. package/dist/energyisland.mjs +37 -0
  80. package/dist/energyparsers.cjs +118 -0
  81. package/dist/energyparsers.d.cts +39 -0
  82. package/dist/energyparsers.d.mts +39 -0
  83. package/dist/energyparsers.mjs +116 -0
  84. package/dist/energyspecific.cjs +457 -0
  85. package/dist/energyspecific.d.cts +258 -0
  86. package/dist/energyspecific.d.mts +258 -0
  87. package/dist/energyspecific.mjs +451 -0
  88. package/dist/exceptions.cjs +1488 -0
  89. package/dist/exceptions.d.cts +1143 -0
  90. package/dist/exceptions.d.mts +1143 -0
  91. package/dist/exceptions.mjs +1325 -0
  92. package/dist/funnel.cjs +297 -0
  93. package/dist/funnel.d.cts +118 -0
  94. package/dist/funnel.d.mts +118 -0
  95. package/dist/funnel.mjs +292 -0
  96. package/dist/index.cjs +287 -0
  97. package/dist/index.d.cts +28 -0
  98. package/dist/index.d.mts +28 -0
  99. package/dist/index.mjs +27 -0
  100. package/dist/log.cjs +75 -0
  101. package/dist/log.d.cts +4 -0
  102. package/dist/log.d.mts +4 -0
  103. package/dist/log.mjs +71 -0
  104. package/dist/oauth.cjs +143 -0
  105. package/dist/oauth.d.cts +69 -0
  106. package/dist/oauth.d.mts +69 -0
  107. package/dist/oauth.mjs +143 -0
  108. package/dist/partner.cjs +32 -0
  109. package/dist/partner.d.cts +24 -0
  110. package/dist/partner.d.mts +23 -0
  111. package/dist/partner.mjs +32 -0
  112. package/dist/powerwall/client.cjs +818 -0
  113. package/dist/powerwall/client.d.cts +297 -0
  114. package/dist/powerwall/client.d.mts +297 -0
  115. package/dist/powerwall/client.mjs +807 -0
  116. package/dist/powerwall/components.cjs +188 -0
  117. package/dist/powerwall/components.d.cts +45 -0
  118. package/dist/powerwall/components.d.mts +45 -0
  119. package/dist/powerwall/components.mjs +182 -0
  120. package/dist/powerwall/energysite.cjs +216 -0
  121. package/dist/powerwall/energysite.d.cts +104 -0
  122. package/dist/powerwall/energysite.d.mts +104 -0
  123. package/dist/powerwall/energysite.mjs +214 -0
  124. package/dist/powerwall/exceptions.cjs +59 -0
  125. package/dist/powerwall/exceptions.d.cts +38 -0
  126. package/dist/powerwall/exceptions.d.mts +38 -0
  127. package/dist/powerwall/exceptions.mjs +51 -0
  128. package/dist/powerwall/gateway.cjs +102 -0
  129. package/dist/powerwall/gateway.d.cts +57 -0
  130. package/dist/powerwall/gateway.d.mts +57 -0
  131. package/dist/powerwall/gateway.mjs +101 -0
  132. package/dist/powerwall/merge.cjs +163 -0
  133. package/dist/powerwall/merge.d.cts +63 -0
  134. package/dist/powerwall/merge.d.mts +63 -0
  135. package/dist/powerwall/merge.mjs +159 -0
  136. package/dist/powerwall/models.d.cts +95 -0
  137. package/dist/powerwall/models.d.mts +95 -0
  138. package/dist/powerwall/queries.cjs +17 -0
  139. package/dist/powerwall/queries.mjs +10 -0
  140. package/dist/powerwall/transport.cjs +232 -0
  141. package/dist/powerwall/transport.mjs +227 -0
  142. package/dist/powerwall.cjs +47 -0
  143. package/dist/powerwall.d.cts +9 -0
  144. package/dist/powerwall.d.mts +9 -0
  145. package/dist/powerwall.mjs +8 -0
  146. package/dist/router/base.cjs +203 -0
  147. package/dist/router/base.d.cts +112 -0
  148. package/dist/router/base.d.mts +112 -0
  149. package/dist/router/base.mjs +200 -0
  150. package/dist/router/energysite.cjs +65 -0
  151. package/dist/router/energysite.d.cts +47 -0
  152. package/dist/router/energysite.d.mts +47 -0
  153. package/dist/router/energysite.mjs +60 -0
  154. package/dist/router.cjs +13 -0
  155. package/dist/router.d.cts +3 -0
  156. package/dist/router.d.mts +3 -0
  157. package/dist/router.mjs +3 -0
  158. package/dist/signing/crypto.cjs +59 -0
  159. package/dist/signing/crypto.mjs +52 -0
  160. package/dist/signing/energy.cjs +123 -0
  161. package/dist/signing/energy.d.cts +45 -0
  162. package/dist/signing/energy.d.mts +45 -0
  163. package/dist/signing/energy.mjs +120 -0
  164. package/dist/signing/jws.cjs +229 -0
  165. package/dist/signing/jws.d.cts +45 -0
  166. package/dist/signing/jws.d.mts +45 -0
  167. package/dist/signing/jws.mjs +211 -0
  168. package/dist/signing/keys.cjs +191 -0
  169. package/dist/signing/keys.d.cts +21 -0
  170. package/dist/signing/keys.d.mts +21 -0
  171. package/dist/signing/keys.mjs +182 -0
  172. package/dist/signing/session.cjs +186 -0
  173. package/dist/signing/session.d.cts +106 -0
  174. package/dist/signing/session.d.mts +106 -0
  175. package/dist/signing/session.mjs +184 -0
  176. package/dist/tariff.cjs +319 -0
  177. package/dist/tariff.d.cts +40 -0
  178. package/dist/tariff.d.mts +40 -0
  179. package/dist/tariff.mjs +317 -0
  180. package/dist/teslafleetapi.cjs +326 -0
  181. package/dist/teslafleetapi.d.cts +160 -0
  182. package/dist/teslafleetapi.d.mts +160 -0
  183. package/dist/teslafleetapi.mjs +326 -0
  184. package/dist/teslemetry.cjs +183 -0
  185. package/dist/teslemetry.d.cts +113 -0
  186. package/dist/teslemetry.d.mts +113 -0
  187. package/dist/teslemetry.mjs +181 -0
  188. package/dist/teslemetryenergy.cjs +287 -0
  189. package/dist/teslemetryenergy.d.cts +154 -0
  190. package/dist/teslemetryenergy.d.mts +154 -0
  191. package/dist/teslemetryenergy.mjs +282 -0
  192. package/dist/teslemetryvehicle.cjs +407 -0
  193. package/dist/teslemetryvehicle.d.cts +238 -0
  194. package/dist/teslemetryvehicle.d.mts +238 -0
  195. package/dist/teslemetryvehicle.mjs +405 -0
  196. package/dist/tessie.cjs +71 -0
  197. package/dist/tessie.d.cts +69 -0
  198. package/dist/tessie.d.mts +69 -0
  199. package/dist/tessie.mjs +71 -0
  200. package/dist/tessievehicle.cjs +514 -0
  201. package/dist/tessievehicle.d.cts +333 -0
  202. package/dist/tessievehicle.d.mts +333 -0
  203. package/dist/tessievehicle.mjs +513 -0
  204. package/dist/types/commands.d.cts +28 -0
  205. package/dist/types/commands.d.mts +28 -0
  206. package/dist/types/live_status.d.cts +30 -0
  207. package/dist/types/live_status.d.mts +30 -0
  208. package/dist/types/products.d.cts +58 -0
  209. package/dist/types/products.d.mts +58 -0
  210. package/dist/types/responses.d.cts +13 -0
  211. package/dist/types/responses.d.mts +13 -0
  212. package/dist/types/site_info.d.cts +154 -0
  213. package/dist/types/site_info.d.mts +154 -0
  214. package/dist/types/vehicle.d.cts +33 -0
  215. package/dist/types/vehicle.d.mts +33 -0
  216. package/dist/types/vehicle_data.d.cts +278 -0
  217. package/dist/types/vehicle_data.d.mts +278 -0
  218. package/dist/types.cjs +0 -0
  219. package/dist/types.d.cts +9 -0
  220. package/dist/types.d.mts +9 -0
  221. package/dist/types.mjs +1 -0
  222. package/dist/user.cjs +28 -0
  223. package/dist/user.d.cts +17 -0
  224. package/dist/user.d.mts +16 -0
  225. package/dist/user.mjs +28 -0
  226. package/dist/util.cjs +41 -0
  227. package/dist/util.d.cts +18 -0
  228. package/dist/util.d.mts +18 -0
  229. package/dist/util.mjs +40 -0
  230. package/dist/vehicle.cjs +847 -0
  231. package/dist/vehicle.d.cts +566 -0
  232. package/dist/vehicle.d.mts +565 -0
  233. package/dist/vehicle.mjs +847 -0
  234. package/dist/vehiclesigned.cjs +35 -0
  235. package/dist/vehiclesigned.d.cts +23 -0
  236. package/dist/vehiclesigned.d.mts +22 -0
  237. package/dist/vehiclesigned.mjs +35 -0
  238. package/dist/vehiclespecific.cjs +715 -0
  239. package/dist/vehiclespecific.d.cts +497 -0
  240. package/dist/vehiclespecific.d.mts +496 -0
  241. package/dist/vehiclespecific.mjs +715 -0
  242. package/package.json +178 -38
  243. package/dist/charging.d.ts +0 -30
  244. package/dist/charging.js +0 -51
  245. package/dist/charging.js.map +0 -1
  246. package/dist/commands.d.ts +0 -352
  247. package/dist/commands.js +0 -875
  248. package/dist/commands.js.map +0 -1
  249. package/dist/energy.d.ts +0 -94
  250. package/dist/energy.js +0 -125
  251. package/dist/energy.js.map +0 -1
  252. package/dist/energyspecific.d.ts +0 -77
  253. package/dist/energyspecific.js +0 -97
  254. package/dist/energyspecific.js.map +0 -1
  255. package/dist/helpers.d.ts +0 -2
  256. package/dist/helpers.js +0 -10
  257. package/dist/helpers.js.map +0 -1
  258. package/dist/index.d.ts +0 -15
  259. package/dist/index.js +0 -15
  260. package/dist/index.js.map +0 -1
  261. package/dist/partner.d.ts +0 -23
  262. package/dist/partner.js +0 -31
  263. package/dist/partner.js.map +0 -1
  264. package/dist/signing/crypto.d.ts +0 -10
  265. package/dist/signing/crypto.js +0 -26
  266. package/dist/signing/crypto.js.map +0 -1
  267. package/dist/signing/errors.d.ts +0 -29
  268. package/dist/signing/errors.js +0 -40
  269. package/dist/signing/errors.js.map +0 -1
  270. package/dist/signing/session.d.ts +0 -69
  271. package/dist/signing/session.js +0 -102
  272. package/dist/signing/session.js.map +0 -1
  273. package/dist/tariff.d.ts +0 -29
  274. package/dist/tariff.js +0 -302
  275. package/dist/tariff.js.map +0 -1
  276. package/dist/teslafleetapi.d.ts +0 -58
  277. package/dist/teslafleetapi.js +0 -120
  278. package/dist/teslafleetapi.js.map +0 -1
  279. package/dist/teslemetry.d.ts +0 -29
  280. package/dist/teslemetry.js +0 -39
  281. package/dist/teslemetry.js.map +0 -1
  282. package/dist/tessie.d.ts +0 -23
  283. package/dist/tessie.js +0 -28
  284. package/dist/tessie.js.map +0 -1
  285. package/dist/types/commands.d.ts +0 -44
  286. package/dist/types/commands.js +0 -11
  287. package/dist/types/commands.js.map +0 -1
  288. package/dist/types/live_status.d.ts +0 -26
  289. package/dist/types/live_status.js +0 -2
  290. package/dist/types/live_status.js.map +0 -1
  291. package/dist/types/products.d.ts +0 -54
  292. package/dist/types/products.js +0 -2
  293. package/dist/types/products.js.map +0 -1
  294. package/dist/types/responses.d.ts +0 -5
  295. package/dist/types/responses.js +0 -2
  296. package/dist/types/responses.js.map +0 -1
  297. package/dist/types/site_info.d.ts +0 -150
  298. package/dist/types/site_info.js +0 -2
  299. package/dist/types/site_info.js.map +0 -1
  300. package/dist/types/vehicle.d.ts +0 -23
  301. package/dist/types/vehicle.js +0 -10
  302. package/dist/types/vehicle.js.map +0 -1
  303. package/dist/types/vehicle_data.d.ts +0 -274
  304. package/dist/types/vehicle_data.js +0 -2
  305. package/dist/types/vehicle_data.js.map +0 -1
  306. package/dist/user.d.ts +0 -16
  307. package/dist/user.js +0 -26
  308. package/dist/user.js.map +0 -1
  309. package/dist/vehicle.d.ts +0 -548
  310. package/dist/vehicle.js +0 -774
  311. package/dist/vehicle.js.map +0 -1
  312. package/dist/vehiclesigned.d.ts +0 -13
  313. package/dist/vehiclesigned.js +0 -19
  314. package/dist/vehiclesigned.js.map +0 -1
  315. package/dist/vehiclespecific.d.ts +0 -432
  316. package/dist/vehiclespecific.js +0 -601
  317. package/dist/vehiclespecific.js.map +0 -1
  318. package/src/charging.ts +0 -66
  319. package/src/commands.ts +0 -1134
  320. package/src/energy.ts +0 -217
  321. package/src/energyspecific.ts +0 -116
  322. package/src/helpers.ts +0 -11
  323. package/src/index.ts +0 -21
  324. package/src/partner.ts +0 -37
  325. package/src/signing/crypto.ts +0 -29
  326. package/src/signing/errors.ts +0 -50
  327. package/src/signing/session.ts +0 -132
  328. package/src/tariff.ts +0 -375
  329. package/src/teslafleetapi.ts +0 -182
  330. package/src/teslemetry.ts +0 -54
  331. package/src/tessie.ts +0 -42
  332. package/src/types/commands.ts +0 -62
  333. package/src/types/live_status.ts +0 -27
  334. package/src/types/products.ts +0 -61
  335. package/src/types/responses.ts +0 -1
  336. package/src/types/site_info.ts +0 -159
  337. package/src/types/vehicle.ts +0 -33
  338. package/src/types/vehicle_data.ts +0 -286
  339. package/src/user.ts +0 -35
  340. package/src/vehicle.ts +0 -886
  341. package/src/vehiclesigned.ts +0 -20
  342. package/src/vehiclespecific.ts +0 -818
@@ -0,0 +1,291 @@
1
+ import { AsyncLock, withTimeout } from "./async.mjs";
2
+ import { APPEARANCE_UUID, CCCD_UUID, DEFAULT_SCAN_TIMEOUT_MS, GENERIC_ACCESS_UUID, NAME_UUID, SERVICE_UUID, VEHICLE_NOTIFY_UUID, VEHICLE_WRITE_UUID, VERSION_UUID } from "./constants.mjs";
3
+ import { BleNotSubmittedError } from "./transport.mjs";
4
+ import { LinkClaims, compactUuid, quietly } from "./adapters/shared.mjs";
5
+ // src/bluetooth/adapters/esphome.ts
6
+ /** The bytes that turn notifications on in a CCCD. */
7
+ const CCCD_ENABLE_NOTIFICATIONS = Buffer.from([1, 0]);
8
+ /** How long a proxy takes to answer a connect. The package's own wait is the same. */
9
+ const DEVICE_CONNECT_WAIT_MS = 1e4;
10
+ /** How long the API connection to the proxy may take. */
11
+ const API_CONNECT_TIMEOUT_MS = 1e4;
12
+ const ADVERTISEMENT = "message.BluetoothLEAdvertisementResponse";
13
+ const CONNECTION_STATE = "message.BluetoothDeviceConnectionResponse";
14
+ const NOTIFY_DATA = "message.BluetoothGATTNotifyDataResponse";
15
+ /** One queue per proxy, however many transports or links share it. */
16
+ const proxyQueues = /* @__PURE__ */ new WeakMap();
17
+ function queueFor(proxy) {
18
+ let queue = proxyQueues.get(proxy);
19
+ if (!queue) {
20
+ queue = new AsyncLock();
21
+ proxyQueues.set(proxy, queue);
22
+ }
23
+ return queue;
24
+ }
25
+ /** A proxy's 48-bit address as a MAC, the form a caller would recognize. */
26
+ function formatAddress(address) {
27
+ return address.toString(16).padStart(12, "0").replace(/(..)(?=.)/g, "$1:").toUpperCase();
28
+ }
29
+ function parseAddress(id) {
30
+ const hex = id.replace(/:/g, "");
31
+ if (!/^[0-9a-fA-F]{12}$/.test(hex)) throw new Error("An ESPHome device id must be a 48-bit address.");
32
+ return Number.parseInt(hex, 16);
33
+ }
34
+ function sameUuid(entry, uuid) {
35
+ return entry.uuid ? compactUuid(entry.uuid) === compactUuid(uuid) : (entry.shortUuid ?? 0).toString(16).padStart(4, "0") === compactUuid(uuid);
36
+ }
37
+ /** A late reply to an abandoned call can land on the next call of its type. */
38
+ function assertReplyFor(reply, address, handle) {
39
+ if (reply.address !== address || reply.handle !== handle) throw new Error("The proxy's reply was for another request.");
40
+ }
41
+ var EsphomeConnection = class {
42
+ proxy;
43
+ address;
44
+ id;
45
+ handlers;
46
+ claims;
47
+ lost = false;
48
+ closed = false;
49
+ closing;
50
+ handles = /* @__PURE__ */ new Map();
51
+ mtu = 0;
52
+ onLost = () => {
53
+ if (this.lost) return;
54
+ this.lost = true;
55
+ this.stopListening();
56
+ if (!this.closed) this.handlers.onDisconnect();
57
+ };
58
+ onState = (state) => {
59
+ if (state.address === this.address && !state.connected) this.onLost();
60
+ };
61
+ notifyHandle;
62
+ onNotifyData = (message) => {
63
+ if (message.address === this.address && message.handle === this.notifyHandle) this.handlers.onNotify(Buffer.from(message.data, "base64"));
64
+ };
65
+ constructor(proxy, address, id, handlers, claims) {
66
+ this.proxy = proxy;
67
+ this.address = address;
68
+ this.id = id;
69
+ this.handlers = handlers;
70
+ this.claims = claims;
71
+ }
72
+ get queue() {
73
+ return queueFor(this.proxy);
74
+ }
75
+ get isConnected() {
76
+ return !this.lost && !this.closed && this.proxy.connected && this.proxy.authorized;
77
+ }
78
+ get maxWriteSize() {
79
+ return this.mtu > 3 ? this.mtu - 3 : 20;
80
+ }
81
+ /** Ask the proxy to connect. The reply is matched here, as a link dropping elsewhere sends the same message type. */
82
+ requestLink(addressType) {
83
+ const { proxy, address } = this;
84
+ return this.queue.run(async () => {
85
+ let onReply;
86
+ const reply = new Promise((resolve) => {
87
+ onReply = (state) => {
88
+ if (state.address === address) resolve(state);
89
+ };
90
+ });
91
+ proxy.on(CONNECTION_STATE, onReply);
92
+ try {
93
+ return await withTimeout(() => Promise.race([reply, proxy.connectBluetoothDeviceService(address, addressType, false).then(() => reply)]), DEVICE_CONNECT_WAIT_MS);
94
+ } finally {
95
+ proxy.off(CONNECTION_STATE, onReply);
96
+ }
97
+ });
98
+ }
99
+ /** Connect, map the handles, and subscribe. Resolves once the CCCD write is done. */
100
+ async open(addressType) {
101
+ const state = await this.requestLink(addressType);
102
+ if (!state.connected || state.error) {
103
+ this.lost = true;
104
+ throw new Error(`The proxy could not connect: error ${state.error}.`);
105
+ }
106
+ this.assertCurrent();
107
+ this.mtu = state.mtu;
108
+ const { proxy, address } = this;
109
+ proxy.on(CONNECTION_STATE, this.onState);
110
+ proxy.on("disconnected", this.onLost);
111
+ proxy.on("unauthorized", this.onLost);
112
+ const { servicesList } = await this.queue.run(() => proxy.listBluetoothGATTServicesService(address));
113
+ const service = servicesList.find((candidate) => sameUuid(candidate, SERVICE_UUID));
114
+ const characteristic = (uuid) => service?.characteristicsList.find((candidate) => sameUuid(candidate, uuid));
115
+ const write = characteristic(VEHICLE_WRITE_UUID);
116
+ const notify = characteristic(VEHICLE_NOTIFY_UUID);
117
+ const cccd = notify?.descriptorsList.find((descriptor) => sameUuid(descriptor, CCCD_UUID));
118
+ if (!write || !notify || !cccd) throw new Error("The vehicle's write or notify characteristic, or its CCCD, was not found.");
119
+ this.handles.set("write", write.handle);
120
+ const version = characteristic(VERSION_UUID);
121
+ if (version) this.handles.set("version", version.handle);
122
+ const genericAccess = servicesList.find((candidate) => sameUuid(candidate, GENERIC_ACCESS_UUID));
123
+ for (const [name, uuid] of [["deviceName", NAME_UUID], ["appearance", APPEARANCE_UUID]]) {
124
+ const entry = genericAccess?.characteristicsList.find((candidate) => sameUuid(candidate, uuid));
125
+ if (entry) this.handles.set(name, entry.handle);
126
+ }
127
+ this.assertCurrent();
128
+ this.notifyHandle = notify.handle;
129
+ proxy.on(NOTIFY_DATA, this.onNotifyData);
130
+ await this.queue.run(() => proxy.notifyBluetoothGATTCharacteristicService(address, notify.handle));
131
+ await this.queue.run(() => proxy.writeBluetoothGATTDescriptorService(address, cccd.handle, CCCD_ENABLE_NOTIFICATIONS));
132
+ }
133
+ /** A superseded attempt stops here, before it changes a link another attempt owns. */
134
+ assertCurrent() {
135
+ if (this.claims.superseded(this.id, this)) throw new Error("A newer connect attempt took over this link.");
136
+ }
137
+ async write(data) {
138
+ const { proxy, address } = this;
139
+ const handle = this.handles.get("write");
140
+ if (handle === void 0) throw new BleNotSubmittedError("The vehicle write characteristic was not found.");
141
+ const queuedAt = performance.now();
142
+ await this.queue.run(async () => {
143
+ if (!this.isConnected) throw new BleNotSubmittedError("The link is down.");
144
+ if (performance.now() - queuedAt >= 5e3) throw new BleNotSubmittedError("The write outlasted its timeout in the proxy queue.");
145
+ assertReplyFor(await proxy.writeBluetoothGATTCharacteristicService(address, handle, Buffer.from(data), true), address, handle);
146
+ });
147
+ }
148
+ async read(characteristic) {
149
+ const { proxy, address } = this;
150
+ const handle = this.handles.get(characteristic);
151
+ if (handle === void 0) throw new BleNotSubmittedError(`The ${characteristic} characteristic was not found.`);
152
+ const reply = await this.queue.run(() => {
153
+ if (!this.isConnected) throw new BleNotSubmittedError("The link is down.");
154
+ return proxy.readBluetoothGATTCharacteristicService(address, handle);
155
+ });
156
+ assertReplyFor(reply, address, handle);
157
+ return Buffer.from(reply.data, "base64");
158
+ }
159
+ stopListening() {
160
+ const { proxy } = this;
161
+ proxy.off(CONNECTION_STATE, this.onState);
162
+ proxy.off("disconnected", this.onLost);
163
+ proxy.off("unauthorized", this.onLost);
164
+ proxy.off(NOTIFY_DATA, this.onNotifyData);
165
+ }
166
+ disconnect() {
167
+ this.closing ??= (async () => {
168
+ this.closed = true;
169
+ this.stopListening();
170
+ const { claims, id, proxy, address } = this;
171
+ const superseded = claims.superseded(id, this);
172
+ claims.release(id, this);
173
+ if (!superseded && !this.lost && proxy.authorized) await quietly(() => this.queue.run(() => proxy.disconnectBluetoothDeviceService(address)));
174
+ })();
175
+ return this.closing;
176
+ }
177
+ };
178
+ var EsphomeBleTransport = class {
179
+ options;
180
+ proxy;
181
+ ownsProxy;
182
+ scans = new AsyncLock();
183
+ claims = new LinkClaims();
184
+ /** A proxy refuses a connect with no address type, and only an advertisement carries it. */
185
+ addressTypes = /* @__PURE__ */ new Map();
186
+ constructor(options) {
187
+ this.options = options;
188
+ this.ownsProxy = !("connection" in options);
189
+ if ("connection" in options) this.proxy = options.connection;
190
+ }
191
+ /** The API connection, up and authorized. */
192
+ async ready() {
193
+ const proxy = this.proxy ??= await this.buildProxy();
194
+ if (proxy.authorized) return proxy;
195
+ if (this.ownsProxy && !proxy.connected) proxy.connect();
196
+ let onAuthorized;
197
+ let onError;
198
+ try {
199
+ await withTimeout(() => new Promise((resolve, reject) => {
200
+ onAuthorized = resolve;
201
+ onError = reject;
202
+ proxy.on("authorized", onAuthorized);
203
+ proxy.on("error", onError);
204
+ }), API_CONNECT_TIMEOUT_MS);
205
+ } finally {
206
+ proxy.off("authorized", onAuthorized);
207
+ proxy.off("error", onError);
208
+ }
209
+ return proxy;
210
+ }
211
+ async buildProxy() {
212
+ if ("connection" in this.options) return this.options.connection;
213
+ const specifier = "@2colors/esphome-native-api";
214
+ let module;
215
+ try {
216
+ module = await import(specifier);
217
+ } catch (cause) {
218
+ throw new Error("EsphomeBleTransport needs @2colors/esphome-native-api, or a `connection` option.", { cause });
219
+ }
220
+ const Connection = module.Connection ?? module.default?.Connection;
221
+ if (!Connection) throw new Error("@2colors/esphome-native-api has no Connection.");
222
+ const { host, port, encryptionKey, password } = this.options;
223
+ const proxy = new Connection({
224
+ host,
225
+ port,
226
+ encryptionKey,
227
+ password
228
+ });
229
+ proxy.on("error", () => void 0);
230
+ return proxy;
231
+ }
232
+ async scan({ timeoutMs, matches }) {
233
+ const proxy = await this.ready();
234
+ return this.listen(proxy, timeoutMs, (device) => device.name !== void 0 && matches?.(device.name) === true);
235
+ }
236
+ /** Listen to advertisements until `done` accepts a device or the window ends. A proxy filters nothing. */
237
+ listen(proxy, timeoutMs, done) {
238
+ return this.scans.run(async () => {
239
+ const seen = /* @__PURE__ */ new Map();
240
+ let finish;
241
+ const finished = new Promise((resolve) => {
242
+ finish = resolve;
243
+ });
244
+ const onAdvertisement = (advertisement) => {
245
+ const id = formatAddress(advertisement.address);
246
+ this.addressTypes.set(id, advertisement.addressType ?? this.addressTypes.get(id));
247
+ const device = {
248
+ id,
249
+ name: advertisement.name || seen.get(id)?.name,
250
+ rssi: advertisement.rssi
251
+ };
252
+ seen.set(id, device);
253
+ if (done(device)) finish();
254
+ };
255
+ proxy.on(ADVERTISEMENT, onAdvertisement);
256
+ const timer = setTimeout(finish, Math.max(0, timeoutMs - 250));
257
+ try {
258
+ proxy.subscribeBluetoothAdvertisementService();
259
+ await finished;
260
+ } finally {
261
+ clearTimeout(timer);
262
+ proxy.off(ADVERTISEMENT, onAdvertisement);
263
+ await quietly(() => proxy.unsubscribeBluetoothAdvertisementService());
264
+ }
265
+ return [...seen.values()];
266
+ });
267
+ }
268
+ async connect(device, handlers) {
269
+ const address = parseAddress(device.id);
270
+ const id = formatAddress(address);
271
+ const proxy = await this.ready();
272
+ if (this.addressTypes.get(id) === void 0) await this.listen(proxy, DEFAULT_SCAN_TIMEOUT_MS, (seen) => seen.id === id && this.addressTypes.get(id) !== void 0);
273
+ const connection = new EsphomeConnection(proxy, address, id, handlers, this.claims);
274
+ this.claims.claim(id, connection);
275
+ try {
276
+ await connection.open(this.addressTypes.get(id));
277
+ } catch (error) {
278
+ await connection.disconnect();
279
+ throw error;
280
+ }
281
+ return connection;
282
+ }
283
+ /** Close the API connection, if this transport opened it. */
284
+ close() {
285
+ if (this.ownsProxy) {
286
+ this.proxy?.disconnect();
287
+ this.proxy = void 0;
288
+ }
289
+ }
290
+ };
291
+ export { EsphomeBleTransport };
@@ -0,0 +1,125 @@
1
+ require("./constants.cjs");
2
+ let _teslemetry_tesla_protocol_command_universal_message = require("@teslemetry/tesla-protocol/command/universal_message");
3
+ let _teslemetry_tesla_protocol_command_vcsec = require("@teslemetry/tesla-protocol/command/vcsec");
4
+ // src/bluetooth/framing.ts
5
+ /** Prefix a payload with its big-endian 2-byte length, which counts the payload only. */
6
+ function prependLength(message) {
7
+ const header = Buffer.alloc(2);
8
+ header.writeUInt16BE(message.length);
9
+ return Buffer.concat([header, message]);
10
+ }
11
+ /**
12
+ * Cut a frame into the writes that carry it.
13
+ * @param maxWriteSize Largest bytes per write. `null` means the backend accepts a full frame, as Python's do.
14
+ */
15
+ function splitFrame(frame, maxWriteSize) {
16
+ if (maxWriteSize === null) return [frame];
17
+ if (!Number.isInteger(maxWriteSize) || maxWriteSize <= 0) throw new RangeError(`maxWriteSize must be a positive integer, got ${maxWriteSize}.`);
18
+ const chunks = [];
19
+ for (let start = 0; start < frame.length; start += maxWriteSize) chunks.push(frame.subarray(start, start + maxWriteSize));
20
+ return chunks;
21
+ }
22
+ /** Whether a decoded `RoutableMessage` holds nothing: ts-proto's shape for "no known field". */
23
+ function hasNoKnownField(message) {
24
+ return message.toDestination === void 0 && message.fromDestination === void 0 && message.protobufMessageAsBytes === void 0 && message.sessionInfoRequest === void 0 && message.sessionInfo === void 0 && message.signatureData === void 0 && message.signedMessageStatus === void 0 && message.messageFrame === void 0 && message.requestUuid.length === 0 && message.uuid.length === 0 && message.flags === 0;
25
+ }
26
+ /**
27
+ * Parse one reassembled frame, wrapping a bare VCSEC message.
28
+ *
29
+ * On a fresh link the car sends `FromVCSECMessage` frames, such as a repeating
30
+ * `authenticationRequest`, with no `RoutableMessage` around them. None of its
31
+ * field numbers exist on `RoutableMessage`, so one parses as a message with no
32
+ * known field. It is wrapped as an unaddressed VCSEC message, so it reaches the
33
+ * broadcast listeners as a wrapped one would.
34
+ * @throws When the frame is not a `RoutableMessage`.
35
+ */
36
+ function parseFrame(payload) {
37
+ const message = _teslemetry_tesla_protocol_command_universal_message.RoutableMessage.decode(payload);
38
+ if (payload.length === 0 || !hasNoKnownField(message)) return message;
39
+ let vcsec;
40
+ try {
41
+ vcsec = _teslemetry_tesla_protocol_command_vcsec.FromVCSECMessage.decode(payload);
42
+ } catch {
43
+ return message;
44
+ }
45
+ if (!Object.values(vcsec).some((field) => field !== void 0)) return message;
46
+ return _teslemetry_tesla_protocol_command_universal_message.RoutableMessage.create({
47
+ fromDestination: { domain: _teslemetry_tesla_protocol_command_universal_message.Domain.DOMAIN_VEHICLE_SECURITY },
48
+ protobufMessageAsBytes: payload
49
+ });
50
+ }
51
+ /**
52
+ * Reassembles BLE notifications into length-prefixed messages.
53
+ *
54
+ * One notification can hold part of a message, one message, or several. A
55
+ * message that cannot be decoded drops the current notification and resyncs at
56
+ * the next one. A partial message is also dropped if its next chunk does not
57
+ * arrive within `STALE_CHUNK_TIMEOUT_MS`.
58
+ */
59
+ var ReassemblingBuffer = class {
60
+ callback;
61
+ buffer = Buffer.alloc(0);
62
+ expectedLength = null;
63
+ /** Where each notification still in `buffer` starts: the only places a resync can land. */
64
+ packetStarts = [];
65
+ lastChunkAt = null;
66
+ now;
67
+ /**
68
+ * @param callback Called with each parsed message. Its errors are not caught.
69
+ * @param now The monotonic clock, in milliseconds. Injectable for tests.
70
+ */
71
+ constructor(callback, now = () => performance.now()) {
72
+ this.callback = callback;
73
+ this.now = now;
74
+ }
75
+ /** Take one notification and emit every message it completes. Malformed bytes never throw. */
76
+ receive(chunk) {
77
+ const now = this.now();
78
+ if (this.buffer.length > 0 && this.lastChunkAt !== null && now - this.lastChunkAt > 1e3) {
79
+ this.buffer = Buffer.alloc(0);
80
+ this.expectedLength = null;
81
+ this.packetStarts = [];
82
+ }
83
+ this.lastChunkAt = now;
84
+ this.packetStarts.push(this.buffer.length);
85
+ this.buffer = Buffer.concat([this.buffer, chunk]);
86
+ for (;;) {
87
+ if (this.expectedLength === null && this.buffer.length >= 2) this.expectedLength = this.buffer.readUInt16BE(0) + 2;
88
+ if (this.expectedLength === null) return;
89
+ if (this.expectedLength > 1024) {
90
+ this.discardPacket();
91
+ continue;
92
+ }
93
+ if (this.buffer.length < this.expectedLength) return;
94
+ let message;
95
+ try {
96
+ message = parseFrame(this.buffer.subarray(2, this.expectedLength));
97
+ } catch {
98
+ this.discardPacket();
99
+ continue;
100
+ }
101
+ const consumed = this.expectedLength;
102
+ this.buffer = this.buffer.subarray(consumed);
103
+ this.packetStarts = this.packetStarts.filter((start) => start >= consumed).map((start) => start - consumed);
104
+ this.expectedLength = null;
105
+ this.callback(message);
106
+ }
107
+ }
108
+ /** Drop the current notification and resync at the next, if there is one. */
109
+ discardPacket() {
110
+ this.packetStarts.shift();
111
+ const next = this.packetStarts[0];
112
+ if (next === void 0) {
113
+ this.buffer = Buffer.alloc(0);
114
+ this.packetStarts = [];
115
+ } else {
116
+ this.buffer = this.buffer.subarray(next);
117
+ this.packetStarts = this.packetStarts.map((start) => start - next);
118
+ }
119
+ this.expectedLength = null;
120
+ }
121
+ };
122
+ exports.ReassemblingBuffer = ReassemblingBuffer;
123
+ exports.parseFrame = parseFrame;
124
+ exports.prependLength = prependLength;
125
+ exports.splitFrame = splitFrame;
@@ -0,0 +1,45 @@
1
+ import { RoutableMessage } from "@teslemetry/tesla-protocol/command/universal_message";
2
+ /** Prefix a payload with its big-endian 2-byte length, which counts the payload only. */
3
+ export declare function prependLength(message: Uint8Array): Buffer;
4
+ /**
5
+ * Cut a frame into the writes that carry it.
6
+ * @param maxWriteSize Largest bytes per write. `null` means the backend accepts a full frame, as Python's do.
7
+ */
8
+ export declare function splitFrame(frame: Uint8Array, maxWriteSize: number | null): Uint8Array[];
9
+ /**
10
+ * Parse one reassembled frame, wrapping a bare VCSEC message.
11
+ *
12
+ * On a fresh link the car sends `FromVCSECMessage` frames, such as a repeating
13
+ * `authenticationRequest`, with no `RoutableMessage` around them. None of its
14
+ * field numbers exist on `RoutableMessage`, so one parses as a message with no
15
+ * known field. It is wrapped as an unaddressed VCSEC message, so it reaches the
16
+ * broadcast listeners as a wrapped one would.
17
+ * @throws When the frame is not a `RoutableMessage`.
18
+ */
19
+ export declare function parseFrame(payload: Uint8Array): RoutableMessage;
20
+ /**
21
+ * Reassembles BLE notifications into length-prefixed messages.
22
+ *
23
+ * One notification can hold part of a message, one message, or several. A
24
+ * message that cannot be decoded drops the current notification and resyncs at
25
+ * the next one. A partial message is also dropped if its next chunk does not
26
+ * arrive within `STALE_CHUNK_TIMEOUT_MS`.
27
+ */
28
+ export declare class ReassemblingBuffer {
29
+ private readonly callback;
30
+ private buffer;
31
+ private expectedLength;
32
+ /** Where each notification still in `buffer` starts: the only places a resync can land. */
33
+ private packetStarts;
34
+ private lastChunkAt;
35
+ private readonly now;
36
+ /**
37
+ * @param callback Called with each parsed message. Its errors are not caught.
38
+ * @param now The monotonic clock, in milliseconds. Injectable for tests.
39
+ */
40
+ constructor(callback: (message: RoutableMessage) => void, now?: () => number);
41
+ /** Take one notification and emit every message it completes. Malformed bytes never throw. */
42
+ receive(chunk: Uint8Array): void;
43
+ /** Drop the current notification and resync at the next, if there is one. */
44
+ private discardPacket;
45
+ }
@@ -0,0 +1,45 @@
1
+ import { RoutableMessage } from "@teslemetry/tesla-protocol/command/universal_message";
2
+ /** Prefix a payload with its big-endian 2-byte length, which counts the payload only. */
3
+ export declare function prependLength(message: Uint8Array): Buffer;
4
+ /**
5
+ * Cut a frame into the writes that carry it.
6
+ * @param maxWriteSize Largest bytes per write. `null` means the backend accepts a full frame, as Python's do.
7
+ */
8
+ export declare function splitFrame(frame: Uint8Array, maxWriteSize: number | null): Uint8Array[];
9
+ /**
10
+ * Parse one reassembled frame, wrapping a bare VCSEC message.
11
+ *
12
+ * On a fresh link the car sends `FromVCSECMessage` frames, such as a repeating
13
+ * `authenticationRequest`, with no `RoutableMessage` around them. None of its
14
+ * field numbers exist on `RoutableMessage`, so one parses as a message with no
15
+ * known field. It is wrapped as an unaddressed VCSEC message, so it reaches the
16
+ * broadcast listeners as a wrapped one would.
17
+ * @throws When the frame is not a `RoutableMessage`.
18
+ */
19
+ export declare function parseFrame(payload: Uint8Array): RoutableMessage;
20
+ /**
21
+ * Reassembles BLE notifications into length-prefixed messages.
22
+ *
23
+ * One notification can hold part of a message, one message, or several. A
24
+ * message that cannot be decoded drops the current notification and resyncs at
25
+ * the next one. A partial message is also dropped if its next chunk does not
26
+ * arrive within `STALE_CHUNK_TIMEOUT_MS`.
27
+ */
28
+ export declare class ReassemblingBuffer {
29
+ private readonly callback;
30
+ private buffer;
31
+ private expectedLength;
32
+ /** Where each notification still in `buffer` starts: the only places a resync can land. */
33
+ private packetStarts;
34
+ private lastChunkAt;
35
+ private readonly now;
36
+ /**
37
+ * @param callback Called with each parsed message. Its errors are not caught.
38
+ * @param now The monotonic clock, in milliseconds. Injectable for tests.
39
+ */
40
+ constructor(callback: (message: RoutableMessage) => void, now?: () => number);
41
+ /** Take one notification and emit every message it completes. Malformed bytes never throw. */
42
+ receive(chunk: Uint8Array): void;
43
+ /** Drop the current notification and resync at the next, if there is one. */
44
+ private discardPacket;
45
+ }
@@ -0,0 +1,122 @@
1
+ import "./constants.mjs";
2
+ import { Domain, RoutableMessage } from "@teslemetry/tesla-protocol/command/universal_message";
3
+ import { FromVCSECMessage } from "@teslemetry/tesla-protocol/command/vcsec";
4
+ // src/bluetooth/framing.ts
5
+ /** Prefix a payload with its big-endian 2-byte length, which counts the payload only. */
6
+ function prependLength(message) {
7
+ const header = Buffer.alloc(2);
8
+ header.writeUInt16BE(message.length);
9
+ return Buffer.concat([header, message]);
10
+ }
11
+ /**
12
+ * Cut a frame into the writes that carry it.
13
+ * @param maxWriteSize Largest bytes per write. `null` means the backend accepts a full frame, as Python's do.
14
+ */
15
+ function splitFrame(frame, maxWriteSize) {
16
+ if (maxWriteSize === null) return [frame];
17
+ if (!Number.isInteger(maxWriteSize) || maxWriteSize <= 0) throw new RangeError(`maxWriteSize must be a positive integer, got ${maxWriteSize}.`);
18
+ const chunks = [];
19
+ for (let start = 0; start < frame.length; start += maxWriteSize) chunks.push(frame.subarray(start, start + maxWriteSize));
20
+ return chunks;
21
+ }
22
+ /** Whether a decoded `RoutableMessage` holds nothing: ts-proto's shape for "no known field". */
23
+ function hasNoKnownField(message) {
24
+ return message.toDestination === void 0 && message.fromDestination === void 0 && message.protobufMessageAsBytes === void 0 && message.sessionInfoRequest === void 0 && message.sessionInfo === void 0 && message.signatureData === void 0 && message.signedMessageStatus === void 0 && message.messageFrame === void 0 && message.requestUuid.length === 0 && message.uuid.length === 0 && message.flags === 0;
25
+ }
26
+ /**
27
+ * Parse one reassembled frame, wrapping a bare VCSEC message.
28
+ *
29
+ * On a fresh link the car sends `FromVCSECMessage` frames, such as a repeating
30
+ * `authenticationRequest`, with no `RoutableMessage` around them. None of its
31
+ * field numbers exist on `RoutableMessage`, so one parses as a message with no
32
+ * known field. It is wrapped as an unaddressed VCSEC message, so it reaches the
33
+ * broadcast listeners as a wrapped one would.
34
+ * @throws When the frame is not a `RoutableMessage`.
35
+ */
36
+ function parseFrame(payload) {
37
+ const message = RoutableMessage.decode(payload);
38
+ if (payload.length === 0 || !hasNoKnownField(message)) return message;
39
+ let vcsec;
40
+ try {
41
+ vcsec = FromVCSECMessage.decode(payload);
42
+ } catch {
43
+ return message;
44
+ }
45
+ if (!Object.values(vcsec).some((field) => field !== void 0)) return message;
46
+ return RoutableMessage.create({
47
+ fromDestination: { domain: Domain.DOMAIN_VEHICLE_SECURITY },
48
+ protobufMessageAsBytes: payload
49
+ });
50
+ }
51
+ /**
52
+ * Reassembles BLE notifications into length-prefixed messages.
53
+ *
54
+ * One notification can hold part of a message, one message, or several. A
55
+ * message that cannot be decoded drops the current notification and resyncs at
56
+ * the next one. A partial message is also dropped if its next chunk does not
57
+ * arrive within `STALE_CHUNK_TIMEOUT_MS`.
58
+ */
59
+ var ReassemblingBuffer = class {
60
+ callback;
61
+ buffer = Buffer.alloc(0);
62
+ expectedLength = null;
63
+ /** Where each notification still in `buffer` starts: the only places a resync can land. */
64
+ packetStarts = [];
65
+ lastChunkAt = null;
66
+ now;
67
+ /**
68
+ * @param callback Called with each parsed message. Its errors are not caught.
69
+ * @param now The monotonic clock, in milliseconds. Injectable for tests.
70
+ */
71
+ constructor(callback, now = () => performance.now()) {
72
+ this.callback = callback;
73
+ this.now = now;
74
+ }
75
+ /** Take one notification and emit every message it completes. Malformed bytes never throw. */
76
+ receive(chunk) {
77
+ const now = this.now();
78
+ if (this.buffer.length > 0 && this.lastChunkAt !== null && now - this.lastChunkAt > 1e3) {
79
+ this.buffer = Buffer.alloc(0);
80
+ this.expectedLength = null;
81
+ this.packetStarts = [];
82
+ }
83
+ this.lastChunkAt = now;
84
+ this.packetStarts.push(this.buffer.length);
85
+ this.buffer = Buffer.concat([this.buffer, chunk]);
86
+ for (;;) {
87
+ if (this.expectedLength === null && this.buffer.length >= 2) this.expectedLength = this.buffer.readUInt16BE(0) + 2;
88
+ if (this.expectedLength === null) return;
89
+ if (this.expectedLength > 1024) {
90
+ this.discardPacket();
91
+ continue;
92
+ }
93
+ if (this.buffer.length < this.expectedLength) return;
94
+ let message;
95
+ try {
96
+ message = parseFrame(this.buffer.subarray(2, this.expectedLength));
97
+ } catch {
98
+ this.discardPacket();
99
+ continue;
100
+ }
101
+ const consumed = this.expectedLength;
102
+ this.buffer = this.buffer.subarray(consumed);
103
+ this.packetStarts = this.packetStarts.filter((start) => start >= consumed).map((start) => start - consumed);
104
+ this.expectedLength = null;
105
+ this.callback(message);
106
+ }
107
+ }
108
+ /** Drop the current notification and resync at the next, if there is one. */
109
+ discardPacket() {
110
+ this.packetStarts.shift();
111
+ const next = this.packetStarts[0];
112
+ if (next === void 0) {
113
+ this.buffer = Buffer.alloc(0);
114
+ this.packetStarts = [];
115
+ } else {
116
+ this.buffer = this.buffer.subarray(next);
117
+ this.packetStarts = this.packetStarts.map((start) => start - next);
118
+ }
119
+ this.expectedLength = null;
120
+ }
121
+ };
122
+ export { ReassemblingBuffer, parseFrame, prependLength, splitFrame };