mcp-3d-printer-server 1.2.25 → 1.2.26

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 (93) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/CONTRIBUTORS.md +5 -1
  3. package/README.md +64 -29
  4. package/dist/ams-mapping.d.ts +38 -0
  5. package/dist/ams-mapping.js +125 -0
  6. package/dist/bambu-network-bridge.d.ts +200 -0
  7. package/dist/bambu-network-bridge.js +881 -0
  8. package/dist/bambu-network-certs.d.ts +13 -0
  9. package/dist/bambu-network-certs.js +152 -0
  10. package/dist/control-reply.d.ts +21 -0
  11. package/dist/control-reply.js +44 -0
  12. package/dist/counterpart/camera-sources.d.ts +16 -0
  13. package/dist/counterpart/camera-sources.js +138 -0
  14. package/dist/counterpart/camera-stream.d.ts +66 -0
  15. package/dist/counterpart/camera-stream.js +246 -0
  16. package/dist/counterpart/camera-tools.d.ts +247 -0
  17. package/dist/counterpart/camera-tools.js +35 -0
  18. package/dist/counterpart/contract.d.ts +21 -5
  19. package/dist/counterpart/contract.js +8 -2
  20. package/dist/counterpart/route.d.ts +31 -0
  21. package/dist/counterpart/route.js +70 -0
  22. package/dist/counterpart/service.d.ts +60 -4
  23. package/dist/counterpart/service.js +142 -16
  24. package/dist/design-guidance.d.ts +26 -0
  25. package/dist/design-guidance.js +177 -0
  26. package/dist/fulu-orca.d.ts +7 -0
  27. package/dist/fulu-orca.js +18 -2
  28. package/dist/index.js +512 -62
  29. package/dist/printers/bambu-filaments.d.ts +68 -0
  30. package/dist/printers/bambu-filaments.js +262 -0
  31. package/dist/printers/bambu.d.ts +99 -6
  32. package/dist/printers/bambu.js +478 -72
  33. package/dist/printers/creality.d.ts +2 -1
  34. package/dist/printers/creality.js +5 -1
  35. package/dist/printers/generic-base.d.ts +11 -6
  36. package/dist/printers/generic-base.js +21 -25
  37. package/dist/printers/klipper.d.ts +2 -1
  38. package/dist/printers/klipper.js +10 -7
  39. package/dist/printers/octoprint.d.ts +2 -1
  40. package/dist/printers/octoprint.js +15 -8
  41. package/dist/printers/printer-factory.d.ts +1 -1
  42. package/dist/printers/printer-factory.js +3 -3
  43. package/dist/printers/prusa.js +0 -1
  44. package/dist/safety/confirmation.d.ts +11 -16
  45. package/dist/safety/confirmation.js +8 -32
  46. package/dist/safety/hidden-hms.d.ts +32 -0
  47. package/dist/safety/hidden-hms.js +405 -0
  48. package/dist/safety/limits.d.ts +8 -2
  49. package/dist/safety/limits.js +14 -5
  50. package/dist/safety/print-file.d.ts +5 -0
  51. package/dist/safety/print-file.js +156 -19
  52. package/dist/safety/printer-state.d.ts +59 -8
  53. package/dist/safety/printer-state.js +174 -39
  54. package/dist/slicer/bambu-cli.d.ts +2 -0
  55. package/dist/slicer/bambu-cli.js +7 -5
  56. package/dist/slicer/layer-pauses.d.ts +47 -0
  57. package/dist/slicer/layer-pauses.js +130 -0
  58. package/dist/stl/stl-manipulator.d.ts +1 -0
  59. package/dist/stl/stl-manipulator.js +40 -0
  60. package/dist/studio-print-options.d.ts +57 -0
  61. package/dist/studio-print-options.js +179 -0
  62. package/package.json +25 -4
  63. package/server.json +2 -2
  64. package/src/ams-mapping.ts +132 -0
  65. package/src/bambu-network-bridge.ts +1075 -0
  66. package/src/bambu-network-certs.ts +154 -0
  67. package/src/control-reply.ts +41 -0
  68. package/src/counterpart/camera-sources.ts +102 -0
  69. package/src/counterpart/camera-stream.ts +195 -0
  70. package/src/counterpart/camera-tools.ts +35 -0
  71. package/src/counterpart/contract.ts +12 -4
  72. package/src/counterpart/route.ts +82 -0
  73. package/src/counterpart/service.ts +150 -16
  74. package/src/design-guidance.ts +188 -0
  75. package/src/fulu-orca.ts +18 -2
  76. package/src/index.ts +526 -75
  77. package/src/printers/bambu-filaments.ts +346 -0
  78. package/src/printers/bambu.ts +506 -81
  79. package/src/printers/creality.ts +8 -3
  80. package/src/printers/generic-base.ts +33 -40
  81. package/src/printers/klipper.ts +12 -8
  82. package/src/printers/octoprint.ts +16 -9
  83. package/src/printers/printer-factory.ts +3 -3
  84. package/src/printers/prusa.ts +0 -1
  85. package/src/safety/confirmation.ts +15 -38
  86. package/src/safety/hidden-hms.ts +406 -0
  87. package/src/safety/limits.ts +15 -5
  88. package/src/safety/print-file.ts +139 -17
  89. package/src/safety/printer-state.ts +179 -40
  90. package/src/slicer/bambu-cli.ts +8 -5
  91. package/src/slicer/layer-pauses.ts +143 -0
  92. package/src/stl/stl-manipulator.ts +45 -0
  93. package/src/studio-print-options.ts +201 -0
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Bambu's public certificates from Bambu Studio v02.08.02.61 resources/cert,
3
+ * the folder Bambu Studio and FULU's GUI pass to the networking plug-in's
4
+ * set_cert_file. printer.cer holds the BBL CA roots that sign each printer's
5
+ * LAN MQTT certificate; slicer_base64.cer is the cloud certificate. FULU's
6
+ * standalone Linux host runtime ships only slicer_base64.cer, so its plug-in
7
+ * cannot verify a LAN printer without these. Exact bytes, pinned by SHA-256
8
+ * (the same pins as scripts/setup-bambu-native.mjs).
9
+ */
10
+ export declare const BAMBU_NETWORK_CERTIFICATES: Readonly<Record<"printer.cer" | "slicer_base64.cer", {
11
+ sha256: string;
12
+ base64: string;
13
+ }>>;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Bambu's public certificates from Bambu Studio v02.08.02.61 resources/cert,
3
+ * the folder Bambu Studio and FULU's GUI pass to the networking plug-in's
4
+ * set_cert_file. printer.cer holds the BBL CA roots that sign each printer's
5
+ * LAN MQTT certificate; slicer_base64.cer is the cloud certificate. FULU's
6
+ * standalone Linux host runtime ships only slicer_base64.cer, so its plug-in
7
+ * cannot verify a LAN printer without these. Exact bytes, pinned by SHA-256
8
+ * (the same pins as scripts/setup-bambu-native.mjs).
9
+ */
10
+ export const BAMBU_NETWORK_CERTIFICATES = Object.freeze({
11
+ "printer.cer": {
12
+ sha256: "36f2bcee347ec7adce719b5fd350099591a4d3d0ec4e039c7019890d78e152a0",
13
+ base64: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZmekNDQTJlZ0F3SUJBZ0lVWHR6UjZ0UmlML1JIQlJYT295RlUwK1hybGlv" +
14
+ "d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1JqRUxNQWtHQTFVRUJoTUNRMDR4SVRBZkJnTlZCQW9NR0VKQ1RDQlVaV05vYm05c2IyZHBa" +
15
+ "WE1nUTI4dQpJRXgwWkRFVU1CSUdBMVVFQXd3TFFrSk1JRU5CTWlCU1UwRXdJQmNOTWpVd05qRTNNREV6T0RBNFdoZ1BNakExCk1E" +
16
+ "QTJNVGN3TVRNNE1EaGFNRVl4Q3pBSkJnTlZCQVlUQWtOT01TRXdId1lEVlFRS0RCaENRa3dnVkdWamFHNXYKYkc5bmFXVnpJRU52" +
17
+ "TGlCTWRHUXhGREFTQmdOVkJBTU1DMEpDVENCRFFUSWdVbE5CTUlJQ0lqQU5CZ2txaGtpRwo5dzBCQVFFRkFBT0NBZzhBTUlJQ0Nn" +
18
+ "S0NBZ0VBbzQ1NTBHNGM0MmdUS3pRcWl4d0tUMDg5Uml6SWRacHlPY0dBCjY3OXJQYU9kV3NNcVZ3bllQUDJGcE1xWEtrakZiZWRF" +
19
+ "K1NwR2xvaTJOS0N1aVBOVlJicTlQSE9PWndUczdZTG8KYk93ZjUzRkp1TzZ2UkZwekZmWDF0bGM5emxGcUp2Wm5ZTzlOZ0hwTXlz" +
20
+ "aWRvY1djZ3JETi9TSUR5d2dQQjVDVgpiWWczVnZ6dWE5ZndaeDllNUtUOXhkNUlwVHFkVHJXUzQ3alFPVktMaGRRQ2JKRklsTXJi" +
21
+ "bE93TEJBeCtmSG9rCndxaDZ0a0k2S3R1eXlqdzhEeXNlYmkxbmRXakt0WjJtVzQ3cjh4Wi9KK3ozRVpxY3lKTVk2TVJ0eC96YjFq" +
22
+ "QkYKdUh0a2pyYjVLdjFETXpTS2xrYU5KSWJ2QytNaytoSTk3VytTakxTUnVJZEM3K29KVXpXYVN6Z3U5Y2pYQ1ZmbQpxOHQ0SUwv" +
23
+ "MzVoUDY5UEs5NUxnTGVjdElyUDk2Q1lBVC9hVk1HMTlGckZXMFFXRXlmVCtrekc0amt1bWZQYkhxClkybk5rRU4wK3RqajNoNFdk" +
24
+ "enJXZ1FFb2pLL2xoZmNSRlZrdHM3NCthWm9NcFFQK3ZtTDE3Q0ttU3pYazVvL2UKSzIxeGd4SmR6TWJkenRmVHBpYmlYazBhYmZP" +
25
+ "cE4rMVZSKzNOWWErYlJPQUtOeUdhUmVFR3N5VzJiamNqTng1MQo1VnF6ajNTVnhoTVNwNXZmRjlFNEExakU5OU0vbDlqUURNNlJ6" +
26
+ "a1QwbE1jY0dBZDV0VVNkTnZEbHJxdFFhUWlLCnYvWnNYUGdYTFRXZk9wdmFMTkVnd2RNZ1pNdWhqcGt3dkFaeW9ZZmVGOWt5eWRq" +
27
+ "RGg3YnZyWC8vY3ovVm9wQVUKbHhVdFF0TUNBd0VBQWFOak1HRXdIUVlEVlIwT0JCWUVGTlZKZ1FhZDFzTlROMGp4Vmt3YkovWE0x" +
28
+ "YW4xTUI4RwpBMVVkSXdRWU1CYUFGTlZKZ1FhZDFzTlROMGp4Vmt3YkovWE0xYW4xTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3CkRn" +
29
+ "WURWUjBQQVFIL0JBUURBZ0VHTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElDQVFCRlpES01KZnAvTjRnQmVGSGgKTWlGZWhhVXlNUzZl" +
30
+ "OW16clRmTUxKTEpvajZKb3BhOVY5aklmY0NFQkdadVJUaHFGY0FUVitVZEZIU0lOcFVjSAp1cGNDWW5helRSQzRkbjFobnhuUTFv" +
31
+ "alFjSHhkR3A5eEd3L1ljbEFLRDk3ZDhiUFNoZkJNVDF0bzl6Yk1LN1Q1Ckw4emdxZzAxWUlPS2pRazBIY2QwKzBpVXI2bTh6UTVQ" +
32
+ "OFJsM1FYcUF5ZVdncW1ZUXJyalRXd1BzZ2RmSE5YS1gKdkRyeDcvY3FyeTVsS1U4MDJoVXBsS01CeGVsdjRXODQwN1l0ajFsZkpP" +
33
+ "d3Z4cXh4c0ZVNWpTd2NVRzN6bzJ2awpRdGpSczhtNUJLdXA1SzFPUFlra1B1N0xkODlYMFhwVTA3My9kTkRHMTF1eGIxZURLcnRO" +
34
+ "UDZ2WnVaak5FMlBxCjhIQ29JMUV0UCtJdHlxdFVNdkhpNloyenNtbEEyNWJyb1Zpb2VVS3hqbEllY3BROUpSL0ZoRHU5Q1dORi9u" +
35
+ "RFcKTFNPUk5hTU16Z3NNU3pJK0hDaVVocU4rcU1JdlZQNnJ6R1RKendxei9sYzVMZitaUENuR0E5V0pUVDR1UEloZgp1ZmJabW5V" +
36
+ "SjM1V3VXS0h4b3ZEc3FCaDg4elE5c1orZWk0SGk0dlZ6T2hVZ2ZHM2FMb1NRRVlxUm9xYWJvQU5oCndDd3p5dVcyUnY1NHU1UVNC" +
37
+ "YmQ2R3gxT3B2c1dtTFBXZDIvaUwya0lTbDV3Zm1MR1Z5ZHZTSmErcmJPZnVBeTcKeWNWUWFjVkRRQ0FuYmhvVnJReTcrNDU0UXNL" +
38
+ "U1czWlY2QmN5UnJvcmV3Q3lDWWdkN255eGZseEhaVEJFeWtYWApoYUdOZS9LRk52SkJNT0l1SVV6a25SUm1pUT09Ci0tLS0tRU5E" +
39
+ "IENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUI4ekNDQVptZ0F3SUJBZ0lVZTYxakdRNFJ6" +
40
+ "SUM4aytzTnVxYkkvQ2FOcVBJd0NnWUlLb1pJemowRUF3SXcKUmpFTE1Ba0dBMVVFQmhNQ1EwNHhJVEFmQmdOVkJBb01HRUpDVENC" +
41
+ "VVpXTm9ibTlzYjJkcFpYTWdRMjh1SUV4MApaREVVTUJJR0ExVUVBd3dMUWtKTUlFTkJNaUJGUTBNd0lCY05NalV3TmpFM01ERXpP" +
42
+ "RE0xV2hnUE1qQTFNREEyCk1UY3dNVE00TXpWYU1FWXhDekFKQmdOVkJBWVRBa05PTVNFd0h3WURWUVFLREJoQ1Frd2dWR1ZqYUc1" +
43
+ "dmJHOW4KYVdWeklFTnZMaUJNZEdReEZEQVNCZ05WQkFNTUMwSkNUQ0JEUVRJZ1JVTkRNRmt3RXdZSEtvWkl6ajBDQVFZSQpLb1pJ" +
44
+ "emowREFRY0RRZ0FFcEtURjd3UlN0eTREWHBHSnpnQ1B3Umg4Z2hMbHhVQzNxSmJ5RWdMcVR2SmdiaXdZCkFQUEhLN2tWYlZtZXJr" +
45
+ "cWhIT1Q0UWVXUmxURzNkT1FHTEEyVnBhTmpNR0V3SFFZRFZSME9CQllFRkt1UnBzalkKUkVPeUlLSDdId09FNmpoR0JkNk5NQjhH" +
46
+ "QTFVZEl3UVlNQmFBRkt1UnBzallSRU95SUtIN0h3T0U2amhHQmQ2TgpNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEZ1lEVlIwUEFR" +
47
+ "SC9CQVFEQWdFR01Bb0dDQ3FHU000OUJBTUNBMGdBCk1FVUNJRXJCaVVtM1ZkdFAzcno0a2I4YUxwSTVwK0J6TDdNOXZFbEJHV1dK" +
48
+ "eHBITUFpRUEzcjV0SldWR3d1eGkKWUNyQjFjNDBLWUZSRnlhaEdyaE9KWkFqL1loUmRuVT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUt" +
49
+ "LS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJRWVUQ0NBMkdnQXdJQkFnSVVPcStsTklhQzJ4c3N3a0ZxajVKUHlW" +
50
+ "Qmw0NWN3RFFZSktvWklodmNOQVFFTApCUUF3UWpFTE1Ba0dBMVVFQmhNQ1EwNHhJakFnQmdOVkJBb01HVUpDVENCVVpXTm9ibTlz" +
51
+ "YjJkcFpYTWdRMjh1CkxDQk1kR1F4RHpBTkJnTlZCQU1NQmtKQ1RDQkRRVEFlRncweU5UQTJNVGN3TWpBeE1qZGFGdzB6TlRBMk1U" +
52
+ "VXcKTWpBeE1qZGFNRVl4Q3pBSkJnTlZCQVlUQWtOT01TRXdId1lEVlFRS0RCaENRa3dnVkdWamFHNXZiRzluYVdWegpJRU52TGlC" +
53
+ "TWRHUXhGREFTQmdOVkJBTU1DMEpDVENCRFFUSWdVbE5CTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGCkFBT0NBZzhBTUlJQ0NnS0NB" +
54
+ "Z0VBbzQ1NTBHNGM0MmdUS3pRcWl4d0tUMDg5Uml6SWRacHlPY0dBNjc5clBhT2QKV3NNcVZ3bllQUDJGcE1xWEtrakZiZWRFK1Nw" +
55
+ "R2xvaTJOS0N1aVBOVlJicTlQSE9PWndUczdZTG9iT3dmNTNGSgp1TzZ2UkZwekZmWDF0bGM5emxGcUp2Wm5ZTzlOZ0hwTXlzaWRv" +
56
+ "Y1djZ3JETi9TSUR5d2dQQjVDVmJZZzNWdnp1CmE5ZndaeDllNUtUOXhkNUlwVHFkVHJXUzQ3alFPVktMaGRRQ2JKRklsTXJibE93" +
57
+ "TEJBeCtmSG9rd3FoNnRrSTYKS3R1eXlqdzhEeXNlYmkxbmRXakt0WjJtVzQ3cjh4Wi9KK3ozRVpxY3lKTVk2TVJ0eC96YjFqQkZ1" +
58
+ "SHRranJiNQpLdjFETXpTS2xrYU5KSWJ2QytNaytoSTk3VytTakxTUnVJZEM3K29KVXpXYVN6Z3U5Y2pYQ1ZmbXE4dDRJTC8zCjVo" +
59
+ "UDY5UEs5NUxnTGVjdElyUDk2Q1lBVC9hVk1HMTlGckZXMFFXRXlmVCtrekc0amt1bWZQYkhxWTJuTmtFTjAKK3RqajNoNFdkenJX" +
60
+ "Z1FFb2pLL2xoZmNSRlZrdHM3NCthWm9NcFFQK3ZtTDE3Q0ttU3pYazVvL2VLMjF4Z3hKZAp6TWJkenRmVHBpYmlYazBhYmZPcE4r" +
61
+ "MVZSKzNOWWErYlJPQUtOeUdhUmVFR3N5VzJiamNqTng1MTVWcXpqM1NWCnhoTVNwNXZmRjlFNEExakU5OU0vbDlqUURNNlJ6a1Qw" +
62
+ "bE1jY0dBZDV0VVNkTnZEbHJxdFFhUWlLdi9ac1hQZ1gKTFRXZk9wdmFMTkVnd2RNZ1pNdWhqcGt3dkFaeW9ZZmVGOWt5eWRqRGg3" +
63
+ "YnZyWC8vY3ovVm9wQVVseFV0UXRNQwpBd0VBQWFOak1HRXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1D" +
64
+ "QVFZd0hRWURWUjBPCkJCWUVGTlZKZ1FhZDFzTlROMGp4Vmt3YkovWE0xYW4xTUI4R0ExVWRJd1FZTUJhQUZJODBRbWpjWjA2UHhD" +
65
+ "S2UKeFh4SjVhdmRSTDRlTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBdlM4dHlmYWdhR3NGZjlZbmNBMmtvL05hNQo5QlZGKzhU" +
66
+ "bFVvKzMyb3pud0lWcFMxQWhTZ0xQNnJOVmVrWE5GS2J1UDVodHVkTFExN1pSQkpJL1VNVnlZRURxCklON3h2N1pqK3pKd0Y2VzZo" +
67
+ "YVlyamIyVms4aWd3MVh2TlVMWmZ2Vk5OS0lrdkpVaVZxRXNsV3JDK2s3NGNyay8KV3Y4Q2hWZit6cXZmSU42TFYzZXNhR1JMMDJK" +
68
+ "M0FwclFHYjdERGhSMUVlZlFNU2NEa05wR0pNVW12Q21ma25ybAppSzhxZ3ZRTjFTV083SlJmNmZOS0hzTjFaUXZ5UDBwZ0xXeHBU" +
69
+ "M1YwLzAvV3R0cVgzY01HdUpGK2pWVXptL05oCnhZaEZld0c4dmMzS3pUam53UUFwTUE2Q1c1NTRGT0pXRnlPRDJqbjV5SkxUM1Z1" +
70
+ "ZSthWURRUnA0YktNeAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlDcmpD" +
71
+ "Q0FaYWdBd0lCQWdJVU9xK2xOSWFDMnhzc3drRnFqNUpQeVZCbDQ1Z3dEUVlKS29aSWh2Y05BUUVMCkJRQXdRakVMTUFrR0ExVUVC" +
72
+ "aE1DUTA0eElqQWdCZ05WQkFvTUdVSkNUQ0JVWldOb2JtOXNiMmRwWlhNZ1EyOHUKTENCTWRHUXhEekFOQmdOVkJBTU1Ca0pDVENC" +
73
+ "RFFUQWVGdzB5TlRBMk1UY3dNakF4TkRkYUZ3MHpOVEEyTVRVdwpNakF4TkRkYU1FWXhDekFKQmdOVkJBWVRBa05PTVNFd0h3WURW" +
74
+ "UVFLREJoQ1Frd2dWR1ZqYUc1dmJHOW5hV1Z6CklFTnZMaUJNZEdReEZEQVNCZ05WQkFNTUMwSkNUQ0JEUVRJZ1JVTkRNRmt3RXdZ" +
75
+ "SEtvWkl6ajBDQVFZSUtvWkkKemowREFRY0RRZ0FFcEtURjd3UlN0eTREWHBHSnpnQ1B3Umg4Z2hMbHhVQzNxSmJ5RWdMcVR2Smdi" +
76
+ "aXdZQVBQSApLN2tWYlZtZXJrcWhIT1Q0UWVXUmxURzNkT1FHTEEyVnBhTmpNR0V3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFPCkJn" +
77
+ "TlZIUThCQWY4RUJBTUNBUVl3SFFZRFZSME9CQllFRkt1UnBzallSRU95SUtIN0h3T0U2amhHQmQ2Tk1COEcKQTFVZEl3UVlNQmFB" +
78
+ "Rkk4MFFtamNaMDZQeENLZXhYeEo1YXZkUkw0ZU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQgpBUUNnNlBqVVNTWlYrNGJ2ZWpjVk12" +
79
+ "Z1htS3pmRDk1b3NXbjBjdG5vTUJ4UERhK20rR2crQmNMVDJJbEZBZTNFCktZTXZ1NFQyOTVXUWM5MnJqS1lxVzZjaXJGcHBuZzl1" +
80
+ "RUZXMm1aTGlteGFTbXV0c1RmdEUzc2JNVk1KL1NMWU4KUFY3VEZ2Nm1jQlNJRldYd21CT0lwYmg0QlVjVmZPTlR2ZFNmSXFmeUFW" +
81
+ "eHNxNHh6YzJuYzZoUEJwQW0yMUF5agpUb0MxZXYvVGJESjhWbGxGWmlFVm1XV2xJUDNhTnpBbThTMm1PcHhQQjJXbmFuYVpIU3J2" +
82
+ "WExGaHN0eXp3cmpECnlPMS9pc09aN3d0cjdyY3VUSmRFdnZ2Q2ltT1psa2ZSaGFEb1Rldzl0UTBFMkZWcHp6U2ludzAycW1RMXhJ" +
83
+ "RTkKNS9INVp6SlNQa3BlQUhXRVBuS2t4ZzB2Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FU" +
84
+ "RS0tLS0tCk1JSURaVENDQWsyZ0F3SUJBZ0lVVjFGY2t3WEVseWVrMW9uRm5ROWtMN0JrNE44d0RRWUpLb1pJaHZjTkFRRUwKQlFB" +
85
+ "d1FqRUxNQWtHQTFVRUJoTUNRMDR4SWpBZ0JnTlZCQW9NR1VKQ1RDQlVaV05vYm05c2IyZHBaWE1nUTI4dQpMQ0JNZEdReER6QU5C" +
86
+ "Z05WQkFNTUJrSkNUQ0JEUVRBZUZ3MHlNakEwTURRd016UXlNVEZhRncwek1qQTBNREV3Ck16UXlNVEZhTUVJeEN6QUpCZ05WQkFZ" +
87
+ "VEFrTk9NU0l3SUFZRFZRUUtEQmxDUWt3Z1ZHVmphRzV2Ykc5bmFXVnoKSUVOdkxpd2dUSFJrTVE4d0RRWURWUVFEREFaQ1Frd2dR" +
88
+ "MEV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQgpEd0F3Z2dFS0FvSUJBUURMM3BuRGR4R09rNVo2dnVnaVQ0ZHBNMGp1KzNY" +
89
+ "YXR4ejA5VVk3bWJqNHRrSWRieTRICm9lRWRpWVNaamM1TEpuZ0p1Q0h3dEViQkp0MUJyaVJkU1ZyRjZNOUQyVWFCRHlhbUVvMGR4" +
90
+ "d1NhVnhaaURWV0MKZWVDUGRFTHBGWmRFaFNOVGFUNE83emd2Y25Gc2ZITWEvMHZNQWt2RTdpMHFwM21qRXpZTGZ6NjBheGNEb0pM" +
91
+ "awpwN242eEtYSStjSmJBNElsVG9GanBTbGRQbUMreW5PbzdZQU9zWHQ3QVlLWTZHbHowQndVVnpTSnhVKy8rVkZ5Ci9Rcm1ZR053" +
92
+ "bHJRdGRSRUhlUmkwU05LMzJ4MStiT25kZkpQMHNvanVJckRqS3NkQ0x5ZTVDU1pJdnFuYm93d1cKMWpSd1pnVEJSMjlacDJuekNv" +
93
+ "eEpZY1U5VFNRcC80S1p1V05WQWdNQkFBR2pVekJSTUIwR0ExVWREZ1FXQkJTUApORUpvM0dkT2o4UWluc1Y4U2VXcjNVUytIakFm" +
94
+ "QmdOVkhTTUVHREFXZ0JTUE5FSm8zR2RPajhRaW5zVjhTZVdyCjNVUytIakFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJ" +
95
+ "YjNEUUVCQ3dVQUE0SUJBUUFCbEJJVDVaZUcKZmdjSzFMT2gxQ045c1R6eE1DTGJ0VFBGRjFOR0dBMTNtQXB1NmoxaDVZRUxiU0tj" +
96
+ "VXFmWHpNblZlQWIwNkh0dQozQ29Db2Urd2o3TE9OVEZPKyt2Qm0yL2lmNkp0L0RVdzFDQUVjTnlxZWg2RVMwTlg4TEpSVlNlMHFk" +
97
+ "VHhQSnVBCkJkT29vOTZpWDg5clJQb3hlZWQxY3BxNWhad2Jla2EzK0NKR1Y3Nml0V3AzNVVwNXJtbVVxcmx5UU9yL1dheDYKaXRv" +
98
+ "c0l6RzBNZmhnVXpVNTFBMlAvaFNuRDNORE1Ydit3VVkvQXZxZ0lMN3U3ZmJES25rdTFHekVLSWtmSDhobQpSczZkOFNDVTg5eHly" +
99
+ "d3pRMFBSODUzaXJIYXMzV3JIVnFhYjNQK3FOd1IwWWlyTDBRazdYdC9xM08xZ3JpTmcyCkJsYmpnM29icEhvOQotLS0tLUVORCBD" +
100
+ "RVJUSUZJQ0FURS0tLS0t",
101
+ },
102
+ "slicer_base64.cer": {
103
+ sha256: "f504dc1c37bc9741b7a95198914e67f3f6f1d917b1e45eaa14b9e84eb7b51ec8",
104
+ base64: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUY3ekNDQk5lZ0F3SUJBZ0lRQm5XSDBPTmY1dE1XNlZyNXhtWXhEREFOQmdr" +
105
+ "cWhraUc5dzBCQVFzRkFEQloKTVFzd0NRWURWUVFHRXdKVlV6RVZNQk1HQTFVRUNoTU1SR2xuYVVObGNuUWdTVzVqTVRNd01RWURW" +
106
+ "UVFERXlwUwpZWEJwWkZOVFRDQlVURk1nUkZZZ1VsTkJJRTFwZUdWa0lGTklRVEkxTmlBeU1ESXdJRU5CTFRFd0hoY05NakV3Ck56" +
107
+ "RXpNREF3TURBd1doY05Nakl3TnpFek1qTTFPVFU1V2pBWk1SY3dGUVlEVlFRRERBNHFMbUpoYldKMWJHRmkKTG1OdmJUQ0NBU0l3" +
108
+ "RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUt4c3pMZGpBS21TMkpscQpaNTI4WkVsM0VxWkd2MWp1UnZ2TTR4" +
109
+ "cCtvcGVNWENzMWpLc2IxV2ZpZUZBL0tqWEJ6NG51ZVpzRkxhYnB5aXBBCjlvUFBkOG9HajhzWjRoOENJYk1iejJHeG1VVktUb1Ra" +
110
+ "OWYzd1lwMHNXRFVQVHhoWEd3L0p6cXk2bWN3cWhvQ08KUWJWck5hUUNxUXVOcElLTWx2UFg1YUsxQTBRL0FzZE4ybXBWdFNHMENw" +
111
+ "cGNSZjVaRTQyelZGUGcxaWVReGdpSAp3NkxXTGlaM0NHcHRueVAxc0JsSUx6VTQ4cXJoMUQwN1ptdUVITDA1cEo3ZFFBc2hzK0Jr" +
112
+ "bFJ6eXpUZ2lVKzFlCitRZVBGYXExRG9zekZlMndMS3Z1UjhPL1JaOGZGS0pzTnlodE80b21jMklTR3ZSb3lrN3FLU3JGUU9jNG1l" +
113
+ "a1kKMDJzSUZHVUNBd0VBQWFPQ0F2RXdnZ0x0TUI4R0ExVWRJd1FZTUJhQUZLU041YjU4ZWVSd0kyMHVLVFN0STFqYwo5VEYvTUIw" +
114
+ "R0ExVWREZ1FXQkJUUDIrTDR0YjJhOGZsMXBPWSt6RmxBNEdOamtqQW5CZ05WSFJFRUlEQWVnZzRxCkxtSmhiV0oxYkdGaUxtTnZi" +
115
+ "WUlNWW1GdFluVnNZV0l1WTI5dE1BNEdBMVVkRHdFQi93UUVBd0lGb0RBZEJnTlYKSFNVRUZqQVVCZ2dyQmdFRkJRY0RBUVlJS3dZ" +
116
+ "QkJRVUhBd0l3UGdZRFZSMGdCRGN3TlRBekJnWm5nUXdCQWdFdwpLVEFuQmdnckJnRUZCUWNDQVJZYmFIUjBjRG92TDNkM2R5NWth" +
117
+ "V2RwWTJWeWRDNWpiMjB2UTFCVE1JR0ZCZ2dyCkJnRUZCUWNCQVFSNU1IY3dKQVlJS3dZQkJRVUhNQUdHR0doMGRIQTZMeTl2WTNO" +
118
+ "d0xtUnBaMmxqWlhKMExtTnYKYlRCUEJnZ3JCZ0VGQlFjd0FvWkRhSFIwY0RvdkwyTmhZMlZ5ZEhNdVpHbG5hV05sY25RdVkyOXRM" +
119
+ "MUpoY0dsawpVMU5NVkV4VFJGWlNVMEZOYVhobFpGTklRVEkxTmpJd01qQkRRUzB4TG1OeWREQUpCZ05WSFJNRUFqQUFNSUlCCmZn" +
120
+ "WUtLd1lCQkFIV2VRSUVBZ1NDQVc0RWdnRnFBV2dBZGdBcGViN3duams1SWZCV2M1OWpwWGZsdmxkOW5HQUsKK1BsTlhTWmNKVjNI" +
121
+ "aEFBQUFYcWVwZEJsQUFBRUF3QkhNRVVDSUcrQVVPdnNqV1VIMDdBK0FaTkNyVVJackxYLwo2MldSVXcrM2FTWlhxSG8vQWlFQW9r" +
122
+ "c1dpMm13bTZrNEZDZW45VThNeTNJbmVEU1lxbnY3K2JoODhLOExrdHdBCmRnQlJvN0QxL1FGNW5GWnR1RGQ0and5a2Vzd2JKOHYz" +
123
+ "bm9oQ21nMysxSXNGNVFBQUFYcWVwZERWQUFBRUF3QkgKTUVVQ0lRRDZLYk5YRWZaT1pnN1MvdURIRlRWY00xczByemw0ZWtQNFhW" +
124
+ "cHlRSHQ5MkFJZ0pVbndQMzlDY1NjTwppZ1dZSm1yeGpCQTMxR25XajE4THVLeURWMWpYSmNRQWRnQkJ5TXF4M3lKR1NoREdvVG9K" +
125
+ "UW9kZVRqR0xHd1ByCjYwdkhhUENRWXBZRzlnQUFBWHFlcGRCRUFBQUVBd0JITUVVQ0lRQ3Y3dWZlY21CVmhwbVF1aUpFV1R6dFkw" +
126
+ "ck4KbnBnWnZnQlVNL3ppVlo0d1VBSWdRQXVHbUM5KzZzbkQwUklud3R0Ly9WdmRZcERYT2RpVVN3ZG4zdXc0NXNjdwpEUVlKS29a" +
127
+ "SWh2Y05BUUVMQlFBRGdnRUJBTEJSMFhuSFNBRm0zODN3TDM0VGcvZnFjdWtwWUFFTGZ4MG9zczVpCnZGUWhERUZndVovUXhjaDcw" +
128
+ "UmZBTnYyK0hETFVKNUJIWmZFRnpPQVZFc2xlalBEL2xPZFZKZVozWGE3azhXZXAKZDJudi9hVmR1UmlUOUc4Q0JFa3MrVWxXKzlW" +
129
+ "NmwvWVB5YThJMWh4bWRLWm5YWlhrSm1kOUJia1A4SndieHNzcApxUEYzRzlkc1Y1RTBab0RTWkZuOXgzSkpIM1QzNUZkSXJ4b0sv" +
130
+ "K3MvK3ZvbzdPbkRPWkdqekZrRjUrWW00ajZ1Cks2dW9vMVRXSmFDOW1NQ2FiNElneXJ5alNiVlNvNDlROUdsdXRvaGR2aEZrNEhD" +
131
+ "SWRmejJ3M0JYM29XdFJ3LzIKdUtLTHlEamRYeWYzVFlqWWFTYmJhblNmRDEycGQwWUsyejAvK0daUHZoMUtiN1E9Ci0tLS0tRU5E" +
132
+ "IENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURyekNDQXBlZ0F3SUJBZ0lRQ0R2Z1ZwQkNS" +
133
+ "ckdoZFdySldaSEhTakFOQmdrcWhraUc5dzBCQVFVRkFEQmgKTVFzd0NRWURWUVFHRXdKVlV6RVZNQk1HQTFVRUNoTU1SR2xuYVVO" +
134
+ "bGNuUWdTVzVqTVJrd0Z3WURWUVFMRXhCMwpkM2N1WkdsbmFXTmxjblF1WTI5dE1TQXdIZ1lEVlFRREV4ZEVhV2RwUTJWeWRDQkhi" +
135
+ "RzlpWVd3Z1VtOXZkQ0JEClFUQWVGdzB3TmpFeE1UQXdNREF3TURCYUZ3MHpNVEV4TVRBd01EQXdNREJhTUdFeEN6QUpCZ05WQkFZ" +
136
+ "VEFsVlQKTVJVd0V3WURWUVFLRXd4RWFXZHBRMlZ5ZENCSmJtTXhHVEFYQmdOVkJBc1RFSGQzZHk1a2FXZHBZMlZ5ZEM1agpiMjB4" +
137
+ "SURBZUJnTlZCQU1URjBScFoybERaWEowSUVkc2IySmhiQ0JTYjI5MElFTkJNSUlCSWpBTkJna3Foa2lHCjl3MEJBUUVGQUFPQ0FR" +
138
+ "OEFNSUlCQ2dLQ0FRRUE0anZoRVhMZXFLVFRvMWVxVUtLUEMzZVF5YUtsN2hMT2xsc0IKQ1NETUFaT25UakMzVS9kRHhHa0FWNTNp" +
139
+ "alNMZGh3WkFBSUVKenM0Ymc3L2Z6VHR4UnVMV1pzY0ZzM1luRm85NwpuaDZWZmU2M1NLTUkydGF2ZWd3NUJtVi9TbDBmdkJmNHE3" +
140
+ "N3VLTmQwZjNwNG1WbUZhRzVjSXpKTHYwN0E2RnB0CjQzQy9keEMvL0FIMmhkbW9SQkJZTXFsMUdOWFJvcjVINGlkcTlKb3orRWtJ" +
141
+ "WUl2VVg3UTZoTCtocWtwTWZUN1AKVDE5c2RsNmdTemVSbnR3aTVtM09GQnFPYXN2K3piTVVaQmZIV3ltZU1yL3k3dnJUQzBMVXE3" +
142
+ "ZEJNdG9NMU8vNApnZFc3alZnL3RSdm9TU2lpY05veEJOMzNzaGJ5VEFwT0I2anRTajFldFgramtNT3ZKd0lEQVFBQm8yTXdZVEFP" +
143
+ "CkJnTlZIUThCQWY4RUJBTUNBWVl3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVQTk1UU5WYlIKVEx0bThLUGlH" +
144
+ "eHZEbDdJOTBWVXdId1lEVlIwakJCZ3dGb0FVQTk1UU5WYlJUTHRtOEtQaUd4dkRsN0k5MFZVdwpEUVlKS29aSWh2Y05BUUVGQlFB" +
145
+ "RGdnRUJBTXVjTjZwSUV4SUsrdDFFbkU5U3NQVGZyZ1QxZVhrSW95UVkvRXNyCmhNQXR1ZFhIL3ZUQkgxakx1RzJjZW5Ubm1DbXJF" +
146
+ "YlhqY0tDaHpVeUltWk9Na1hEaXF3OGN2cE9wLzJQVjVBZGcKMDZPL25Wc0o4ZFdPNDFQMGptUDZQNmZidEdiZlltYlcwVzVCamZJ" +
147
+ "dHRlcDNTcCtkV09JcldjQkFJKzB0S0lKRgpQbmxVa2lhWTRJQklxRGZ2OE5aNVlCYmVyT2dPelc2c1JCYzRMMG5hNFVVK0tyazJV" +
148
+ "ODg2VUFiM0x1akVWMGxzCllTRVkxUVN0ZUR3c09vQnJwK3V2RlJUcDJJbkJ1VGhzNHBGc2l2OWt1WGNsVnpEQUd5U2o0ZHpwMzBk" +
149
+ "OHRiUWsKQ0FVdzdDMjlDNzlGdjFDNXFmUHJtQUVTcmNpSXhwZzBYNDBLUE1icDFaV1ZiZDQ9Ci0tLS0tRU5EIENFUlRJRklDQVRF" +
150
+ "LS0tLS0K",
151
+ },
152
+ });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The printer's answer to Pause, Resume or Stop, shared by every route (FULU's local network and Bambu Cloud,
3
+ * and the direct local network a printer in LAN Only + Developer Mode takes).
4
+ */
5
+ export type ControlReply = {
6
+ accepted: boolean;
7
+ errCode: number;
8
+ };
9
+ /**
10
+ * The printer's answer to a control it was sent: it echoes command and sequence_id, with
11
+ * err_code set when it refuses. A P1S on authorization firmware signs it: {"header":{...},"print":{...}}.
12
+ */
13
+ export declare function controlReply(msg: unknown, command: string, sequenceId: string): ControlReply | undefined;
14
+ /**
15
+ * The printer's command verification refused the envelope. These codes do not
16
+ * establish that third-party controls are unsupported: P1S 01.10 also returns
17
+ * 0x0502400A for a valid signature with a sequence ID outside the Studio window.
18
+ */
19
+ export declare function isAuthorizationRefusal(errCode: number): boolean;
20
+ /** What to tell a person about a refused control. direct: the control went straight to the printer (Developer Mode connection). */
21
+ export declare function refusedControlMessage(command: string, route: string, errCode: number, direct?: boolean): string;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The printer's answer to Pause, Resume or Stop, shared by every route (FULU's local network and Bambu Cloud,
3
+ * and the direct local network a printer in LAN Only + Developer Mode takes).
4
+ */
5
+ /**
6
+ * The printer's answer to a control it was sent: it echoes command and sequence_id, with
7
+ * err_code set when it refuses. A P1S on authorization firmware signs it: {"header":{...},"print":{...}}.
8
+ */
9
+ export function controlReply(msg, command, sequenceId) {
10
+ if (typeof msg !== "string" || !msg.includes(sequenceId))
11
+ return undefined;
12
+ let print;
13
+ try {
14
+ print = JSON.parse(msg)?.print;
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ if (print?.command !== command || String(print.sequence_id) !== sequenceId)
20
+ return undefined;
21
+ const errCode = Number(print.err_code ?? 0) || 0;
22
+ const failed = typeof print.result === "string" && print.result.toLowerCase() !== "success";
23
+ return { accepted: errCode === 0 && !failed, errCode };
24
+ }
25
+ /**
26
+ * The printer's command verification refused the envelope. These codes do not
27
+ * establish that third-party controls are unsupported: P1S 01.10 also returns
28
+ * 0x0502400A for a valid signature with a sequence ID outside the Studio window.
29
+ */
30
+ export function isAuthorizationRefusal(errCode) {
31
+ const code = errCode >>> 0;
32
+ return code >= 0x05024007 && code <= 0x0502400B;
33
+ }
34
+ /** What to tell a person about a refused control. direct: the control went straight to the printer (Developer Mode connection). */
35
+ export function refusedControlMessage(command, route, errCode, direct = false) {
36
+ const word = `${command[0].toUpperCase()}${command.slice(1)}`;
37
+ const code = (errCode >>> 0).toString(16).toUpperCase().padStart(8, "0");
38
+ if (!isAuthorizationRefusal(errCode))
39
+ return `The printer refused ${word} over ${route} (error ${code}). Nothing changed on the printer.`;
40
+ const message = `The printer refused ${word} (error ${code}): its command verification failed. `;
41
+ return message + (direct
42
+ ? "Check LAN Only and Developer Mode for the direct connection, or choose an authenticated Local network or Bambu Cloud connection."
43
+ : "Check the authenticated connection and signing setup for this printer.");
44
+ }
@@ -0,0 +1,16 @@
1
+ import { type AxiosInstance } from "axios";
2
+ import type { OpenCameraSource } from "./camera-stream.js";
3
+ type Handlers = Parameters<OpenCameraSource>[0];
4
+ export type CameraUrl = {
5
+ url: string;
6
+ headers: Record<string, string>;
7
+ };
8
+ /** Same bounded JPEG framing as the Bambu stream; native code decodes pixels. */
9
+ export declare function mjpegParser(onFrame: (bytes: Buffer) => void, onError: (error: Error) => void): (chunk: Buffer) => void;
10
+ /** One persistent MJPEG response, or serialized snapshot requests on one keep-alive socket. */
11
+ export declare function openHttpCameraSource(http: AxiosInstance, plan: {
12
+ urls: CameraUrl[];
13
+ snapshots?: CameraUrl[];
14
+ mode: "http-mjpeg" | "http-snapshot";
15
+ }, fps: number, handlers: Handlers): ReturnType<OpenCameraSource>;
16
+ export {};
@@ -0,0 +1,138 @@
1
+ import { Agent as HttpAgent } from "node:http";
2
+ import { Agent as HttpsAgent } from "node:https";
3
+ import { isAxiosError } from "axios";
4
+ const MAX_BYTES = 5000000;
5
+ /** Same bounded JPEG framing as the Bambu stream; native code decodes pixels. */
6
+ export function mjpegParser(onFrame, onError) {
7
+ let buffer = Buffer.alloc(0), failed = false;
8
+ return chunk => {
9
+ if (failed)
10
+ return;
11
+ buffer = Buffer.concat([buffer, chunk]);
12
+ for (;;) {
13
+ const start = buffer.indexOf(Buffer.from([0xff, 0xd8]));
14
+ if (start < 0) {
15
+ buffer = buffer.subarray(Math.max(0, buffer.length - 1));
16
+ return;
17
+ }
18
+ if (start)
19
+ buffer = buffer.subarray(start);
20
+ const end = buffer.indexOf(Buffer.from([0xff, 0xd9]), 2);
21
+ if ((end >= 0 ? end + 2 : buffer.length) > MAX_BYTES) {
22
+ failed = true;
23
+ onError(new Error("Camera JPEG too large"));
24
+ return;
25
+ }
26
+ if (end < 0)
27
+ return;
28
+ onFrame(Buffer.from(buffer.subarray(0, end + 2)));
29
+ buffer = buffer.subarray(end + 2);
30
+ }
31
+ };
32
+ }
33
+ /** One persistent MJPEG response, or serialized snapshot requests on one keep-alive socket. */
34
+ export function openHttpCameraSource(http, plan, fps, handlers) {
35
+ const controller = new AbortController();
36
+ const httpAgent = new HttpAgent({ keepAlive: true, maxSockets: 1 });
37
+ const httpsAgent = new HttpsAgent({ keepAlive: true, maxSockets: 1 });
38
+ let closed = false, response, selected;
39
+ let mode = plan.mode;
40
+ let timer;
41
+ const fail = (error) => {
42
+ if (!closed)
43
+ handlers.onError(new Error(isAxiosError(error) && [401, 403].includes(error.response?.status ?? 0) ? "Camera authentication failed" : "Camera unavailable"));
44
+ };
45
+ const close = () => {
46
+ closed = true;
47
+ clearTimeout(timer);
48
+ controller.abort();
49
+ response?.destroy();
50
+ httpAgent.destroy();
51
+ httpsAgent.destroy();
52
+ };
53
+ const useSnapshot = () => { mode = "http-snapshot"; selected = undefined; };
54
+ const request = async () => {
55
+ for (const candidate of selected ? [selected] : mode === "http-snapshot" && plan.snapshots?.length ? plan.snapshots : plan.urls) {
56
+ try {
57
+ const result = await http.get(candidate.url, {
58
+ headers: candidate.headers, responseType: "stream", timeout: 8000,
59
+ maxRedirects: Object.keys(candidate.headers).length ? 0 : 3,
60
+ httpAgent, httpsAgent, signal: controller.signal,
61
+ });
62
+ response = result.data;
63
+ if (closed) {
64
+ response.destroy();
65
+ return undefined;
66
+ }
67
+ const contentType = String(result.headers["content-type"] ?? "");
68
+ if (mode === "http-mjpeg" && !/^multipart\/x-mixed-replace\b/i.test(contentType)) {
69
+ response.destroy();
70
+ if (plan.snapshots?.length) {
71
+ useSnapshot();
72
+ return request();
73
+ }
74
+ throw new Error("Camera did not return MJPEG");
75
+ }
76
+ selected = candidate;
77
+ return response;
78
+ }
79
+ catch (error) {
80
+ // Error responses are streams too; always release their socket before trying another URL.
81
+ if (isAxiosError(error))
82
+ error.response?.data?.destroy?.();
83
+ if (closed || (isAxiosError(error) && [401, 403].includes(error.response?.status ?? 0)))
84
+ throw error;
85
+ if (mode === "http-mjpeg" && plan.snapshots?.length && isAxiosError(error) && [404, 405, 415].includes(error.response?.status ?? 0)) {
86
+ useSnapshot();
87
+ return request();
88
+ }
89
+ }
90
+ }
91
+ throw new Error("No camera URL answered");
92
+ };
93
+ const run = async () => {
94
+ const began = Date.now(), body = await request();
95
+ if (!body || closed)
96
+ return;
97
+ if (mode === "http-mjpeg") {
98
+ const arm = () => { clearTimeout(timer); timer = setTimeout(() => fail(new Error("Camera timed out")), 15000); timer.unref(); };
99
+ arm();
100
+ const parse = mjpegParser(bytes => { arm(); if (!closed)
101
+ handlers.onFrame(bytes); }, fail);
102
+ for await (const chunk of body) {
103
+ if (closed)
104
+ return;
105
+ parse(Buffer.from(chunk));
106
+ }
107
+ if (!closed)
108
+ handlers.onEnd();
109
+ }
110
+ else {
111
+ let size = 0;
112
+ const chunks = [];
113
+ for await (const chunk of body) {
114
+ if (closed)
115
+ return;
116
+ const bytes = Buffer.from(chunk);
117
+ size += bytes.length;
118
+ if (size > MAX_BYTES) {
119
+ body.destroy();
120
+ throw new Error("Camera JPEG too large");
121
+ }
122
+ chunks.push(bytes);
123
+ }
124
+ if (closed)
125
+ return;
126
+ const jpeg = Buffer.concat(chunks);
127
+ if (jpeg.length < 3 || jpeg[0] !== 0xff || jpeg[1] !== 0xd8 || jpeg[2] !== 0xff)
128
+ throw new Error("Camera did not return JPEG");
129
+ handlers.onFrame(jpeg);
130
+ if (closed)
131
+ return;
132
+ timer = setTimeout(() => { void run().catch(fail); }, Math.max(0, 1000 / fps - (Date.now() - began)));
133
+ timer.unref();
134
+ }
135
+ };
136
+ void run().catch(fail);
137
+ return { close };
138
+ }
@@ -0,0 +1,66 @@
1
+ export declare function cameraNativeAvailable(): Promise<boolean>;
2
+ export type CameraTransport = "http";
3
+ export type CameraSource = {
4
+ close(): void;
5
+ };
6
+ export type OpenCameraSource = (handlers: {
7
+ onFrame(bytes: Buffer): void;
8
+ onError(error: Error): void;
9
+ onEnd(): void;
10
+ }) => CameraSource;
11
+ export type FrameOptions = {
12
+ sinceSeq?: number;
13
+ waitMs?: number;
14
+ maxWidth?: number;
15
+ maxBytes?: number;
16
+ changedOnly?: boolean;
17
+ minChange?: number;
18
+ signal?: AbortSignal;
19
+ };
20
+ export type StreamFrameResult = {
21
+ status: "frame" | "unchanged" | "stopped" | "unavailable";
22
+ seq: number;
23
+ capturedAt?: string;
24
+ width?: number;
25
+ height?: number;
26
+ format?: "image/jpeg";
27
+ base64?: string;
28
+ change?: number;
29
+ reason?: "auth" | "unreachable" | "no-camera";
30
+ };
31
+ export type StreamOptions = {
32
+ transport: CameraTransport;
33
+ fps: number;
34
+ idleTimeoutMs: number;
35
+ open: OpenCameraSource;
36
+ skipFirstFrame: boolean;
37
+ };
38
+ export declare function thumbnailChange(a: Uint8Array, b: Uint8Array): number;
39
+ export declare class CameraStream {
40
+ #private;
41
+ private options;
42
+ private nextSeq;
43
+ private now;
44
+ readonly startedAt: string;
45
+ readonly transport: CameraTransport;
46
+ readonly fps: number;
47
+ constructor(options: StreamOptions, nextSeq: () => number, now?: () => number);
48
+ get stopped(): boolean;
49
+ start(): void;
50
+ touch(idleTimeoutMs?: number): void;
51
+ stop(): void;
52
+ frame(options?: FrameOptions): Promise<StreamFrameResult>;
53
+ latest(waitMs: number): Promise<Buffer | undefined>;
54
+ }
55
+ export declare class CameraStreams {
56
+ #private;
57
+ private now;
58
+ constructor(now?: () => number);
59
+ get(key: string): CameraStream | undefined;
60
+ start(key: string, options: StreamOptions): {
61
+ stream: CameraStream;
62
+ status: "started" | "running";
63
+ };
64
+ stop(key: string): void;
65
+ stopAll(): void;
66
+ }