@fdm-monster/server 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/.yarn/releases/{yarn-4.13.0.cjs → yarn-4.14.1.cjs} +288 -288
  3. package/.yarnrc.yml +5 -1
  4. package/RELEASE_NOTES.MD +14 -0
  5. package/dist/_virtual/{_@oxc-project_runtime@0.127.0 → _@oxc-project_runtime@0.129.0}/helpers/decorate.js +1 -1
  6. package/dist/_virtual/{_@oxc-project_runtime@0.127.0 → _@oxc-project_runtime@0.129.0}/helpers/decorateMetadata.js +1 -1
  7. package/dist/consoles/typeorm-create.js.map +1 -1
  8. package/dist/consoles/typeorm-generate.js.map +1 -1
  9. package/dist/consoles/typeorm-migrate.js.map +1 -1
  10. package/dist/constants/authorization.constants.js.map +1 -1
  11. package/dist/container.js.map +1 -1
  12. package/dist/controllers/api-key.controller.js +2 -2
  13. package/dist/controllers/api-key.controller.js.map +1 -1
  14. package/dist/controllers/auth.controller.js +5 -3
  15. package/dist/controllers/auth.controller.js.map +1 -1
  16. package/dist/controllers/batch-call.controller.js +2 -2
  17. package/dist/controllers/batch-call.controller.js.map +1 -1
  18. package/dist/controllers/camera-stream.controller.js +2 -2
  19. package/dist/controllers/camera-stream.controller.js.map +1 -1
  20. package/dist/controllers/file-storage.controller.js +2 -2
  21. package/dist/controllers/file-storage.controller.js.map +1 -1
  22. package/dist/controllers/first-time-setup.controller.js +2 -2
  23. package/dist/controllers/first-time-setup.controller.js.map +1 -1
  24. package/dist/controllers/floor.controller.js +2 -2
  25. package/dist/controllers/floor.controller.js.map +1 -1
  26. package/dist/controllers/metrics.controller.js +2 -2
  27. package/dist/controllers/metrics.controller.js.map +1 -1
  28. package/dist/controllers/print-job.controller.js +2 -2
  29. package/dist/controllers/print-job.controller.js.map +1 -1
  30. package/dist/controllers/print-queue.controller.js +2 -2
  31. package/dist/controllers/print-queue.controller.js.map +1 -1
  32. package/dist/controllers/printer-files.controller.js +2 -2
  33. package/dist/controllers/printer-files.controller.js.map +1 -1
  34. package/dist/controllers/printer-maintenance-log.controller.js +2 -2
  35. package/dist/controllers/printer-maintenance-log.controller.js.map +1 -1
  36. package/dist/controllers/printer-settings.controller.js +2 -2
  37. package/dist/controllers/printer-settings.controller.js.map +1 -1
  38. package/dist/controllers/printer-tag.controller.js +2 -2
  39. package/dist/controllers/printer-tag.controller.js.map +1 -1
  40. package/dist/controllers/printer.controller.js +2 -2
  41. package/dist/controllers/printer.controller.js.map +1 -1
  42. package/dist/controllers/server-private.controller.js +2 -2
  43. package/dist/controllers/server-private.controller.js.map +1 -1
  44. package/dist/controllers/server-public.controller.js +2 -2
  45. package/dist/controllers/server-public.controller.js.map +1 -1
  46. package/dist/controllers/settings.controller.js +2 -2
  47. package/dist/controllers/settings.controller.js.map +1 -1
  48. package/dist/controllers/slicer-compat.controller.js +2 -2
  49. package/dist/controllers/slicer-compat.controller.js.map +1 -1
  50. package/dist/controllers/user.controller.js +2 -2
  51. package/dist/controllers/user.controller.js.map +1 -1
  52. package/dist/entities/api-key.entity.js +2 -2
  53. package/dist/entities/camera-stream.entity.js +2 -2
  54. package/dist/entities/floor-position.entity.js +2 -2
  55. package/dist/entities/floor.entity.js +2 -2
  56. package/dist/entities/print-job.entity.js +2 -2
  57. package/dist/entities/printer-maintenance-log.entity.js +2 -2
  58. package/dist/entities/printer-tag.entity.js +2 -2
  59. package/dist/entities/printer.entity.js +2 -2
  60. package/dist/entities/refresh-token.entity.js +2 -2
  61. package/dist/entities/role.entity.js +2 -2
  62. package/dist/entities/settings.entity.js +2 -2
  63. package/dist/entities/tag.entity.js +2 -2
  64. package/dist/entities/user-role.entity.js +2 -2
  65. package/dist/entities/user.entity.js +2 -2
  66. package/dist/exceptions/failed-dependency.exception.js.map +1 -1
  67. package/dist/exceptions/job.exceptions.js.map +1 -1
  68. package/dist/exceptions/runtime.exceptions.js.map +1 -1
  69. package/dist/handlers/event-emitter.js.map +1 -1
  70. package/dist/handlers/logger-factory.js.map +1 -1
  71. package/dist/handlers/logger.js.map +1 -1
  72. package/dist/handlers/logging/file-logging.transport.js +1 -2
  73. package/dist/handlers/logging/file-logging.transport.js.map +1 -1
  74. package/dist/handlers/logging/loki-logging.transport.js.map +1 -1
  75. package/dist/handlers/logging/static.logger.js.map +1 -1
  76. package/dist/handlers/validators.js.map +1 -1
  77. package/dist/index.js.map +1 -1
  78. package/dist/middleware/api-key.strategy.js.map +1 -1
  79. package/dist/middleware/authenticate.js.map +1 -1
  80. package/dist/middleware/database.js.map +1 -1
  81. package/dist/middleware/demo.middleware.js.map +1 -1
  82. package/dist/middleware/exception.filter.js.map +1 -1
  83. package/dist/middleware/global.middleware.js.map +1 -1
  84. package/dist/middleware/param-converter.middleware.js.map +1 -1
  85. package/dist/middleware/passport.js.map +1 -1
  86. package/dist/middleware/printer-resolver.js.map +1 -1
  87. package/dist/middleware/printer.js.map +1 -1
  88. package/dist/middleware/slicer-api-key.middleware.js.map +1 -1
  89. package/dist/middleware/socketio.middleware.js.map +1 -1
  90. package/dist/migrations/1706829146617-InitSqlite.js.map +1 -1
  91. package/dist/migrations/1707494762198-PrinterGroup.js.map +1 -1
  92. package/dist/migrations/1708465930665-ChangePrintCompletionDeletePrinterCascade.js.map +1 -1
  93. package/dist/migrations/1713300747465-ChangeRoleNameUnique.js.map +1 -1
  94. package/dist/migrations/1713897879622-AddPrinterType.js.map +1 -1
  95. package/dist/migrations/1720338804844-RemovePrinterFile.js.map +1 -1
  96. package/dist/migrations/1745141688926-AddPrinterUsernamePassword.js.map +1 -1
  97. package/dist/migrations/1766576698569-DropPermissions.js.map +1 -1
  98. package/dist/migrations/1767278216516-ChangeCameraPrinterOnDeleteSetNull.js.map +1 -1
  99. package/dist/migrations/1767279607392-DropCustomGcode.js.map +1 -1
  100. package/dist/migrations/1767291804417-DropPrintCompletions.js.map +1 -1
  101. package/dist/migrations/1767352862576-DropSettingsFileClean.js.map +1 -1
  102. package/dist/migrations/1767355639023-ChangeFloorLevelToOrder.js.map +1 -1
  103. package/dist/migrations/1767370191762-ChangeFloorNonUniqueOrder.js.map +1 -1
  104. package/dist/migrations/1767432108916-RenameGroupToTag.js.map +1 -1
  105. package/dist/migrations/1767451444137-AddPrintJob.js.map +1 -1
  106. package/dist/migrations/1767909428129-AddPrinterMaintenanceLog.js.map +1 -1
  107. package/dist/migrations/1778446203015-AddApiKey.js.map +1 -1
  108. package/dist/plugins/controllers-plugin.js.map +1 -1
  109. package/dist/server.constants.js +2 -1
  110. package/dist/server.constants.js.map +1 -1
  111. package/dist/server.core.js.map +1 -1
  112. package/dist/server.env.js.map +1 -1
  113. package/dist/server.host.js.map +1 -1
  114. package/dist/services/authentication/auth.service.js.map +1 -1
  115. package/dist/services/authentication/jwt.service.js.map +1 -1
  116. package/dist/services/bambu/bambu-ftp.adapter.js.map +1 -1
  117. package/dist/services/bambu/bambu-mqtt.adapter.js.map +1 -1
  118. package/dist/services/bambu/bambu.client.js.map +1 -1
  119. package/dist/services/bambu.api.js.map +1 -1
  120. package/dist/services/core/batch-call.service.js.map +1 -1
  121. package/dist/services/core/client-bundle.service.js.map +1 -1
  122. package/dist/services/core/config.service.js +4 -0
  123. package/dist/services/core/config.service.js.map +1 -1
  124. package/dist/services/core/cradle.service.js.map +1 -1
  125. package/dist/services/core/github.service.js.map +1 -1
  126. package/dist/services/core/http-client.factory.js.map +1 -1
  127. package/dist/services/core/logs-manager.service.js.map +1 -1
  128. package/dist/services/core/monsterpi.service.js.map +1 -1
  129. package/dist/services/core/multer.service.js.map +1 -1
  130. package/dist/services/core/server-release.service.js.map +1 -1
  131. package/dist/services/core/yaml.service.js.map +1 -1
  132. package/dist/services/file-analysis.service.js.map +1 -1
  133. package/dist/services/file-storage.service.js.map +1 -1
  134. package/dist/services/moonraker/moonraker-websocket.adapter.js.map +1 -1
  135. package/dist/services/moonraker/moonraker.client.js.map +1 -1
  136. package/dist/services/moonraker.api.js.map +1 -1
  137. package/dist/services/octoprint/octoprint-api.routes.js.map +1 -1
  138. package/dist/services/octoprint/octoprint-websocket.adapter.js.map +1 -1
  139. package/dist/services/octoprint/octoprint.client.js.map +1 -1
  140. package/dist/services/octoprint/utils/api.utils.js.map +1 -1
  141. package/dist/services/octoprint/utils/file.utils.js.map +1 -1
  142. package/dist/services/octoprint/utils/octoprint-http-client.builder.js.map +1 -1
  143. package/dist/services/octoprint.api.js.map +1 -1
  144. package/dist/services/orm/api-key.service.js.map +1 -1
  145. package/dist/services/orm/base.service.js.map +1 -1
  146. package/dist/services/orm/camera-stream.service.js.map +1 -1
  147. package/dist/services/orm/floor-position.service.js.map +1 -1
  148. package/dist/services/orm/floor.service.js.map +1 -1
  149. package/dist/services/orm/permission.service.js.map +1 -1
  150. package/dist/services/orm/print-job.service.js.map +1 -1
  151. package/dist/services/orm/printer-maintenance-log.service.js.map +1 -1
  152. package/dist/services/orm/printer-tag.service.js.map +1 -1
  153. package/dist/services/orm/printer.service.js.map +1 -1
  154. package/dist/services/orm/refresh-token.service.js.map +1 -1
  155. package/dist/services/orm/role.service.js.map +1 -1
  156. package/dist/services/orm/settings.service.js.map +1 -1
  157. package/dist/services/orm/user-role.service.js.map +1 -1
  158. package/dist/services/orm/user.service.js.map +1 -1
  159. package/dist/services/print-file-downloader.service.js.map +1 -1
  160. package/dist/services/print-queue.service.js.map +1 -1
  161. package/dist/services/printer-api.factory.js.map +1 -1
  162. package/dist/services/printer-api.interface.js.map +1 -1
  163. package/dist/services/prusa-link/prusa-link-http-polling.adapter.js.map +1 -1
  164. package/dist/services/prusa-link/prusa-link.api.js.map +1 -1
  165. package/dist/services/prusa-link/utils/digest-auth.util.js +19 -12
  166. package/dist/services/prusa-link/utils/digest-auth.util.js.map +1 -1
  167. package/dist/services/prusa-link/utils/prusa-link-http-client.builder.js +45 -11
  168. package/dist/services/prusa-link/utils/prusa-link-http-client.builder.js.map +1 -1
  169. package/dist/services/socket.factory.js.map +1 -1
  170. package/dist/services/task-manager.service.js.map +1 -1
  171. package/dist/services/typeorm/typeorm.service.js.map +1 -1
  172. package/dist/services/validators/printer-service.validation.js.map +1 -1
  173. package/dist/shared/default-http-client.builder.js.map +1 -1
  174. package/dist/shared/load-controllers.js.map +1 -1
  175. package/dist/shared/runtime-settings.migration.js.map +1 -1
  176. package/dist/shared/websocket-rpc-extended.adapter.js.map +1 -1
  177. package/dist/shared/websocket.adapter.js.map +1 -1
  178. package/dist/state/file-upload-tracker.cache.js.map +1 -1
  179. package/dist/state/floor.store.js.map +1 -1
  180. package/dist/state/printer-events.cache.js.map +1 -1
  181. package/dist/state/printer-socket.store.js.map +1 -1
  182. package/dist/state/printer-thumbnail.cache.js.map +1 -1
  183. package/dist/state/printer.cache.js.map +1 -1
  184. package/dist/state/settings.store.js.map +1 -1
  185. package/dist/state/socket-io.gateway.js.map +1 -1
  186. package/dist/state/test-printer-socket.store.js.map +1 -1
  187. package/dist/tasks/boot.task.js.map +1 -1
  188. package/dist/tasks/client-bundle.task.js.map +1 -1
  189. package/dist/tasks/print-job-analysis.task.js.map +1 -1
  190. package/dist/tasks/printer-websocket-restore.task.js.map +1 -1
  191. package/dist/tasks/printer-websocket.task.js.map +1 -1
  192. package/dist/tasks/socketio.task.js.map +1 -1
  193. package/dist/tasks/software-update.task.js.map +1 -1
  194. package/dist/tasks.js.map +1 -1
  195. package/dist/utils/array.util.js.map +1 -1
  196. package/dist/utils/bgcode/bgcode-thumbnail.parser.js.map +1 -1
  197. package/dist/utils/bgcode/bgcode.utils.js.map +1 -1
  198. package/dist/utils/bgcode/heatshrink-decoder.js.map +1 -1
  199. package/dist/utils/bgcode/png-encoder.js.map +1 -1
  200. package/dist/utils/bgcode/qoi-decoder.js.map +1 -1
  201. package/dist/utils/cache/key-diff.cache.js.map +1 -1
  202. package/dist/utils/correlation-token.util.js.map +1 -1
  203. package/dist/utils/crypto.utils.js.map +1 -1
  204. package/dist/utils/env.utils.js.map +1 -1
  205. package/dist/utils/error.utils.js.map +1 -1
  206. package/dist/utils/fs.utils.js.map +1 -1
  207. package/dist/utils/gcode.utils.js.map +1 -1
  208. package/dist/utils/image-dimensions.js.map +1 -1
  209. package/dist/utils/job-stats.util.js.map +1 -1
  210. package/dist/utils/normalize-url.js.map +1 -1
  211. package/dist/utils/parsers/3mf.parser.js.map +1 -1
  212. package/dist/utils/parsers/bgcode.parser.js.map +1 -1
  213. package/dist/utils/parsers/gcode.parser.js.map +1 -1
  214. package/dist/utils/pretty-print.utils.js.map +1 -1
  215. package/dist/utils/semver.utils.js.map +1 -1
  216. package/dist/utils/swagger/decorators.js.map +1 -1
  217. package/dist/utils/swagger/generator.js.map +1 -1
  218. package/dist/utils/swagger/swagger.js.map +1 -1
  219. package/dist/utils/thumbnail.util.js.map +1 -1
  220. package/dist/utils/time.utils.js.map +1 -1
  221. package/dist/utils/url.utils.js.map +1 -1
  222. package/package.json +10 -7
  223. package/packages/consoles/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"printer.cache.js","names":[],"sources":["../../src/state/printer.cache.ts"],"sourcesContent":["import { KeyDiffCache } from \"@/utils/cache/key-diff.cache\";\nimport {\n BatchPrinterCreatedEvent,\n PrinterCreatedEvent,\n printerEvents,\n PrintersDeletedEvent,\n} from \"@/constants/event.constants\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport EventEmitter2 from \"eventemitter2\";\nimport type { IPrinterService } from \"@/services/interfaces/printer.service.interface\";\nimport type { PrinterDto } from \"@/services/interfaces/printer.dto\";\nimport { Printer } from \"@/entities\";\n\nexport class PrinterCache extends KeyDiffCache<PrinterDto> {\n constructor(\n private readonly printerService: IPrinterService,\n private readonly eventEmitter2: EventEmitter2,\n ) {\n super();\n\n this.eventEmitter2.on(printerEvents.batchPrinterCreated, this.handleBatchPrinterCreated.bind(this));\n this.eventEmitter2.on(printerEvents.printerCreated, this.handlePrinterCreatedOrUpdated.bind(this));\n this.eventEmitter2.on(printerEvents.printerUpdated, this.handlePrinterCreatedOrUpdated.bind(this));\n this.eventEmitter2.on(printerEvents.printersDeleted, this.handlePrintersDeleted.bind(this));\n }\n\n async loadCache(): Promise<PrinterDto[]> {\n const printerDocs = await this.printerService.list();\n const dtos = this.mapArray(printerDocs);\n const keyValues = dtos.map((p) => ({ key: p.id, value: p }));\n await this.setKeyValuesBatch(keyValues, true);\n return dtos;\n }\n\n async listCachedPrinters(includeDisabled = false): Promise<PrinterDto[]> {\n const printers = await this.getAllValues();\n if (!includeDisabled) {\n return printers.filter((p) => p.enabled);\n }\n return printers;\n }\n\n async getCachedPrinterOrThrowAsync(id: number): Promise<PrinterDto> {\n const printer = await this.getValue(id);\n if (!printer) {\n throw new NotFoundException(`Printer with provided id not found`);\n }\n return printer;\n }\n\n getCachedPrinterOrThrow(id: number) {\n const printer = this.keyValueStore.get(id);\n if (!printer) {\n throw new NotFoundException(`Printer with provided id not found`);\n }\n return printer;\n }\n\n async getNameAsync(id: number) {\n const printer = await this.getCachedPrinterOrThrowAsync(id);\n return printer.name;\n }\n\n async getLoginDtoAsync(id: number) {\n const printer = await this.getCachedPrinterOrThrowAsync(id);\n return {\n printerURL: printer.printerURL,\n apiKey: printer.apiKey,\n username: printer.username,\n password: printer.password,\n printerType: printer.printerType,\n };\n }\n\n getLoginDto(id: number) {\n const printer = this.getCachedPrinterOrThrow(id);\n return {\n printerURL: printer.printerURL,\n apiKey: printer.apiKey,\n printerType: printer.printerType,\n username: printer.username,\n password: printer.password,\n };\n }\n\n private async handleBatchPrinterCreated(event: BatchPrinterCreatedEvent) {\n const mappedPrinters = this.mapArray(event.printers);\n const keyValues = mappedPrinters.map((p) => ({ key: p.id, value: p }));\n await this.setKeyValuesBatch(keyValues, true);\n }\n\n private async handlePrinterCreatedOrUpdated(event: PrinterCreatedEvent) {\n const printerDto = this.map(event.printer);\n await this.setKeyValue(printerDto.id, printerDto, true);\n }\n\n private async handlePrintersDeleted(event: PrintersDeletedEvent) {\n await this.deleteKeysBatch(event.printerIds, true);\n }\n\n private mapArray(entities: Printer[]) {\n return entities.map((p) => {\n return this.map(p);\n });\n }\n\n private map(entity: Printer): PrinterDto {\n return this.printerService.toDto(entity);\n }\n}\n"],"mappings":";;;;AAaA,IAAa,eAAb,cAAkC,aAAyB;CACzD,YACE,gBACA,eACA;AACA,SAAO;AAHU,OAAA,iBAAA;AACA,OAAA,gBAAA;AAIjB,OAAK,cAAc,GAAG,cAAc,qBAAqB,KAAK,0BAA0B,KAAK,KAAK,CAAC;AACnG,OAAK,cAAc,GAAG,cAAc,gBAAgB,KAAK,8BAA8B,KAAK,KAAK,CAAC;AAClG,OAAK,cAAc,GAAG,cAAc,gBAAgB,KAAK,8BAA8B,KAAK,KAAK,CAAC;AAClG,OAAK,cAAc,GAAG,cAAc,iBAAiB,KAAK,sBAAsB,KAAK,KAAK,CAAC;;CAG7F,MAAM,YAAmC;EACvC,MAAM,cAAc,MAAM,KAAK,eAAe,MAAM;EACpD,MAAM,OAAO,KAAK,SAAS,YAAY;EACvC,MAAM,YAAY,KAAK,KAAK,OAAO;GAAE,KAAK,EAAE;GAAI,OAAO;GAAG,EAAE;AAC5D,QAAM,KAAK,kBAAkB,WAAW,KAAK;AAC7C,SAAO;;CAGT,MAAM,mBAAmB,kBAAkB,OAA8B;EACvE,MAAM,WAAW,MAAM,KAAK,cAAc;AAC1C,MAAI,CAAC,gBACH,QAAO,SAAS,QAAQ,MAAM,EAAE,QAAQ;AAE1C,SAAO;;CAGT,MAAM,6BAA6B,IAAiC;EAClE,MAAM,UAAU,MAAM,KAAK,SAAS,GAAG;AACvC,MAAI,CAAC,QACH,OAAM,IAAI,kBAAkB,qCAAqC;AAEnE,SAAO;;CAGT,wBAAwB,IAAY;EAClC,MAAM,UAAU,KAAK,cAAc,IAAI,GAAG;AAC1C,MAAI,CAAC,QACH,OAAM,IAAI,kBAAkB,qCAAqC;AAEnE,SAAO;;CAGT,MAAM,aAAa,IAAY;AAE7B,UAAO,MADe,KAAK,6BAA6B,GAAG,EAC5C;;CAGjB,MAAM,iBAAiB,IAAY;EACjC,MAAM,UAAU,MAAM,KAAK,6BAA6B,GAAG;AAC3D,SAAO;GACL,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,UAAU,QAAQ;GAClB,aAAa,QAAQ;GACtB;;CAGH,YAAY,IAAY;EACtB,MAAM,UAAU,KAAK,wBAAwB,GAAG;AAChD,SAAO;GACL,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GAChB,aAAa,QAAQ;GACrB,UAAU,QAAQ;GAClB,UAAU,QAAQ;GACnB;;CAGH,MAAc,0BAA0B,OAAiC;EAEvE,MAAM,YADiB,KAAK,SAAS,MAAM,SACX,CAAC,KAAK,OAAO;GAAE,KAAK,EAAE;GAAI,OAAO;GAAG,EAAE;AACtE,QAAM,KAAK,kBAAkB,WAAW,KAAK;;CAG/C,MAAc,8BAA8B,OAA4B;EACtE,MAAM,aAAa,KAAK,IAAI,MAAM,QAAQ;AAC1C,QAAM,KAAK,YAAY,WAAW,IAAI,YAAY,KAAK;;CAGzD,MAAc,sBAAsB,OAA6B;AAC/D,QAAM,KAAK,gBAAgB,MAAM,YAAY,KAAK;;CAGpD,SAAiB,UAAqB;AACpC,SAAO,SAAS,KAAK,MAAM;AACzB,UAAO,KAAK,IAAI,EAAE;IAClB;;CAGJ,IAAY,QAA6B;AACvC,SAAO,KAAK,eAAe,MAAM,OAAO"}
1
+ {"version":3,"file":"printer.cache.js","names":[],"sources":["../../src/state/printer.cache.ts"],"sourcesContent":["import { KeyDiffCache } from \"@/utils/cache/key-diff.cache\";\nimport {\n BatchPrinterCreatedEvent,\n PrinterCreatedEvent,\n printerEvents,\n PrintersDeletedEvent,\n} from \"@/constants/event.constants\";\nimport { NotFoundException } from \"@/exceptions/runtime.exceptions\";\nimport EventEmitter2 from \"eventemitter2\";\nimport type { IPrinterService } from \"@/services/interfaces/printer.service.interface\";\nimport type { PrinterDto } from \"@/services/interfaces/printer.dto\";\nimport { Printer } from \"@/entities\";\n\nexport class PrinterCache extends KeyDiffCache<PrinterDto> {\n constructor(\n private readonly printerService: IPrinterService,\n private readonly eventEmitter2: EventEmitter2,\n ) {\n super();\n\n this.eventEmitter2.on(printerEvents.batchPrinterCreated, this.handleBatchPrinterCreated.bind(this));\n this.eventEmitter2.on(printerEvents.printerCreated, this.handlePrinterCreatedOrUpdated.bind(this));\n this.eventEmitter2.on(printerEvents.printerUpdated, this.handlePrinterCreatedOrUpdated.bind(this));\n this.eventEmitter2.on(printerEvents.printersDeleted, this.handlePrintersDeleted.bind(this));\n }\n\n async loadCache(): Promise<PrinterDto[]> {\n const printerDocs = await this.printerService.list();\n const dtos = this.mapArray(printerDocs);\n const keyValues = dtos.map((p) => ({ key: p.id, value: p }));\n await this.setKeyValuesBatch(keyValues, true);\n return dtos;\n }\n\n async listCachedPrinters(includeDisabled = false): Promise<PrinterDto[]> {\n const printers = await this.getAllValues();\n if (!includeDisabled) {\n return printers.filter((p) => p.enabled);\n }\n return printers;\n }\n\n async getCachedPrinterOrThrowAsync(id: number): Promise<PrinterDto> {\n const printer = await this.getValue(id);\n if (!printer) {\n throw new NotFoundException(`Printer with provided id not found`);\n }\n return printer;\n }\n\n getCachedPrinterOrThrow(id: number) {\n const printer = this.keyValueStore.get(id);\n if (!printer) {\n throw new NotFoundException(`Printer with provided id not found`);\n }\n return printer;\n }\n\n async getNameAsync(id: number) {\n const printer = await this.getCachedPrinterOrThrowAsync(id);\n return printer.name;\n }\n\n async getLoginDtoAsync(id: number) {\n const printer = await this.getCachedPrinterOrThrowAsync(id);\n return {\n printerURL: printer.printerURL,\n apiKey: printer.apiKey,\n username: printer.username,\n password: printer.password,\n printerType: printer.printerType,\n };\n }\n\n getLoginDto(id: number) {\n const printer = this.getCachedPrinterOrThrow(id);\n return {\n printerURL: printer.printerURL,\n apiKey: printer.apiKey,\n printerType: printer.printerType,\n username: printer.username,\n password: printer.password,\n };\n }\n\n private async handleBatchPrinterCreated(event: BatchPrinterCreatedEvent) {\n const mappedPrinters = this.mapArray(event.printers);\n const keyValues = mappedPrinters.map((p) => ({ key: p.id, value: p }));\n await this.setKeyValuesBatch(keyValues, true);\n }\n\n private async handlePrinterCreatedOrUpdated(event: PrinterCreatedEvent) {\n const printerDto = this.map(event.printer);\n await this.setKeyValue(printerDto.id, printerDto, true);\n }\n\n private async handlePrintersDeleted(event: PrintersDeletedEvent) {\n await this.deleteKeysBatch(event.printerIds, true);\n }\n\n private mapArray(entities: Printer[]) {\n return entities.map((p) => {\n return this.map(p);\n });\n }\n\n private map(entity: Printer): PrinterDto {\n return this.printerService.toDto(entity);\n }\n}\n"],"mappings":";;;;AAaA,IAAa,eAAb,cAAkC,aAAyB;CACzD,YACE,gBACA,eACA;EACA,OAAO;EAHU,KAAA,iBAAA;EACA,KAAA,gBAAA;EAIjB,KAAK,cAAc,GAAG,cAAc,qBAAqB,KAAK,0BAA0B,KAAK,KAAK,CAAC;EACnG,KAAK,cAAc,GAAG,cAAc,gBAAgB,KAAK,8BAA8B,KAAK,KAAK,CAAC;EAClG,KAAK,cAAc,GAAG,cAAc,gBAAgB,KAAK,8BAA8B,KAAK,KAAK,CAAC;EAClG,KAAK,cAAc,GAAG,cAAc,iBAAiB,KAAK,sBAAsB,KAAK,KAAK,CAAC;;CAG7F,MAAM,YAAmC;EACvC,MAAM,cAAc,MAAM,KAAK,eAAe,MAAM;EACpD,MAAM,OAAO,KAAK,SAAS,YAAY;EACvC,MAAM,YAAY,KAAK,KAAK,OAAO;GAAE,KAAK,EAAE;GAAI,OAAO;GAAG,EAAE;EAC5D,MAAM,KAAK,kBAAkB,WAAW,KAAK;EAC7C,OAAO;;CAGT,MAAM,mBAAmB,kBAAkB,OAA8B;EACvE,MAAM,WAAW,MAAM,KAAK,cAAc;EAC1C,IAAI,CAAC,iBACH,OAAO,SAAS,QAAQ,MAAM,EAAE,QAAQ;EAE1C,OAAO;;CAGT,MAAM,6BAA6B,IAAiC;EAClE,MAAM,UAAU,MAAM,KAAK,SAAS,GAAG;EACvC,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB,qCAAqC;EAEnE,OAAO;;CAGT,wBAAwB,IAAY;EAClC,MAAM,UAAU,KAAK,cAAc,IAAI,GAAG;EAC1C,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB,qCAAqC;EAEnE,OAAO;;CAGT,MAAM,aAAa,IAAY;EAE7B,QAAO,MADe,KAAK,6BAA6B,GAAG,EAC5C;;CAGjB,MAAM,iBAAiB,IAAY;EACjC,MAAM,UAAU,MAAM,KAAK,6BAA6B,GAAG;EAC3D,OAAO;GACL,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,UAAU,QAAQ;GAClB,aAAa,QAAQ;GACtB;;CAGH,YAAY,IAAY;EACtB,MAAM,UAAU,KAAK,wBAAwB,GAAG;EAChD,OAAO;GACL,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GAChB,aAAa,QAAQ;GACrB,UAAU,QAAQ;GAClB,UAAU,QAAQ;GACnB;;CAGH,MAAc,0BAA0B,OAAiC;EAEvE,MAAM,YADiB,KAAK,SAAS,MAAM,SACX,CAAC,KAAK,OAAO;GAAE,KAAK,EAAE;GAAI,OAAO;GAAG,EAAE;EACtE,MAAM,KAAK,kBAAkB,WAAW,KAAK;;CAG/C,MAAc,8BAA8B,OAA4B;EACtE,MAAM,aAAa,KAAK,IAAI,MAAM,QAAQ;EAC1C,MAAM,KAAK,YAAY,WAAW,IAAI,YAAY,KAAK;;CAGzD,MAAc,sBAAsB,OAA6B;EAC/D,MAAM,KAAK,gBAAgB,MAAM,YAAY,KAAK;;CAGpD,SAAiB,UAAqB;EACpC,OAAO,SAAS,KAAK,MAAM;GACzB,OAAO,KAAK,IAAI,EAAE;IAClB;;CAGJ,IAAY,QAA6B;EACvC,OAAO,KAAK,eAAe,MAAM,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"settings.store.js","names":["uuidv4"],"sources":["../../src/state/settings.store.ts"],"sourcesContent":["import { InternalServerException } from \"@/exceptions/runtime.exceptions\";\nimport {\n credentialSettingsKey,\n frontendSettingKey,\n serverSettingsKey,\n timeoutSettingKey,\n wizardSettingKey,\n} from \"@/constants/server-settings.constants\";\nimport { getClient } from \"@sentry/node\";\nimport { isTestEnvironment } from \"@/utils/env.utils\";\nimport { AppConstants } from \"@/server.constants\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ISettingsService } from \"@/services/interfaces/settings.service.interface\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { z } from \"zod\";\nimport {\n credentialCoreSettingUpdateSchema,\n frontendSettingsUpdateSchema,\n serverSettingsUpdateSchema,\n timeoutSettingsUpdateSchema,\n} from \"@/services/validators/settings-service.validation\";\nimport { Settings } from \"@/entities\";\nimport { v4 as uuidv4 } from \"uuid\";\n\nexport class SettingsStore {\n private readonly logger: LoggerService;\n\n private settings: Settings | null = null;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly settingsService: ISettingsService,\n ) {\n this.logger = loggerFactory(SettingsStore.name);\n }\n\n getSettings() {\n this.throwIfSettingsUnset();\n\n const settings = this.settings!;\n\n return Object.freeze({\n // Credential settings are not shared with the client\n [serverSettingsKey]: {\n loginRequired: settings[serverSettingsKey].loginRequired,\n registration: settings[serverSettingsKey].registration,\n sentryDiagnosticsEnabled: settings[serverSettingsKey].sentryDiagnosticsEnabled,\n experimentalMoonrakerSupport: settings[serverSettingsKey].experimentalMoonrakerSupport,\n experimentalPrusaLinkSupport: settings[serverSettingsKey].experimentalPrusaLinkSupport,\n experimentalBambuSupport: settings[serverSettingsKey].experimentalBambuSupport,\n },\n [wizardSettingKey]: settings[wizardSettingKey],\n [frontendSettingKey]: settings[frontendSettingKey],\n [timeoutSettingKey]: settings[timeoutSettingKey],\n });\n }\n\n getSettingsSensitive() {\n this.throwIfSettingsUnset();\n\n const settings = this.settings!;\n return Object.freeze({\n [credentialSettingsKey]: {\n jwtExpiresIn: settings[credentialSettingsKey].jwtExpiresIn,\n refreshTokenAttempts: settings[credentialSettingsKey].refreshTokenAttempts,\n refreshTokenExpiry: settings[credentialSettingsKey].refreshTokenExpiry,\n slicerApiKey: settings[credentialSettingsKey].slicerApiKey,\n },\n [serverSettingsKey]: {\n loginRequired: settings[serverSettingsKey].loginRequired,\n registration: settings[serverSettingsKey].registration,\n sentryDiagnosticsEnabled: settings[serverSettingsKey].sentryDiagnosticsEnabled,\n experimentalMoonrakerSupport: settings[serverSettingsKey].experimentalMoonrakerSupport,\n experimentalPrusaLinkSupport: settings[serverSettingsKey].experimentalPrusaLinkSupport,\n experimentalBambuSupport: settings[serverSettingsKey].experimentalBambuSupport,\n },\n });\n }\n\n async loadSettings() {\n // Setup Settings and add established connection info\n this.settings = await this.settingsService.getOrCreate();\n await this.processSentryEnabled();\n }\n\n async getCredentialSettings() {\n this.throwIfSettingsUnset();\n\n return this.settings![credentialSettingsKey];\n }\n\n async getAnonymousDiagnosticsEnabled() {\n this.throwIfSettingsUnset();\n\n return this.settings![serverSettingsKey].sentryDiagnosticsEnabled;\n }\n\n async persistOptionalCredentialSettings(overrideJwtSecret?: string, overrideJwtExpiresIn?: string) {\n this.throwIfSettingsUnset();\n\n const credentialSettings = await this.getCredentialSettings();\n if (overrideJwtSecret?.length) {\n await this.settingsService.updateJwtSecretCredentialSetting({\n jwtSecret: overrideJwtSecret,\n });\n }\n\n if (overrideJwtExpiresIn?.length) {\n await this.updateCoreCredentialSettings({\n refreshTokenExpiry: credentialSettings.refreshTokenExpiry,\n refreshTokenAttempts: credentialSettings.refreshTokenAttempts,\n jwtExpiresIn: Number.parseInt(overrideJwtExpiresIn),\n });\n }\n\n this.settings![credentialSettingsKey] = await this.getCredentialSettings();\n }\n\n getWizardState() {\n this.throwIfSettingsUnset();\n\n const settings = this.settings!;\n return {\n wizardCompleted: settings[wizardSettingKey].wizardCompleted,\n wizardVersion: settings[wizardSettingKey].wizardVersion,\n latestWizardVersion: AppConstants.currentWizardVersion,\n };\n }\n\n isWizardCompleted() {\n this.throwIfSettingsUnset();\n\n const settings = this.settings!;\n return (\n settings[wizardSettingKey].wizardCompleted &&\n settings[wizardSettingKey].wizardVersion === AppConstants.currentWizardVersion\n );\n }\n\n getWizardSettings() {\n this.throwIfSettingsUnset();\n\n return this.settings![wizardSettingKey];\n }\n\n isRegistrationEnabled() {\n this.throwIfSettingsUnset();\n\n return this.settings![serverSettingsKey].registration;\n }\n\n getServerSettings() {\n return this.getSettings()[serverSettingsKey];\n }\n\n getTimeoutSettings() {\n return this.getSettings()[timeoutSettingKey];\n }\n\n async setWizardCompleted(version: number) {\n this.settings = await this.settingsService.updateWizardSettings({\n wizardCompleted: true,\n wizardCompletedAt: new Date(),\n wizardVersion: version,\n });\n return this.getSettings();\n }\n\n async getLoginRequired() {\n return this.getServerSettings().loginRequired;\n }\n\n async setLoginRequired(loginRequired = true) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].loginRequired = loginRequired;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n return this.getSettings();\n }\n\n async setRegistrationEnabled(registration = true) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].registration = registration;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n return this.getSettings();\n }\n\n async updateServerSettings(serverSettings: z.infer<typeof serverSettingsUpdateSchema>) {\n this.settings = await this.settingsService.updateServerSettings(serverSettings);\n return this.getSettings();\n }\n\n async updateTimeoutSettings(timeoutSettings: z.infer<typeof timeoutSettingsUpdateSchema>) {\n this.settings = await this.settingsService.updateTimeoutSettings(timeoutSettings);\n return this.getSettings();\n }\n\n async updateCoreCredentialSettings(credentialSettings: z.infer<typeof credentialCoreSettingUpdateSchema>) {\n this.settings = await this.settingsService.updateCoreCredentialSettings(credentialSettings);\n }\n\n async setRefreshTokenSettings({\n refreshTokenAttempts,\n refreshTokenExpiry,\n }: {\n refreshTokenAttempts: number;\n refreshTokenExpiry: number;\n }) {\n this.throwIfSettingsUnset();\n this.settings![credentialSettingsKey].refreshTokenAttempts = refreshTokenAttempts;\n this.settings![credentialSettingsKey].refreshTokenExpiry = refreshTokenExpiry;\n await this.updateCoreCredentialSettings(this.settings![credentialSettingsKey]);\n }\n\n async setSentryDiagnosticsEnabled(sentryDiagnosticsEnabled: boolean) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].sentryDiagnosticsEnabled = sentryDiagnosticsEnabled;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n await this.processSentryEnabled();\n return this.getSettings();\n }\n\n async setExperimentalMoonrakerSupport(experimentalMoonrakerSupport: boolean) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].experimentalMoonrakerSupport = experimentalMoonrakerSupport;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n return this.getSettings();\n }\n\n async setExperimentalPrusaLinkSupport(experimentalPrusaLinkSupport: boolean) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].experimentalPrusaLinkSupport = experimentalPrusaLinkSupport;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n\n return this.getSettings();\n }\n\n async setExperimentalBambuSupport(experimentalBambuSupport: boolean) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].experimentalBambuSupport = experimentalBambuSupport;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n return this.getSettings();\n }\n\n async updateFrontendSettings(frontendSettings: z.infer<typeof frontendSettingsUpdateSchema>) {\n this.settings = await this.settingsService.updateFrontendSettings(frontendSettings);\n return this.getSettings();\n }\n\n getSlicerApiKey() {\n this.throwIfSettingsUnset();\n return this.settings![credentialSettingsKey].slicerApiKey;\n }\n\n async generateSlicerApiKey() {\n const newApiKey = uuidv4();\n return this.setSlicerApiKey(newApiKey);\n }\n\n async setSlicerApiKey(apiKey: string) {\n this.throwIfSettingsUnset();\n this.settings = await this.settingsService.updateSlicerApiKey({ slicerApiKey: apiKey });\n return apiKey;\n }\n\n async deleteSlicerApiKey() {\n this.throwIfSettingsUnset();\n this.settings = await this.settingsService.updateSlicerApiKey({ slicerApiKey: null });\n }\n\n validateSlicerApiKey(apiKey: string): boolean {\n const storedKey = this.getSlicerApiKey();\n if (!storedKey) {\n return false;\n }\n return storedKey === apiKey;\n }\n\n private throwIfSettingsUnset() {\n if (!this.settings)\n throw new InternalServerException(\"Could not check server settings (server settings not loaded)\");\n }\n\n private async processSentryEnabled() {\n const sentryEnabled = await this.getAnonymousDiagnosticsEnabled();\n if (sentryEnabled) {\n this.logger.log(\"Enabling Sentry for remote diagnostics\");\n } else {\n this.logger.log(\"Disabling Sentry for remote diagnostics\");\n }\n\n if (isTestEnvironment()) return;\n const client = getClient();\n if (!client) {\n this.logger.warn(\"Could not apply Sentry. Was the SDK initialized?\");\n return;\n }\n client.getOptions().enabled = sentryEnabled;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"settings.store.js","names":["uuidv4"],"sources":["../../src/state/settings.store.ts"],"sourcesContent":["import { InternalServerException } from \"@/exceptions/runtime.exceptions\";\nimport {\n credentialSettingsKey,\n frontendSettingKey,\n serverSettingsKey,\n timeoutSettingKey,\n wizardSettingKey,\n} from \"@/constants/server-settings.constants\";\nimport { getClient } from \"@sentry/node\";\nimport { isTestEnvironment } from \"@/utils/env.utils\";\nimport { AppConstants } from \"@/server.constants\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ISettingsService } from \"@/services/interfaces/settings.service.interface\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { z } from \"zod\";\nimport {\n credentialCoreSettingUpdateSchema,\n frontendSettingsUpdateSchema,\n serverSettingsUpdateSchema,\n timeoutSettingsUpdateSchema,\n} from \"@/services/validators/settings-service.validation\";\nimport { Settings } from \"@/entities\";\nimport { v4 as uuidv4 } from \"uuid\";\n\nexport class SettingsStore {\n private readonly logger: LoggerService;\n\n private settings: Settings | null = null;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly settingsService: ISettingsService,\n ) {\n this.logger = loggerFactory(SettingsStore.name);\n }\n\n getSettings() {\n this.throwIfSettingsUnset();\n\n const settings = this.settings!;\n\n return Object.freeze({\n // Credential settings are not shared with the client\n [serverSettingsKey]: {\n loginRequired: settings[serverSettingsKey].loginRequired,\n registration: settings[serverSettingsKey].registration,\n sentryDiagnosticsEnabled: settings[serverSettingsKey].sentryDiagnosticsEnabled,\n experimentalMoonrakerSupport: settings[serverSettingsKey].experimentalMoonrakerSupport,\n experimentalPrusaLinkSupport: settings[serverSettingsKey].experimentalPrusaLinkSupport,\n experimentalBambuSupport: settings[serverSettingsKey].experimentalBambuSupport,\n },\n [wizardSettingKey]: settings[wizardSettingKey],\n [frontendSettingKey]: settings[frontendSettingKey],\n [timeoutSettingKey]: settings[timeoutSettingKey],\n });\n }\n\n getSettingsSensitive() {\n this.throwIfSettingsUnset();\n\n const settings = this.settings!;\n return Object.freeze({\n [credentialSettingsKey]: {\n jwtExpiresIn: settings[credentialSettingsKey].jwtExpiresIn,\n refreshTokenAttempts: settings[credentialSettingsKey].refreshTokenAttempts,\n refreshTokenExpiry: settings[credentialSettingsKey].refreshTokenExpiry,\n slicerApiKey: settings[credentialSettingsKey].slicerApiKey,\n },\n [serverSettingsKey]: {\n loginRequired: settings[serverSettingsKey].loginRequired,\n registration: settings[serverSettingsKey].registration,\n sentryDiagnosticsEnabled: settings[serverSettingsKey].sentryDiagnosticsEnabled,\n experimentalMoonrakerSupport: settings[serverSettingsKey].experimentalMoonrakerSupport,\n experimentalPrusaLinkSupport: settings[serverSettingsKey].experimentalPrusaLinkSupport,\n experimentalBambuSupport: settings[serverSettingsKey].experimentalBambuSupport,\n },\n });\n }\n\n async loadSettings() {\n // Setup Settings and add established connection info\n this.settings = await this.settingsService.getOrCreate();\n await this.processSentryEnabled();\n }\n\n async getCredentialSettings() {\n this.throwIfSettingsUnset();\n\n return this.settings![credentialSettingsKey];\n }\n\n async getAnonymousDiagnosticsEnabled() {\n this.throwIfSettingsUnset();\n\n return this.settings![serverSettingsKey].sentryDiagnosticsEnabled;\n }\n\n async persistOptionalCredentialSettings(overrideJwtSecret?: string, overrideJwtExpiresIn?: string) {\n this.throwIfSettingsUnset();\n\n const credentialSettings = await this.getCredentialSettings();\n if (overrideJwtSecret?.length) {\n await this.settingsService.updateJwtSecretCredentialSetting({\n jwtSecret: overrideJwtSecret,\n });\n }\n\n if (overrideJwtExpiresIn?.length) {\n await this.updateCoreCredentialSettings({\n refreshTokenExpiry: credentialSettings.refreshTokenExpiry,\n refreshTokenAttempts: credentialSettings.refreshTokenAttempts,\n jwtExpiresIn: Number.parseInt(overrideJwtExpiresIn),\n });\n }\n\n this.settings![credentialSettingsKey] = await this.getCredentialSettings();\n }\n\n getWizardState() {\n this.throwIfSettingsUnset();\n\n const settings = this.settings!;\n return {\n wizardCompleted: settings[wizardSettingKey].wizardCompleted,\n wizardVersion: settings[wizardSettingKey].wizardVersion,\n latestWizardVersion: AppConstants.currentWizardVersion,\n };\n }\n\n isWizardCompleted() {\n this.throwIfSettingsUnset();\n\n const settings = this.settings!;\n return (\n settings[wizardSettingKey].wizardCompleted &&\n settings[wizardSettingKey].wizardVersion === AppConstants.currentWizardVersion\n );\n }\n\n getWizardSettings() {\n this.throwIfSettingsUnset();\n\n return this.settings![wizardSettingKey];\n }\n\n isRegistrationEnabled() {\n this.throwIfSettingsUnset();\n\n return this.settings![serverSettingsKey].registration;\n }\n\n getServerSettings() {\n return this.getSettings()[serverSettingsKey];\n }\n\n getTimeoutSettings() {\n return this.getSettings()[timeoutSettingKey];\n }\n\n async setWizardCompleted(version: number) {\n this.settings = await this.settingsService.updateWizardSettings({\n wizardCompleted: true,\n wizardCompletedAt: new Date(),\n wizardVersion: version,\n });\n return this.getSettings();\n }\n\n async getLoginRequired() {\n return this.getServerSettings().loginRequired;\n }\n\n async setLoginRequired(loginRequired = true) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].loginRequired = loginRequired;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n return this.getSettings();\n }\n\n async setRegistrationEnabled(registration = true) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].registration = registration;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n return this.getSettings();\n }\n\n async updateServerSettings(serverSettings: z.infer<typeof serverSettingsUpdateSchema>) {\n this.settings = await this.settingsService.updateServerSettings(serverSettings);\n return this.getSettings();\n }\n\n async updateTimeoutSettings(timeoutSettings: z.infer<typeof timeoutSettingsUpdateSchema>) {\n this.settings = await this.settingsService.updateTimeoutSettings(timeoutSettings);\n return this.getSettings();\n }\n\n async updateCoreCredentialSettings(credentialSettings: z.infer<typeof credentialCoreSettingUpdateSchema>) {\n this.settings = await this.settingsService.updateCoreCredentialSettings(credentialSettings);\n }\n\n async setRefreshTokenSettings({\n refreshTokenAttempts,\n refreshTokenExpiry,\n }: {\n refreshTokenAttempts: number;\n refreshTokenExpiry: number;\n }) {\n this.throwIfSettingsUnset();\n this.settings![credentialSettingsKey].refreshTokenAttempts = refreshTokenAttempts;\n this.settings![credentialSettingsKey].refreshTokenExpiry = refreshTokenExpiry;\n await this.updateCoreCredentialSettings(this.settings![credentialSettingsKey]);\n }\n\n async setSentryDiagnosticsEnabled(sentryDiagnosticsEnabled: boolean) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].sentryDiagnosticsEnabled = sentryDiagnosticsEnabled;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n await this.processSentryEnabled();\n return this.getSettings();\n }\n\n async setExperimentalMoonrakerSupport(experimentalMoonrakerSupport: boolean) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].experimentalMoonrakerSupport = experimentalMoonrakerSupport;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n return this.getSettings();\n }\n\n async setExperimentalPrusaLinkSupport(experimentalPrusaLinkSupport: boolean) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].experimentalPrusaLinkSupport = experimentalPrusaLinkSupport;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n\n return this.getSettings();\n }\n\n async setExperimentalBambuSupport(experimentalBambuSupport: boolean) {\n this.throwIfSettingsUnset();\n this.settings![serverSettingsKey].experimentalBambuSupport = experimentalBambuSupport;\n this.settings = await this.settingsService.updateServerSettings(this.settings![serverSettingsKey]);\n return this.getSettings();\n }\n\n async updateFrontendSettings(frontendSettings: z.infer<typeof frontendSettingsUpdateSchema>) {\n this.settings = await this.settingsService.updateFrontendSettings(frontendSettings);\n return this.getSettings();\n }\n\n getSlicerApiKey() {\n this.throwIfSettingsUnset();\n return this.settings![credentialSettingsKey].slicerApiKey;\n }\n\n async generateSlicerApiKey() {\n const newApiKey = uuidv4();\n return this.setSlicerApiKey(newApiKey);\n }\n\n async setSlicerApiKey(apiKey: string) {\n this.throwIfSettingsUnset();\n this.settings = await this.settingsService.updateSlicerApiKey({ slicerApiKey: apiKey });\n return apiKey;\n }\n\n async deleteSlicerApiKey() {\n this.throwIfSettingsUnset();\n this.settings = await this.settingsService.updateSlicerApiKey({ slicerApiKey: null });\n }\n\n validateSlicerApiKey(apiKey: string): boolean {\n const storedKey = this.getSlicerApiKey();\n if (!storedKey) {\n return false;\n }\n return storedKey === apiKey;\n }\n\n private throwIfSettingsUnset() {\n if (!this.settings)\n throw new InternalServerException(\"Could not check server settings (server settings not loaded)\");\n }\n\n private async processSentryEnabled() {\n const sentryEnabled = await this.getAnonymousDiagnosticsEnabled();\n if (sentryEnabled) {\n this.logger.log(\"Enabling Sentry for remote diagnostics\");\n } else {\n this.logger.log(\"Disabling Sentry for remote diagnostics\");\n }\n\n if (isTestEnvironment()) return;\n const client = getClient();\n if (!client) {\n this.logger.warn(\"Could not apply Sentry. Was the SDK initialized?\");\n return;\n }\n client.getOptions().enabled = sentryEnabled;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"socket-io.gateway.js","names":[],"sources":["../../src/state/socket-io.gateway.ts"],"sourcesContent":["import { Server, Socket } from \"socket.io\";\nimport { socketIoConnectedEvent } from \"@/constants/event.constants\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { Server as HttpServer } from \"http\";\nimport { getPassportJwtOptions, verifyUserCallback } from \"@/middleware/passport\";\nimport type { IConfigService } from \"@/services/core/config.service\";\nimport type { IUserService } from \"@/services/interfaces/user-service.interface\";\nimport { authorize } from \"@/middleware/socketio.middleware\";\nimport { Counter, Gauge } from \"prom-client\";\n\nconst socketIoGatewaySessions = new Gauge({\n name: \"socketio_gateway_sessions\",\n help: \"Gateway active sessions\",\n});\n\nconst socketIoGatewayDisconnects = new Counter({\n name: \"socketio_gateway_disconnects\",\n help: \"Gateway connections closed\",\n});\n\nconst socketIoGatewayMessagesSent = new Counter({\n name: \"socketio_messages_sent\",\n help: \"Gateway messages sent\",\n});\n\nconst socketIoGatewayMessageSentSize = new Gauge({\n name: \"socketio_message_size\",\n help: \"Gateway message sent size\",\n});\n\nexport class SocketIoGateway {\n logger: LoggerService;\n\n io: Server;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly eventEmitter2: EventEmitter2,\n private readonly settingsStore: SettingsStore,\n private readonly userService: IUserService,\n private readonly configService: IConfigService,\n ) {\n this.logger = loggerFactory(SocketIoGateway.name);\n }\n\n attachServer(httpServer: HttpServer) {\n this.io = new Server(httpServer, { cors: { origin: \"*\" } });\n const opts = getPassportJwtOptions(\n this.settingsStore,\n this.configService,\n (value: Socket) => value.handshake.auth.token,\n );\n this.io.use(authorize(this.settingsStore, opts, this.logger, verifyUserCallback(this.userService)));\n this.io.on(\"connection\", (socket) => this.onConnect.bind(this)(socket));\n }\n\n onConnect(socket: Socket) {\n this.logger.debug(\"SocketIO Client connected\", { socketId: socket.id });\n this.eventEmitter2.emit(socketIoConnectedEvent, socket.id);\n socketIoGatewaySessions.inc();\n\n socket.on(\"disconnect\", () => {\n this.logger.debug(\"SocketIO Client disconnected\", { socketId: socket.id });\n socketIoGatewaySessions.dec(1);\n socketIoGatewayDisconnects.inc();\n });\n }\n\n send<T>(event: string, data: T) {\n if (!this.io) {\n this.logger.debug(`Cant send event ${event}, socketio gateway must be created first`);\n return;\n }\n\n this.io.emit(event, data);\n socketIoGatewayMessagesSent.inc();\n\n const payload = JSON.stringify(data);\n const sizeInBytes = Buffer.byteLength(payload);\n socketIoGatewayMessageSentSize.set(sizeInBytes);\n }\n}\n\nexport const IO_MESSAGES = {\n Update: \"update\",\n};\n"],"mappings":";;;;;;AAaA,MAAM,0BAA0B,IAAI,MAAM;CACxC,MAAM;CACN,MAAM;CACP,CAAC;AAEF,MAAM,6BAA6B,IAAI,QAAQ;CAC7C,MAAM;CACN,MAAM;CACP,CAAC;AAEF,MAAM,8BAA8B,IAAI,QAAQ;CAC9C,MAAM;CACN,MAAM;CACP,CAAC;AAEF,MAAM,iCAAiC,IAAI,MAAM;CAC/C,MAAM;CACN,MAAM;CACP,CAAC;AAEF,IAAa,kBAAb,MAAa,gBAAgB;CAC3B;CAEA;CAEA,YACE,eACA,eACA,eACA,aACA,eACA;AAJiB,OAAA,gBAAA;AACA,OAAA,gBAAA;AACA,OAAA,cAAA;AACA,OAAA,gBAAA;AAEjB,OAAK,SAAS,cAAc,gBAAgB,KAAK;;CAGnD,aAAa,YAAwB;AACnC,OAAK,KAAK,IAAI,OAAO,YAAY,EAAE,MAAM,EAAE,QAAQ,KAAK,EAAE,CAAC;EAC3D,MAAM,OAAO,sBACX,KAAK,eACL,KAAK,gBACJ,UAAkB,MAAM,UAAU,KAAK,MACzC;AACD,OAAK,GAAG,IAAI,UAAU,KAAK,eAAe,MAAM,KAAK,QAAQ,mBAAmB,KAAK,YAAY,CAAC,CAAC;AACnG,OAAK,GAAG,GAAG,eAAe,WAAW,KAAK,UAAU,KAAK,KAAK,CAAC,OAAO,CAAC;;CAGzE,UAAU,QAAgB;AACxB,OAAK,OAAO,MAAM,6BAA6B,EAAE,UAAU,OAAO,IAAI,CAAC;AACvE,OAAK,cAAc,KAAK,wBAAwB,OAAO,GAAG;AAC1D,0BAAwB,KAAK;AAE7B,SAAO,GAAG,oBAAoB;AAC5B,QAAK,OAAO,MAAM,gCAAgC,EAAE,UAAU,OAAO,IAAI,CAAC;AAC1E,2BAAwB,IAAI,EAAE;AAC9B,8BAA2B,KAAK;IAChC;;CAGJ,KAAQ,OAAe,MAAS;AAC9B,MAAI,CAAC,KAAK,IAAI;AACZ,QAAK,OAAO,MAAM,mBAAmB,MAAM,0CAA0C;AACrF;;AAGF,OAAK,GAAG,KAAK,OAAO,KAAK;AACzB,8BAA4B,KAAK;EAEjC,MAAM,UAAU,KAAK,UAAU,KAAK;EACpC,MAAM,cAAc,OAAO,WAAW,QAAQ;AAC9C,iCAA+B,IAAI,YAAY;;;AAInD,MAAa,cAAc,EACzB,QAAQ,UACT"}
1
+ {"version":3,"file":"socket-io.gateway.js","names":[],"sources":["../../src/state/socket-io.gateway.ts"],"sourcesContent":["import { Server, Socket } from \"socket.io\";\nimport { socketIoConnectedEvent } from \"@/constants/event.constants\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { Server as HttpServer } from \"http\";\nimport { getPassportJwtOptions, verifyUserCallback } from \"@/middleware/passport\";\nimport type { IConfigService } from \"@/services/core/config.service\";\nimport type { IUserService } from \"@/services/interfaces/user-service.interface\";\nimport { authorize } from \"@/middleware/socketio.middleware\";\nimport { Counter, Gauge } from \"prom-client\";\n\nconst socketIoGatewaySessions = new Gauge({\n name: \"socketio_gateway_sessions\",\n help: \"Gateway active sessions\",\n});\n\nconst socketIoGatewayDisconnects = new Counter({\n name: \"socketio_gateway_disconnects\",\n help: \"Gateway connections closed\",\n});\n\nconst socketIoGatewayMessagesSent = new Counter({\n name: \"socketio_messages_sent\",\n help: \"Gateway messages sent\",\n});\n\nconst socketIoGatewayMessageSentSize = new Gauge({\n name: \"socketio_message_size\",\n help: \"Gateway message sent size\",\n});\n\nexport class SocketIoGateway {\n logger: LoggerService;\n\n io: Server;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly eventEmitter2: EventEmitter2,\n private readonly settingsStore: SettingsStore,\n private readonly userService: IUserService,\n private readonly configService: IConfigService,\n ) {\n this.logger = loggerFactory(SocketIoGateway.name);\n }\n\n attachServer(httpServer: HttpServer) {\n this.io = new Server(httpServer, { cors: { origin: \"*\" } });\n const opts = getPassportJwtOptions(\n this.settingsStore,\n this.configService,\n (value: Socket) => value.handshake.auth.token,\n );\n this.io.use(authorize(this.settingsStore, opts, this.logger, verifyUserCallback(this.userService)));\n this.io.on(\"connection\", (socket) => this.onConnect.bind(this)(socket));\n }\n\n onConnect(socket: Socket) {\n this.logger.debug(\"SocketIO Client connected\", { socketId: socket.id });\n this.eventEmitter2.emit(socketIoConnectedEvent, socket.id);\n socketIoGatewaySessions.inc();\n\n socket.on(\"disconnect\", () => {\n this.logger.debug(\"SocketIO Client disconnected\", { socketId: socket.id });\n socketIoGatewaySessions.dec(1);\n socketIoGatewayDisconnects.inc();\n });\n }\n\n send<T>(event: string, data: T) {\n if (!this.io) {\n this.logger.debug(`Cant send event ${event}, socketio gateway must be created first`);\n return;\n }\n\n this.io.emit(event, data);\n socketIoGatewayMessagesSent.inc();\n\n const payload = JSON.stringify(data);\n const sizeInBytes = Buffer.byteLength(payload);\n socketIoGatewayMessageSentSize.set(sizeInBytes);\n }\n}\n\nexport const IO_MESSAGES = {\n Update: \"update\",\n};\n"],"mappings":";;;;;;AAaA,MAAM,0BAA0B,IAAI,MAAM;CACxC,MAAM;CACN,MAAM;CACP,CAAC;AAEF,MAAM,6BAA6B,IAAI,QAAQ;CAC7C,MAAM;CACN,MAAM;CACP,CAAC;AAEF,MAAM,8BAA8B,IAAI,QAAQ;CAC9C,MAAM;CACN,MAAM;CACP,CAAC;AAEF,MAAM,iCAAiC,IAAI,MAAM;CAC/C,MAAM;CACN,MAAM;CACP,CAAC;AAEF,IAAa,kBAAb,MAAa,gBAAgB;CAC3B;CAEA;CAEA,YACE,eACA,eACA,eACA,aACA,eACA;EAJiB,KAAA,gBAAA;EACA,KAAA,gBAAA;EACA,KAAA,cAAA;EACA,KAAA,gBAAA;EAEjB,KAAK,SAAS,cAAc,gBAAgB,KAAK;;CAGnD,aAAa,YAAwB;EACnC,KAAK,KAAK,IAAI,OAAO,YAAY,EAAE,MAAM,EAAE,QAAQ,KAAK,EAAE,CAAC;EAC3D,MAAM,OAAO,sBACX,KAAK,eACL,KAAK,gBACJ,UAAkB,MAAM,UAAU,KAAK,MACzC;EACD,KAAK,GAAG,IAAI,UAAU,KAAK,eAAe,MAAM,KAAK,QAAQ,mBAAmB,KAAK,YAAY,CAAC,CAAC;EACnG,KAAK,GAAG,GAAG,eAAe,WAAW,KAAK,UAAU,KAAK,KAAK,CAAC,OAAO,CAAC;;CAGzE,UAAU,QAAgB;EACxB,KAAK,OAAO,MAAM,6BAA6B,EAAE,UAAU,OAAO,IAAI,CAAC;EACvE,KAAK,cAAc,KAAK,wBAAwB,OAAO,GAAG;EAC1D,wBAAwB,KAAK;EAE7B,OAAO,GAAG,oBAAoB;GAC5B,KAAK,OAAO,MAAM,gCAAgC,EAAE,UAAU,OAAO,IAAI,CAAC;GAC1E,wBAAwB,IAAI,EAAE;GAC9B,2BAA2B,KAAK;IAChC;;CAGJ,KAAQ,OAAe,MAAS;EAC9B,IAAI,CAAC,KAAK,IAAI;GACZ,KAAK,OAAO,MAAM,mBAAmB,MAAM,0CAA0C;GACrF;;EAGF,KAAK,GAAG,KAAK,OAAO,KAAK;EACzB,4BAA4B,KAAK;EAEjC,MAAM,UAAU,KAAK,UAAU,KAAK;EACpC,MAAM,cAAc,OAAO,WAAW,QAAQ;EAC9C,+BAA+B,IAAI,YAAY;;;AAInD,MAAa,cAAc,EACzB,QAAQ,UACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"test-printer-socket.store.js","names":[],"sources":["../../src/state/test-printer-socket.store.ts"],"sourcesContent":["import { setInterval, setTimeout } from \"node:timers/promises\";\nimport { validateInput } from \"@/handlers/validators\";\nimport { createTestPrinterSchema } from \"./validation/create-test-printer.validation\";\nimport { octoPrintEvent, WsMessage } from \"@/services/octoprint/octoprint-websocket.adapter\";\nimport { AppConstants } from \"@/server.constants\";\nimport { SocketIoGateway } from \"@/state/socket-io.gateway\";\nimport { SocketFactory } from \"@/services/socket.factory\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { errorSummary } from \"@/utils/error.utils\";\nimport { captureException } from \"@sentry/node\";\nimport { SOCKET_STATE } from \"@/shared/dtos/socket-state.type\";\nimport type { IWebsocketAdapter } from \"@/services/websocket-adapter.interface\";\nimport { moonrakerEvent } from \"@/services/moonraker/constants/moonraker.constants\";\nimport { bambuEvent } from \"@/services/bambu/bambu-mqtt.adapter\";\nimport { prusaLinkEvent } from \"@/services/prusa-link/constants/prusalink.constants\";\nimport { printerEvents } from \"@/constants/event.constants\";\nimport type { OctoPrintEventDto } from \"@/services/octoprint/dto/octoprint-event.dto\";\nimport { z } from \"zod\";\n\n// Use a large number range for test printer IDs to avoid conflicts with real printers\nconst TEST_PRINTER_ID_BASE = 100000;\nlet testPrinterIdCounter = 0;\n\nexport class TestPrinterSocketStore {\n testSocket?: IWebsocketAdapter;\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly socketFactory: SocketFactory,\n private readonly socketIoGateway: SocketIoGateway,\n private readonly eventEmitter2: EventEmitter2,\n ) {\n this.logger = loggerFactory(TestPrinterSocketStore.name);\n }\n\n async setupTestPrinter(correlationToken: string, printer: z.infer<typeof createTestPrinterSchema>): Promise<void> {\n if (this.testSocket) {\n this.testSocket.close();\n delete this.testSocket;\n }\n\n const validatedData = await validateInput(printer, createTestPrinterSchema);\n validatedData.enabled = true;\n\n // Generate a unique test printer ID\n const testPrinterId = TEST_PRINTER_ID_BASE + ++testPrinterIdCounter;\n\n // Create a new socket if it doesn't exist\n this.testSocket = this.socketFactory.createInstance(printer.printerType);\n\n // Reset the socket credentials before (re-)connecting\n this.testSocket.registerCredentials({\n printerId: testPrinterId,\n loginDto: {\n apiKey: printer.apiKey,\n username: printer.username ?? undefined,\n password: printer.password ?? undefined,\n printerURL: printer.printerURL,\n printerType: printer.printerType,\n },\n });\n\n const testEvents = [\n octoPrintEvent(WsMessage.WS_STATE_UPDATED),\n octoPrintEvent(WsMessage.API_STATE_UPDATED),\n octoPrintEvent(WsMessage.WS_CLOSED),\n octoPrintEvent(WsMessage.WS_OPENED),\n octoPrintEvent(WsMessage.WS_ERROR),\n moonrakerEvent(WsMessage.WS_STATE_UPDATED),\n moonrakerEvent(WsMessage.API_STATE_UPDATED),\n moonrakerEvent(WsMessage.WS_CLOSED),\n moonrakerEvent(WsMessage.WS_OPENED),\n moonrakerEvent(WsMessage.WS_ERROR),\n bambuEvent(WsMessage.WS_STATE_UPDATED),\n bambuEvent(WsMessage.API_STATE_UPDATED),\n bambuEvent(WsMessage.WS_CLOSED),\n bambuEvent(WsMessage.WS_OPENED),\n bambuEvent(WsMessage.WS_ERROR),\n prusaLinkEvent(WsMessage.WS_STATE_UPDATED),\n prusaLinkEvent(WsMessage.API_STATE_UPDATED),\n prusaLinkEvent(WsMessage.WS_CLOSED),\n prusaLinkEvent(WsMessage.WS_OPENED),\n prusaLinkEvent(WsMessage.WS_ERROR),\n ];\n const listener = ({ event, payload, printerId }: OctoPrintEventDto) => {\n if (printerId !== testPrinterId) {\n return;\n }\n this.socketIoGateway.send(\"test-printer-state\", {\n event,\n payload,\n correlationToken,\n });\n };\n testEvents.forEach((te) => {\n this.eventEmitter2.on(te, listener);\n });\n\n try {\n this.logger.log(\"Test API calls for authentication and session\");\n await this.testSocket.setupSocketSession();\n\n this.logger.log(\"Test socket connection started\");\n const promise = new Promise(async (resolve, reject) => {\n if (!this.testSocket) {\n this.logger.error(\"Aborting test as testSocket is undefined.\");\n return;\n }\n this.testSocket.open();\n for await (const _startTime of setInterval(100)) {\n if (!this.testSocket) {\n this.logger.warn(\"Test without socket, rejecting\");\n reject(new Error(\"Test without socket, rejecting\"));\n return;\n }\n if (this.testSocket.socketState === SOCKET_STATE.authenticated) {\n this.logger.log(\"Test completed successfully, resolving\");\n resolve(true);\n break;\n }\n }\n });\n\n await Promise.race([promise, setTimeout(AppConstants.defaultWebsocketHandshakeTimeout)]);\n\n this.logger.log(\"Test finalized\");\n } catch (e) {\n this.logger.error(`Test harness error ${errorSummary(e)}`);\n captureException(e);\n } finally {\n // Ensure that the printer does not re-register itself after being purged\n this.testSocket.disallowEmittingEvents();\n\n if (this.testSocket) {\n this.testSocket.close();\n }\n this.eventEmitter2.emit(printerEvents.printersDeleted, {\n printerIds: [testPrinterId],\n });\n delete this.testSocket;\n testEvents.forEach((te) => {\n this.eventEmitter2.off(te, listener);\n });\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"test-printer-socket.store.js","names":[],"sources":["../../src/state/test-printer-socket.store.ts"],"sourcesContent":["import { setInterval, setTimeout } from \"node:timers/promises\";\nimport { validateInput } from \"@/handlers/validators\";\nimport { createTestPrinterSchema } from \"./validation/create-test-printer.validation\";\nimport { octoPrintEvent, WsMessage } from \"@/services/octoprint/octoprint-websocket.adapter\";\nimport { AppConstants } from \"@/server.constants\";\nimport { SocketIoGateway } from \"@/state/socket-io.gateway\";\nimport { SocketFactory } from \"@/services/socket.factory\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { errorSummary } from \"@/utils/error.utils\";\nimport { captureException } from \"@sentry/node\";\nimport { SOCKET_STATE } from \"@/shared/dtos/socket-state.type\";\nimport type { IWebsocketAdapter } from \"@/services/websocket-adapter.interface\";\nimport { moonrakerEvent } from \"@/services/moonraker/constants/moonraker.constants\";\nimport { bambuEvent } from \"@/services/bambu/bambu-mqtt.adapter\";\nimport { prusaLinkEvent } from \"@/services/prusa-link/constants/prusalink.constants\";\nimport { printerEvents } from \"@/constants/event.constants\";\nimport type { OctoPrintEventDto } from \"@/services/octoprint/dto/octoprint-event.dto\";\nimport { z } from \"zod\";\n\n// Use a large number range for test printer IDs to avoid conflicts with real printers\nconst TEST_PRINTER_ID_BASE = 100000;\nlet testPrinterIdCounter = 0;\n\nexport class TestPrinterSocketStore {\n testSocket?: IWebsocketAdapter;\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly socketFactory: SocketFactory,\n private readonly socketIoGateway: SocketIoGateway,\n private readonly eventEmitter2: EventEmitter2,\n ) {\n this.logger = loggerFactory(TestPrinterSocketStore.name);\n }\n\n async setupTestPrinter(correlationToken: string, printer: z.infer<typeof createTestPrinterSchema>): Promise<void> {\n if (this.testSocket) {\n this.testSocket.close();\n delete this.testSocket;\n }\n\n const validatedData = await validateInput(printer, createTestPrinterSchema);\n validatedData.enabled = true;\n\n // Generate a unique test printer ID\n const testPrinterId = TEST_PRINTER_ID_BASE + ++testPrinterIdCounter;\n\n // Create a new socket if it doesn't exist\n this.testSocket = this.socketFactory.createInstance(printer.printerType);\n\n // Reset the socket credentials before (re-)connecting\n this.testSocket.registerCredentials({\n printerId: testPrinterId,\n loginDto: {\n apiKey: printer.apiKey,\n username: printer.username ?? undefined,\n password: printer.password ?? undefined,\n printerURL: printer.printerURL,\n printerType: printer.printerType,\n },\n });\n\n const testEvents = [\n octoPrintEvent(WsMessage.WS_STATE_UPDATED),\n octoPrintEvent(WsMessage.API_STATE_UPDATED),\n octoPrintEvent(WsMessage.WS_CLOSED),\n octoPrintEvent(WsMessage.WS_OPENED),\n octoPrintEvent(WsMessage.WS_ERROR),\n moonrakerEvent(WsMessage.WS_STATE_UPDATED),\n moonrakerEvent(WsMessage.API_STATE_UPDATED),\n moonrakerEvent(WsMessage.WS_CLOSED),\n moonrakerEvent(WsMessage.WS_OPENED),\n moonrakerEvent(WsMessage.WS_ERROR),\n bambuEvent(WsMessage.WS_STATE_UPDATED),\n bambuEvent(WsMessage.API_STATE_UPDATED),\n bambuEvent(WsMessage.WS_CLOSED),\n bambuEvent(WsMessage.WS_OPENED),\n bambuEvent(WsMessage.WS_ERROR),\n prusaLinkEvent(WsMessage.WS_STATE_UPDATED),\n prusaLinkEvent(WsMessage.API_STATE_UPDATED),\n prusaLinkEvent(WsMessage.WS_CLOSED),\n prusaLinkEvent(WsMessage.WS_OPENED),\n prusaLinkEvent(WsMessage.WS_ERROR),\n ];\n const listener = ({ event, payload, printerId }: OctoPrintEventDto) => {\n if (printerId !== testPrinterId) {\n return;\n }\n this.socketIoGateway.send(\"test-printer-state\", {\n event,\n payload,\n correlationToken,\n });\n };\n testEvents.forEach((te) => {\n this.eventEmitter2.on(te, listener);\n });\n\n try {\n this.logger.log(\"Test API calls for authentication and session\");\n await this.testSocket.setupSocketSession();\n\n this.logger.log(\"Test socket connection started\");\n const promise = new Promise(async (resolve, reject) => {\n if (!this.testSocket) {\n this.logger.error(\"Aborting test as testSocket is undefined.\");\n return;\n }\n this.testSocket.open();\n for await (const _startTime of setInterval(100)) {\n if (!this.testSocket) {\n this.logger.warn(\"Test without socket, rejecting\");\n reject(new Error(\"Test without socket, rejecting\"));\n return;\n }\n if (this.testSocket.socketState === SOCKET_STATE.authenticated) {\n this.logger.log(\"Test completed successfully, resolving\");\n resolve(true);\n break;\n }\n }\n });\n\n await Promise.race([promise, setTimeout(AppConstants.defaultWebsocketHandshakeTimeout)]);\n\n this.logger.log(\"Test finalized\");\n } catch (e) {\n this.logger.error(`Test harness error ${errorSummary(e)}`);\n captureException(e);\n } finally {\n // Ensure that the printer does not re-register itself after being purged\n this.testSocket.disallowEmittingEvents();\n\n if (this.testSocket) {\n this.testSocket.close();\n }\n this.eventEmitter2.emit(printerEvents.printersDeleted, {\n printerIds: [testPrinterId],\n });\n delete this.testSocket;\n testEvents.forEach((te) => {\n this.eventEmitter2.off(te, listener);\n });\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"boot.task.js","names":[],"sources":["../../src/tasks/boot.task.ts"],"sourcesContent":["import { DITokens } from \"@/container.tokens\";\nimport { AppConstants } from \"@/server.constants\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { TaskManagerService } from \"@/services/task-manager.service\";\nimport { ServerTasks } from \"@/tasks\";\nimport { MulterService } from \"@/services/core/multer.service\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport { FloorStore } from \"@/state/floor.store\";\nimport { ConfigService } from \"@/services/core/config.service\";\nimport { PrinterSocketStore } from \"@/state/printer-socket.store\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { PrinterThumbnailCache } from \"@/state/printer-thumbnail.cache\";\nimport { TaskService } from \"@/services/interfaces/task.interfaces\";\nimport { RoleService } from \"@/services/orm/role.service\";\nimport { UserService } from \"@/services/orm/user.service\";\nimport { PermissionService } from \"@/services/orm/permission.service\";\nimport type { RoleName } from \"@/constants/authorization.constants\";\nimport { PrintFileDownloaderService } from \"@/services/print-file-downloader.service\";\nimport { FileStorageService } from \"@/services/file-storage.service\";\n\nexport class BootTask implements TaskService {\n logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly taskManagerService: TaskManagerService,\n private readonly settingsStore: SettingsStore,\n private readonly multerService: MulterService,\n private readonly printerSocketStore: PrinterSocketStore,\n private readonly permissionService: PermissionService,\n private readonly roleService: RoleService,\n private readonly userService: UserService,\n private readonly floorStore: FloorStore,\n private readonly configService: ConfigService,\n private readonly typeormService: TypeormService,\n private readonly printerThumbnailCache: PrinterThumbnailCache,\n private readonly printFileDownloaderService: PrintFileDownloaderService,\n private readonly fileStorageService: FileStorageService,\n ) {\n this.logger = loggerFactory(BootTask.name);\n }\n\n async runOnce() {\n // To cope with retries after failures we register this task - disabled\n this.taskManagerService.registerJobOrTask(ServerTasks.SERVER_BOOT_TASK);\n\n this.logger.log(\"Running boot task once.\");\n await this.run();\n }\n\n async run() {\n await this.typeormService.createConnection();\n\n this.logger.log(\"Ensuring file storage directories exist\");\n await this.fileStorageService.ensureStorageDirectories();\n\n this.logger.log(\"Loading and synchronizing Server Settings\");\n await this.settingsStore.loadSettings();\n\n this.logger.log(\"Synchronizing user permission and roles definition\");\n await this.permissionService.syncPermissions();\n await this.roleService.syncRoles();\n\n const isDemoMode = this.configService.isDemoMode();\n if (isDemoMode) {\n this.logger.warn(`Starting in demo mode due to ${AppConstants.OVERRIDE_IS_DEMO_MODE}`);\n await this.createOrUpdateDemoAccount();\n this.logger.warn(\n `Setting loginRequired=true and registration=false due to ${AppConstants.OVERRIDE_IS_DEMO_MODE}`,\n );\n await this.settingsStore.setLoginRequired(true);\n await this.settingsStore.setRegistrationEnabled(false);\n } else {\n const loginRequired = this.configService.get<string | null>(AppConstants.OVERRIDE_LOGIN_REQUIRED, null);\n if (loginRequired !== null) {\n this.logger.warn(`Setting login required due to ${AppConstants.OVERRIDE_LOGIN_REQUIRED}`);\n await this.settingsStore.setLoginRequired(loginRequired === \"true\");\n }\n\n const registrationEnabled = this.configService.get(AppConstants.OVERRIDE_REGISTRATION_ENABLED, null);\n if (registrationEnabled !== null) {\n this.logger.warn(`Setting registration enabled due to ${AppConstants.OVERRIDE_REGISTRATION_ENABLED}`);\n await this.settingsStore.setRegistrationEnabled(registrationEnabled === \"true\");\n }\n }\n\n const overrideJwtSecret = this.configService.get<string>(AppConstants.OVERRIDE_JWT_SECRET);\n const overrideJwtExpiresIn = this.configService.get<string>(AppConstants.OVERRIDE_JWT_EXPIRES_IN);\n await this.settingsStore.persistOptionalCredentialSettings(overrideJwtSecret, overrideJwtExpiresIn);\n\n this.logger.log(\"Clearing upload folder\");\n this.multerService.clearUploadsFolder();\n this.logger.log(\"Loading printer sockets\");\n await this.printerSocketStore.loadPrinterSockets(); // New sockets\n this.logger.log(\"Loading floor store\");\n await this.floorStore.loadStore();\n this.logger.log(\"Loading printer thumbnail cache\");\n await this.printerThumbnailCache.loadCache();\n const length = await this.printerThumbnailCache.getAllValues();\n this.logger.log(`Loaded ${length.length} thumbnail(s)`);\n\n if (process.env.SAFEMODE_ENABLED === \"true\") {\n this.logger.warn(\"Starting in safe mode due to SAFEMODE_ENABLED\");\n } else {\n ServerTasks.BOOT_TASKS.forEach((task) => {\n this.taskManagerService.registerJobOrTask(task);\n });\n }\n\n // Success so we disable this task\n this.taskManagerService.disableJob(DITokens.bootTask, false);\n }\n\n async createOrUpdateDemoAccount() {\n const demoUsername = this.configService.get(\n AppConstants.OVERRIDE_DEMO_USERNAME,\n AppConstants.DEFAULT_DEMO_USERNAME,\n ) as string;\n const demoPassword = this.configService.get(\n AppConstants.OVERRIDE_DEMO_PASSWORD,\n AppConstants.DEFAULT_DEMO_PASSWORD,\n ) as string;\n const demoRole = this.configService.get(\n AppConstants.OVERRIDE_DEMO_ROLE,\n AppConstants.DEFAULT_DEMO_ROLE,\n ) as RoleName;\n\n const demoUserId = await this.userService.getDemoUserId();\n if (!demoUserId) {\n await this.userService.register({\n username: demoUsername,\n password: demoPassword,\n isDemoUser: true,\n isVerified: true,\n isRootUser: false,\n needsPasswordChange: false,\n roles: [demoRole],\n });\n this.logger.log(\"Created demo account\");\n } else {\n await this.userService.setVerifiedById(demoUserId, true);\n await this.userService.setIsRootUserById(demoUserId, false);\n await this.userService.updatePasswordUnsafeByUsername(demoUsername, demoPassword);\n await this.userService.setUserRoles(demoUserId, [demoRole]);\n this.logger.log(\"Updated demo account\");\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"boot.task.js","names":[],"sources":["../../src/tasks/boot.task.ts"],"sourcesContent":["import { DITokens } from \"@/container.tokens\";\nimport { AppConstants } from \"@/server.constants\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { TaskManagerService } from \"@/services/task-manager.service\";\nimport { ServerTasks } from \"@/tasks\";\nimport { MulterService } from \"@/services/core/multer.service\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport { FloorStore } from \"@/state/floor.store\";\nimport { ConfigService } from \"@/services/core/config.service\";\nimport { PrinterSocketStore } from \"@/state/printer-socket.store\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { PrinterThumbnailCache } from \"@/state/printer-thumbnail.cache\";\nimport { TaskService } from \"@/services/interfaces/task.interfaces\";\nimport { RoleService } from \"@/services/orm/role.service\";\nimport { UserService } from \"@/services/orm/user.service\";\nimport { PermissionService } from \"@/services/orm/permission.service\";\nimport type { RoleName } from \"@/constants/authorization.constants\";\nimport { PrintFileDownloaderService } from \"@/services/print-file-downloader.service\";\nimport { FileStorageService } from \"@/services/file-storage.service\";\n\nexport class BootTask implements TaskService {\n logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly taskManagerService: TaskManagerService,\n private readonly settingsStore: SettingsStore,\n private readonly multerService: MulterService,\n private readonly printerSocketStore: PrinterSocketStore,\n private readonly permissionService: PermissionService,\n private readonly roleService: RoleService,\n private readonly userService: UserService,\n private readonly floorStore: FloorStore,\n private readonly configService: ConfigService,\n private readonly typeormService: TypeormService,\n private readonly printerThumbnailCache: PrinterThumbnailCache,\n private readonly printFileDownloaderService: PrintFileDownloaderService,\n private readonly fileStorageService: FileStorageService,\n ) {\n this.logger = loggerFactory(BootTask.name);\n }\n\n async runOnce() {\n // To cope with retries after failures we register this task - disabled\n this.taskManagerService.registerJobOrTask(ServerTasks.SERVER_BOOT_TASK);\n\n this.logger.log(\"Running boot task once.\");\n await this.run();\n }\n\n async run() {\n await this.typeormService.createConnection();\n\n this.logger.log(\"Ensuring file storage directories exist\");\n await this.fileStorageService.ensureStorageDirectories();\n\n this.logger.log(\"Loading and synchronizing Server Settings\");\n await this.settingsStore.loadSettings();\n\n this.logger.log(\"Synchronizing user permission and roles definition\");\n await this.permissionService.syncPermissions();\n await this.roleService.syncRoles();\n\n const isDemoMode = this.configService.isDemoMode();\n if (isDemoMode) {\n this.logger.warn(`Starting in demo mode due to ${AppConstants.OVERRIDE_IS_DEMO_MODE}`);\n await this.createOrUpdateDemoAccount();\n this.logger.warn(\n `Setting loginRequired=true and registration=false due to ${AppConstants.OVERRIDE_IS_DEMO_MODE}`,\n );\n await this.settingsStore.setLoginRequired(true);\n await this.settingsStore.setRegistrationEnabled(false);\n } else {\n const loginRequired = this.configService.get<string | null>(AppConstants.OVERRIDE_LOGIN_REQUIRED, null);\n if (loginRequired !== null) {\n this.logger.warn(`Setting login required due to ${AppConstants.OVERRIDE_LOGIN_REQUIRED}`);\n await this.settingsStore.setLoginRequired(loginRequired === \"true\");\n }\n\n const registrationEnabled = this.configService.get(AppConstants.OVERRIDE_REGISTRATION_ENABLED, null);\n if (registrationEnabled !== null) {\n this.logger.warn(`Setting registration enabled due to ${AppConstants.OVERRIDE_REGISTRATION_ENABLED}`);\n await this.settingsStore.setRegistrationEnabled(registrationEnabled === \"true\");\n }\n }\n\n const overrideJwtSecret = this.configService.get<string>(AppConstants.OVERRIDE_JWT_SECRET);\n const overrideJwtExpiresIn = this.configService.get<string>(AppConstants.OVERRIDE_JWT_EXPIRES_IN);\n await this.settingsStore.persistOptionalCredentialSettings(overrideJwtSecret, overrideJwtExpiresIn);\n\n this.logger.log(\"Clearing upload folder\");\n this.multerService.clearUploadsFolder();\n this.logger.log(\"Loading printer sockets\");\n await this.printerSocketStore.loadPrinterSockets(); // New sockets\n this.logger.log(\"Loading floor store\");\n await this.floorStore.loadStore();\n this.logger.log(\"Loading printer thumbnail cache\");\n await this.printerThumbnailCache.loadCache();\n const length = await this.printerThumbnailCache.getAllValues();\n this.logger.log(`Loaded ${length.length} thumbnail(s)`);\n\n if (process.env.SAFEMODE_ENABLED === \"true\") {\n this.logger.warn(\"Starting in safe mode due to SAFEMODE_ENABLED\");\n } else {\n ServerTasks.BOOT_TASKS.forEach((task) => {\n this.taskManagerService.registerJobOrTask(task);\n });\n }\n\n // Success so we disable this task\n this.taskManagerService.disableJob(DITokens.bootTask, false);\n }\n\n async createOrUpdateDemoAccount() {\n const demoUsername = this.configService.get(\n AppConstants.OVERRIDE_DEMO_USERNAME,\n AppConstants.DEFAULT_DEMO_USERNAME,\n ) as string;\n const demoPassword = this.configService.get(\n AppConstants.OVERRIDE_DEMO_PASSWORD,\n AppConstants.DEFAULT_DEMO_PASSWORD,\n ) as string;\n const demoRole = this.configService.get(\n AppConstants.OVERRIDE_DEMO_ROLE,\n AppConstants.DEFAULT_DEMO_ROLE,\n ) as RoleName;\n\n const demoUserId = await this.userService.getDemoUserId();\n if (!demoUserId) {\n await this.userService.register({\n username: demoUsername,\n password: demoPassword,\n isDemoUser: true,\n isVerified: true,\n isRootUser: false,\n needsPasswordChange: false,\n roles: [demoRole],\n });\n this.logger.log(\"Created demo account\");\n } else {\n await this.userService.setVerifiedById(demoUserId, true);\n await this.userService.setIsRootUserById(demoUserId, false);\n await this.userService.updatePasswordUnsafeByUsername(demoUsername, demoPassword);\n await this.userService.setUserRoles(demoUserId, [demoRole]);\n this.logger.log(\"Updated demo account\");\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"client-bundle.task.js","names":[],"sources":["../../src/tasks/client-bundle.task.ts"],"sourcesContent":["import { AppConstants } from \"@/server.constants\";\nimport { ClientBundleService } from \"@/services/core/client-bundle.service\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\n\nexport class ClientDistDownloadTask {\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly clientBundleService: ClientBundleService,\n ) {\n this.logger = loggerFactory(ClientDistDownloadTask.name);\n }\n\n async run() {\n const result = await this.clientBundleService.shouldUpdateWithReason(false, AppConstants.defaultClientMinimum);\n if (!result.shouldUpdate) {\n this.logger.log(`Client bundle update skipped. Reason: ${result.reason}`);\n return;\n }\n\n this.logger.log(`Client bundle update required. Reason for updating: ${result.reason}`);\n\n await this.clientBundleService.downloadClientUpdate(AppConstants.defaultClientMinimum);\n }\n}\n"],"mappings":";;AAKA,IAAa,yBAAb,MAAa,uBAAuB;CAClC;CAEA,YACE,eACA,qBACA;AADiB,OAAA,sBAAA;AAEjB,OAAK,SAAS,cAAc,uBAAuB,KAAK;;CAG1D,MAAM,MAAM;EACV,MAAM,SAAS,MAAM,KAAK,oBAAoB,uBAAuB,OAAO,aAAa,qBAAqB;AAC9G,MAAI,CAAC,OAAO,cAAc;AACxB,QAAK,OAAO,IAAI,yCAAyC,OAAO,SAAS;AACzE;;AAGF,OAAK,OAAO,IAAI,uDAAuD,OAAO,SAAS;AAEvF,QAAM,KAAK,oBAAoB,qBAAqB,aAAa,qBAAqB"}
1
+ {"version":3,"file":"client-bundle.task.js","names":[],"sources":["../../src/tasks/client-bundle.task.ts"],"sourcesContent":["import { AppConstants } from \"@/server.constants\";\nimport { ClientBundleService } from \"@/services/core/client-bundle.service\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\n\nexport class ClientDistDownloadTask {\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly clientBundleService: ClientBundleService,\n ) {\n this.logger = loggerFactory(ClientDistDownloadTask.name);\n }\n\n async run() {\n const result = await this.clientBundleService.shouldUpdateWithReason(false, AppConstants.defaultClientMinimum);\n if (!result.shouldUpdate) {\n this.logger.log(`Client bundle update skipped. Reason: ${result.reason}`);\n return;\n }\n\n this.logger.log(`Client bundle update required. Reason for updating: ${result.reason}`);\n\n await this.clientBundleService.downloadClientUpdate(AppConstants.defaultClientMinimum);\n }\n}\n"],"mappings":";;AAKA,IAAa,yBAAb,MAAa,uBAAuB;CAClC;CAEA,YACE,eACA,qBACA;EADiB,KAAA,sBAAA;EAEjB,KAAK,SAAS,cAAc,uBAAuB,KAAK;;CAG1D,MAAM,MAAM;EACV,MAAM,SAAS,MAAM,KAAK,oBAAoB,uBAAuB,OAAO,aAAa,qBAAqB;EAC9G,IAAI,CAAC,OAAO,cAAc;GACxB,KAAK,OAAO,IAAI,yCAAyC,OAAO,SAAS;GACzE;;EAGF,KAAK,OAAO,IAAI,uDAAuD,OAAO,SAAS;EAEvF,MAAM,KAAK,oBAAoB,qBAAqB,aAAa,qBAAqB"}
@@ -1 +1 @@
1
- {"version":3,"file":"print-job-analysis.task.js","names":[],"sources":["../../src/tasks/print-job-analysis.task.ts"],"sourcesContent":["import { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { TaskService } from \"@/services/interfaces/task.interfaces\";\nimport { PrintJobService } from \"@/services/orm/print-job.service\";\nimport { FileAnalysisService } from \"@/services/file-analysis.service\";\nimport { FileStorageService } from \"@/services/file-storage.service\";\nimport { Repository } from \"typeorm\";\nimport { PrintJob } from \"@/entities/print-job.entity\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\n\n/**\n * Background task to analyze pending print jobs\n * Runs periodically to extract metadata from uploaded files\n */\nexport class PrintJobAnalysisTask implements TaskService {\n logger: LoggerService;\n private readonly printJobRepository: Repository<PrintJob>;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly printJobService: PrintJobService,\n private readonly fileAnalysisService: FileAnalysisService,\n private readonly fileStorageService: FileStorageService,\n typeormService: TypeormService,\n ) {\n this.logger = loggerFactory(PrintJobAnalysisTask.name);\n this.printJobRepository = typeormService.getDataSource().getRepository(PrintJob);\n }\n\n async run() {\n try {\n // Find all jobs that need analysis\n const pendingJobs = await this.printJobRepository.find({\n where: [\n { analysisState: \"NOT_ANALYZED\", status: \"PENDING\" },\n { analysisState: \"ANALYZING\" }, // Retry stuck analyzing jobs\n ],\n take: 10, // Process up to 10 jobs per run\n });\n\n if (pendingJobs.length === 0) {\n return;\n }\n\n this.logger.log(`Found ${pendingJobs.length} print job(s) to analyze`);\n\n for (const job of pendingJobs) {\n try {\n await this.analyzeJob(job);\n } catch (error) {\n this.logger.error(`Failed to analyze job ${job.id}: ${job.fileName}`, error);\n\n // Mark as failed\n job.analysisState = \"FAILED\";\n job.statusReason = `Analysis failed: ${error instanceof Error ? error.message : \"Unknown error\"}`;\n await this.printJobRepository.save(job);\n }\n }\n\n this.logger.log(`Completed analysis of ${pendingJobs.length} print job(s)`);\n } catch (error) {\n this.logger.error(\"Failed to run print job analysis task\", error);\n }\n }\n\n private async analyzeJob(job: PrintJob): Promise<void> {\n this.logger.log(`Analyzing print job ${job.id}: ${job.fileName}`);\n\n // Mark as analyzing\n job.analysisState = \"ANALYZING\";\n await this.printJobRepository.save(job);\n\n if (!job.fileStorageId) {\n throw new Error(\"Job has no fileStorageId - cannot analyze\");\n }\n\n // Check if metadata JSON already exists (cached analysis)\n const cachedMetadata = await this.fileStorageService.loadMetadata(job.fileStorageId);\n\n let metadata;\n let thumbnails: any[] = [];\n\n if (cachedMetadata) {\n // Use cached metadata from JSON file (fast path - no re-analysis)\n this.logger.log(`Using cached metadata for job ${job.id} (storageId: ${job.fileStorageId})`);\n metadata = cachedMetadata;\n thumbnails = []; // Thumbnails not stored in JSON yet\n } else {\n // No cache - analyze the file\n const filePath = await this.resolveFilePath(job);\n\n if (!filePath) {\n throw new Error(\"File path could not be resolved\");\n }\n\n // Check if file exists\n const exists = await this.fileAnalysisService.needsAnalysis(filePath);\n if (!exists) {\n throw new Error(`File not found: ${filePath}`);\n }\n\n // Analyze the file\n const analysisResult = await this.fileAnalysisService.analyzeFile(filePath);\n metadata = analysisResult.metadata;\n thumbnails = analysisResult.thumbnails;\n\n // Save thumbnails\n let thumbnailMetadata: any[] = [];\n if (thumbnails && thumbnails.length > 0) {\n thumbnailMetadata = await this.fileStorageService.saveThumbnails(job.fileStorageId, thumbnails);\n this.logger.log(`Saved ${thumbnailMetadata.length} thumbnail(s) for job ${job.id}`);\n }\n\n // Cache metadata to JSON with thumbnail index\n const fileHash = job.fileHash || undefined;\n await this.fileStorageService.saveMetadata(\n job.fileStorageId,\n metadata,\n fileHash,\n job.fileName,\n thumbnailMetadata,\n );\n this.logger.log(`Cached metadata JSON for job ${job.id}`);\n }\n\n // Update job with metadata through service (emits events)\n await this.printJobService.handleFileAnalyzed(job.id, metadata, thumbnails);\n\n this.logger.log(`Successfully analyzed job ${job.id}: ${job.fileName}`);\n }\n\n private async resolveFilePath(job: PrintJob): Promise<string | null> {\n // If job has fileStorageId, get file from storage\n if (job.fileStorageId) {\n try {\n const exists = await this.fileStorageService.fileExists(job.fileStorageId);\n if (!exists) {\n this.logger.warn(`File ${job.fileStorageId} not found in storage for job ${job.id}`);\n return null;\n }\n\n return this.fileStorageService.getFilePath(job.fileStorageId);\n } catch (error) {\n this.logger.error(`Failed to resolve file path for job ${job.id}: ${error}`);\n return null;\n }\n }\n\n // If no fileStorageId, we can't analyze (file is on printer, not locally)\n this.logger.debug(`Job ${job.id} has no fileStorageId - cannot analyze remotely stored file`);\n return null;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"print-job-analysis.task.js","names":[],"sources":["../../src/tasks/print-job-analysis.task.ts"],"sourcesContent":["import { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { TaskService } from \"@/services/interfaces/task.interfaces\";\nimport { PrintJobService } from \"@/services/orm/print-job.service\";\nimport { FileAnalysisService } from \"@/services/file-analysis.service\";\nimport { FileStorageService } from \"@/services/file-storage.service\";\nimport { Repository } from \"typeorm\";\nimport { PrintJob } from \"@/entities/print-job.entity\";\nimport { TypeormService } from \"@/services/typeorm/typeorm.service\";\n\n/**\n * Background task to analyze pending print jobs\n * Runs periodically to extract metadata from uploaded files\n */\nexport class PrintJobAnalysisTask implements TaskService {\n logger: LoggerService;\n private readonly printJobRepository: Repository<PrintJob>;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly printJobService: PrintJobService,\n private readonly fileAnalysisService: FileAnalysisService,\n private readonly fileStorageService: FileStorageService,\n typeormService: TypeormService,\n ) {\n this.logger = loggerFactory(PrintJobAnalysisTask.name);\n this.printJobRepository = typeormService.getDataSource().getRepository(PrintJob);\n }\n\n async run() {\n try {\n // Find all jobs that need analysis\n const pendingJobs = await this.printJobRepository.find({\n where: [\n { analysisState: \"NOT_ANALYZED\", status: \"PENDING\" },\n { analysisState: \"ANALYZING\" }, // Retry stuck analyzing jobs\n ],\n take: 10, // Process up to 10 jobs per run\n });\n\n if (pendingJobs.length === 0) {\n return;\n }\n\n this.logger.log(`Found ${pendingJobs.length} print job(s) to analyze`);\n\n for (const job of pendingJobs) {\n try {\n await this.analyzeJob(job);\n } catch (error) {\n this.logger.error(`Failed to analyze job ${job.id}: ${job.fileName}`, error);\n\n // Mark as failed\n job.analysisState = \"FAILED\";\n job.statusReason = `Analysis failed: ${error instanceof Error ? error.message : \"Unknown error\"}`;\n await this.printJobRepository.save(job);\n }\n }\n\n this.logger.log(`Completed analysis of ${pendingJobs.length} print job(s)`);\n } catch (error) {\n this.logger.error(\"Failed to run print job analysis task\", error);\n }\n }\n\n private async analyzeJob(job: PrintJob): Promise<void> {\n this.logger.log(`Analyzing print job ${job.id}: ${job.fileName}`);\n\n // Mark as analyzing\n job.analysisState = \"ANALYZING\";\n await this.printJobRepository.save(job);\n\n if (!job.fileStorageId) {\n throw new Error(\"Job has no fileStorageId - cannot analyze\");\n }\n\n // Check if metadata JSON already exists (cached analysis)\n const cachedMetadata = await this.fileStorageService.loadMetadata(job.fileStorageId);\n\n let metadata;\n let thumbnails: any[] = [];\n\n if (cachedMetadata) {\n // Use cached metadata from JSON file (fast path - no re-analysis)\n this.logger.log(`Using cached metadata for job ${job.id} (storageId: ${job.fileStorageId})`);\n metadata = cachedMetadata;\n thumbnails = []; // Thumbnails not stored in JSON yet\n } else {\n // No cache - analyze the file\n const filePath = await this.resolveFilePath(job);\n\n if (!filePath) {\n throw new Error(\"File path could not be resolved\");\n }\n\n // Check if file exists\n const exists = await this.fileAnalysisService.needsAnalysis(filePath);\n if (!exists) {\n throw new Error(`File not found: ${filePath}`);\n }\n\n // Analyze the file\n const analysisResult = await this.fileAnalysisService.analyzeFile(filePath);\n metadata = analysisResult.metadata;\n thumbnails = analysisResult.thumbnails;\n\n // Save thumbnails\n let thumbnailMetadata: any[] = [];\n if (thumbnails && thumbnails.length > 0) {\n thumbnailMetadata = await this.fileStorageService.saveThumbnails(job.fileStorageId, thumbnails);\n this.logger.log(`Saved ${thumbnailMetadata.length} thumbnail(s) for job ${job.id}`);\n }\n\n // Cache metadata to JSON with thumbnail index\n const fileHash = job.fileHash || undefined;\n await this.fileStorageService.saveMetadata(\n job.fileStorageId,\n metadata,\n fileHash,\n job.fileName,\n thumbnailMetadata,\n );\n this.logger.log(`Cached metadata JSON for job ${job.id}`);\n }\n\n // Update job with metadata through service (emits events)\n await this.printJobService.handleFileAnalyzed(job.id, metadata, thumbnails);\n\n this.logger.log(`Successfully analyzed job ${job.id}: ${job.fileName}`);\n }\n\n private async resolveFilePath(job: PrintJob): Promise<string | null> {\n // If job has fileStorageId, get file from storage\n if (job.fileStorageId) {\n try {\n const exists = await this.fileStorageService.fileExists(job.fileStorageId);\n if (!exists) {\n this.logger.warn(`File ${job.fileStorageId} not found in storage for job ${job.id}`);\n return null;\n }\n\n return this.fileStorageService.getFilePath(job.fileStorageId);\n } catch (error) {\n this.logger.error(`Failed to resolve file path for job ${job.id}: ${error}`);\n return null;\n }\n }\n\n // If no fileStorageId, we can't analyze (file is on printer, not locally)\n this.logger.debug(`Job ${job.id} has no fileStorageId - cannot analyze remotely stored file`);\n return null;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"printer-websocket-restore.task.js","names":[],"sources":["../../src/tasks/printer-websocket-restore.task.ts"],"sourcesContent":["import { errorSummary } from \"@/utils/error.utils\";\nimport { PrinterSocketStore } from \"@/state/printer-socket.store\";\nimport { OctoprintClient } from \"@/services/octoprint/octoprint.client\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { captureException } from \"@sentry/node\";\nimport { API_STATE } from \"@/shared/dtos/api-state.type\";\nimport { OctoprintType } from \"@/services/printer-api.interface\";\n\nexport class PrinterWebsocketRestoreTask {\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly printerSocketStore: PrinterSocketStore,\n private readonly octoprintClient: OctoprintClient,\n ) {\n this.logger = loggerFactory(PrinterWebsocketRestoreTask.name);\n }\n\n async run() {\n const existingSockets = this.printerSocketStore.listPrinterSockets();\n for (const socket of existingSockets) {\n if (socket.printerType !== OctoprintType) {\n continue;\n }\n\n try {\n if (socket.isClosedOrAborted()) {\n socket.close();\n socket.resetSocketState();\n this.logger.warn(\"Socket was closed or aborted, manually removing it\");\n continue;\n }\n } catch (e: any) {\n captureException(e);\n this.logger.error(`Resetting WebSocket socket failed for printer with error '${errorSummary(e)}'`, e.stack);\n continue;\n }\n\n // Often due to USB disconnect, not interesting to reconnect unless we perform an API call for verification\n if (\n (socket.apiState !== API_STATE.unset && !socket.lastMessageReceivedTimestamp) ||\n (socket.lastMessageReceivedTimestamp && Date.now() - socket.lastMessageReceivedTimestamp > 10 * 1000)\n ) {\n const result = await this.octoprintClient.getConnection(socket.login);\n\n try {\n if (result.data?.current?.state !== \"Closed\") {\n this.logger.warn(\n `Silence was detected, but the OctoPrint current connection was not closed. Connection state ${result.data?.current?.state}`,\n );\n }\n } catch (e) {\n this.logger.error(`Silence was detected, but Websocket was not closed/aborted and API could not be called`);\n }\n }\n }\n }\n}\n"],"mappings":";;;;;AASA,IAAa,8BAAb,MAAa,4BAA4B;CACvC;CAEA,YACE,eACA,oBACA,iBACA;AAFiB,OAAA,qBAAA;AACA,OAAA,kBAAA;AAEjB,OAAK,SAAS,cAAc,4BAA4B,KAAK;;CAG/D,MAAM,MAAM;EACV,MAAM,kBAAkB,KAAK,mBAAmB,oBAAoB;AACpE,OAAK,MAAM,UAAU,iBAAiB;AACpC,OAAI,OAAO,gBAAA,EACT;AAGF,OAAI;AACF,QAAI,OAAO,mBAAmB,EAAE;AAC9B,YAAO,OAAO;AACd,YAAO,kBAAkB;AACzB,UAAK,OAAO,KAAK,qDAAqD;AACtE;;YAEK,GAAQ;AACf,qBAAiB,EAAE;AACnB,SAAK,OAAO,MAAM,6DAA6D,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM;AAC3G;;AAIF,OACG,OAAO,aAAa,UAAU,SAAS,CAAC,OAAO,gCAC/C,OAAO,gCAAgC,KAAK,KAAK,GAAG,OAAO,+BAA+B,KAAK,KAChG;IACA,MAAM,SAAS,MAAM,KAAK,gBAAgB,cAAc,OAAO,MAAM;AAErE,QAAI;AACF,SAAI,OAAO,MAAM,SAAS,UAAU,SAClC,MAAK,OAAO,KACV,+FAA+F,OAAO,MAAM,SAAS,QACtH;aAEI,GAAG;AACV,UAAK,OAAO,MAAM,yFAAyF"}
1
+ {"version":3,"file":"printer-websocket-restore.task.js","names":[],"sources":["../../src/tasks/printer-websocket-restore.task.ts"],"sourcesContent":["import { errorSummary } from \"@/utils/error.utils\";\nimport { PrinterSocketStore } from \"@/state/printer-socket.store\";\nimport { OctoprintClient } from \"@/services/octoprint/octoprint.client\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { captureException } from \"@sentry/node\";\nimport { API_STATE } from \"@/shared/dtos/api-state.type\";\nimport { OctoprintType } from \"@/services/printer-api.interface\";\n\nexport class PrinterWebsocketRestoreTask {\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly printerSocketStore: PrinterSocketStore,\n private readonly octoprintClient: OctoprintClient,\n ) {\n this.logger = loggerFactory(PrinterWebsocketRestoreTask.name);\n }\n\n async run() {\n const existingSockets = this.printerSocketStore.listPrinterSockets();\n for (const socket of existingSockets) {\n if (socket.printerType !== OctoprintType) {\n continue;\n }\n\n try {\n if (socket.isClosedOrAborted()) {\n socket.close();\n socket.resetSocketState();\n this.logger.warn(\"Socket was closed or aborted, manually removing it\");\n continue;\n }\n } catch (e: any) {\n captureException(e);\n this.logger.error(`Resetting WebSocket socket failed for printer with error '${errorSummary(e)}'`, e.stack);\n continue;\n }\n\n // Often due to USB disconnect, not interesting to reconnect unless we perform an API call for verification\n if (\n (socket.apiState !== API_STATE.unset && !socket.lastMessageReceivedTimestamp) ||\n (socket.lastMessageReceivedTimestamp && Date.now() - socket.lastMessageReceivedTimestamp > 10 * 1000)\n ) {\n const result = await this.octoprintClient.getConnection(socket.login);\n\n try {\n if (result.data?.current?.state !== \"Closed\") {\n this.logger.warn(\n `Silence was detected, but the OctoPrint current connection was not closed. Connection state ${result.data?.current?.state}`,\n );\n }\n } catch (e) {\n this.logger.error(`Silence was detected, but Websocket was not closed/aborted and API could not be called`);\n }\n }\n }\n }\n}\n"],"mappings":";;;;;AASA,IAAa,8BAAb,MAAa,4BAA4B;CACvC;CAEA,YACE,eACA,oBACA,iBACA;EAFiB,KAAA,qBAAA;EACA,KAAA,kBAAA;EAEjB,KAAK,SAAS,cAAc,4BAA4B,KAAK;;CAG/D,MAAM,MAAM;EACV,MAAM,kBAAkB,KAAK,mBAAmB,oBAAoB;EACpE,KAAK,MAAM,UAAU,iBAAiB;GACpC,IAAI,OAAO,gBAAA,GACT;GAGF,IAAI;IACF,IAAI,OAAO,mBAAmB,EAAE;KAC9B,OAAO,OAAO;KACd,OAAO,kBAAkB;KACzB,KAAK,OAAO,KAAK,qDAAqD;KACtE;;YAEK,GAAQ;IACf,iBAAiB,EAAE;IACnB,KAAK,OAAO,MAAM,6DAA6D,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM;IAC3G;;GAIF,IACG,OAAO,aAAa,UAAU,SAAS,CAAC,OAAO,gCAC/C,OAAO,gCAAgC,KAAK,KAAK,GAAG,OAAO,+BAA+B,KAAK,KAChG;IACA,MAAM,SAAS,MAAM,KAAK,gBAAgB,cAAc,OAAO,MAAM;IAErE,IAAI;KACF,IAAI,OAAO,MAAM,SAAS,UAAU,UAClC,KAAK,OAAO,KACV,+FAA+F,OAAO,MAAM,SAAS,QACtH;aAEI,GAAG;KACV,KAAK,OAAO,MAAM,yFAAyF"}
@@ -1 +1 @@
1
- {"version":3,"file":"printer-websocket.task.js","names":[],"sources":["../../src/tasks/printer-websocket.task.ts"],"sourcesContent":["import { PrinterSocketStore } from \"@/state/printer-socket.store\";\n\nexport class PrinterWebsocketTask {\n constructor(private readonly printerSocketStore: PrinterSocketStore) {}\n\n async run() {\n await this.printerSocketStore.reconnectPrinterSockets();\n }\n}\n"],"mappings":";AAEA,IAAa,uBAAb,MAAkC;CAChC,YAAY,oBAAyD;AAAxC,OAAA,qBAAA;;CAE7B,MAAM,MAAM;AACV,QAAM,KAAK,mBAAmB,yBAAyB"}
1
+ {"version":3,"file":"printer-websocket.task.js","names":[],"sources":["../../src/tasks/printer-websocket.task.ts"],"sourcesContent":["import { PrinterSocketStore } from \"@/state/printer-socket.store\";\n\nexport class PrinterWebsocketTask {\n constructor(private readonly printerSocketStore: PrinterSocketStore) {}\n\n async run() {\n await this.printerSocketStore.reconnectPrinterSockets();\n }\n}\n"],"mappings":";AAEA,IAAa,uBAAb,MAAkC;CAChC,YAAY,oBAAyD;EAAxC,KAAA,qBAAA;;CAE7B,MAAM,MAAM;EACV,MAAM,KAAK,mBAAmB,yBAAyB"}
@@ -1 +1 @@
1
- {"version":3,"file":"socketio.task.js","names":[],"sources":["../../src/tasks/socketio.task.ts"],"sourcesContent":["import { SocketIoGateway, IO_MESSAGES } from \"@/state/socket-io.gateway\";\nimport { socketIoConnectedEvent } from \"@/constants/event.constants\";\nimport { PrinterSocketStore } from \"@/state/printer-socket.store\";\nimport { PrinterEventsCache } from \"@/state/printer-events.cache\";\nimport { FloorStore } from \"@/state/floor.store\";\nimport { FileUploadTrackerCache } from \"@/state/file-upload-tracker.cache\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { PrinterCache } from \"@/state/printer.cache\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\n\nexport class SocketIoTask {\n logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly socketIoGateway: SocketIoGateway,\n private readonly floorStore: FloorStore,\n private readonly printerSocketStore: PrinterSocketStore,\n private readonly printerEventsCache: PrinterEventsCache,\n private readonly printerCache: PrinterCache,\n private readonly fileUploadTrackerCache: FileUploadTrackerCache,\n private readonly eventEmitter2: EventEmitter2,\n ) {\n this.logger = loggerFactory(SocketIoTask.name);\n\n this.eventEmitter2.on(socketIoConnectedEvent, async () => {\n await this.sendUpdate();\n });\n }\n\n async run() {\n await this.sendUpdate();\n }\n\n async sendUpdate() {\n const floors = await this.floorStore.listCache();\n const printers = await this.printerCache.listCachedPrinters(true);\n const socketStates = this.printerSocketStore.getSocketStatesById();\n const printerEvents = await this.printerEventsCache.getAllKeyValues();\n const trackedUploads = this.fileUploadTrackerCache.getUploads();\n\n const socketIoData = {\n printers,\n floors,\n socketStates,\n printerEvents,\n trackedUploads,\n };\n\n this.socketIoGateway.send(IO_MESSAGES.Update, socketIoData);\n }\n}\n"],"mappings":";;;AAWA,IAAa,eAAb,MAAa,aAAa;CACxB;CAEA,YACE,eACA,iBACA,YACA,oBACA,oBACA,cACA,wBACA,eACA;AAPiB,OAAA,kBAAA;AACA,OAAA,aAAA;AACA,OAAA,qBAAA;AACA,OAAA,qBAAA;AACA,OAAA,eAAA;AACA,OAAA,yBAAA;AACA,OAAA,gBAAA;AAEjB,OAAK,SAAS,cAAc,aAAa,KAAK;AAE9C,OAAK,cAAc,GAAG,wBAAwB,YAAY;AACxD,SAAM,KAAK,YAAY;IACvB;;CAGJ,MAAM,MAAM;AACV,QAAM,KAAK,YAAY;;CAGzB,MAAM,aAAa;EACjB,MAAM,SAAS,MAAM,KAAK,WAAW,WAAW;EAMhD,MAAM,eAAe;GACnB,UAAA,MANqB,KAAK,aAAa,mBAAmB,KAAK;GAO/D;GACA,cAPmB,KAAK,mBAAmB,qBAO/B;GACZ,eAAA,MAP0B,KAAK,mBAAmB,iBAAiB;GAQnE,gBAPqB,KAAK,uBAAuB,YAOnC;GACf;AAED,OAAK,gBAAgB,KAAK,YAAY,QAAQ,aAAa"}
1
+ {"version":3,"file":"socketio.task.js","names":[],"sources":["../../src/tasks/socketio.task.ts"],"sourcesContent":["import { SocketIoGateway, IO_MESSAGES } from \"@/state/socket-io.gateway\";\nimport { socketIoConnectedEvent } from \"@/constants/event.constants\";\nimport { PrinterSocketStore } from \"@/state/printer-socket.store\";\nimport { PrinterEventsCache } from \"@/state/printer-events.cache\";\nimport { FloorStore } from \"@/state/floor.store\";\nimport { FileUploadTrackerCache } from \"@/state/file-upload-tracker.cache\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { PrinterCache } from \"@/state/printer.cache\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\n\nexport class SocketIoTask {\n logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly socketIoGateway: SocketIoGateway,\n private readonly floorStore: FloorStore,\n private readonly printerSocketStore: PrinterSocketStore,\n private readonly printerEventsCache: PrinterEventsCache,\n private readonly printerCache: PrinterCache,\n private readonly fileUploadTrackerCache: FileUploadTrackerCache,\n private readonly eventEmitter2: EventEmitter2,\n ) {\n this.logger = loggerFactory(SocketIoTask.name);\n\n this.eventEmitter2.on(socketIoConnectedEvent, async () => {\n await this.sendUpdate();\n });\n }\n\n async run() {\n await this.sendUpdate();\n }\n\n async sendUpdate() {\n const floors = await this.floorStore.listCache();\n const printers = await this.printerCache.listCachedPrinters(true);\n const socketStates = this.printerSocketStore.getSocketStatesById();\n const printerEvents = await this.printerEventsCache.getAllKeyValues();\n const trackedUploads = this.fileUploadTrackerCache.getUploads();\n\n const socketIoData = {\n printers,\n floors,\n socketStates,\n printerEvents,\n trackedUploads,\n };\n\n this.socketIoGateway.send(IO_MESSAGES.Update, socketIoData);\n }\n}\n"],"mappings":";;;AAWA,IAAa,eAAb,MAAa,aAAa;CACxB;CAEA,YACE,eACA,iBACA,YACA,oBACA,oBACA,cACA,wBACA,eACA;EAPiB,KAAA,kBAAA;EACA,KAAA,aAAA;EACA,KAAA,qBAAA;EACA,KAAA,qBAAA;EACA,KAAA,eAAA;EACA,KAAA,yBAAA;EACA,KAAA,gBAAA;EAEjB,KAAK,SAAS,cAAc,aAAa,KAAK;EAE9C,KAAK,cAAc,GAAG,wBAAwB,YAAY;GACxD,MAAM,KAAK,YAAY;IACvB;;CAGJ,MAAM,MAAM;EACV,MAAM,KAAK,YAAY;;CAGzB,MAAM,aAAa;EACjB,MAAM,SAAS,MAAM,KAAK,WAAW,WAAW;EAMhD,MAAM,eAAe;GACnB,UAAA,MANqB,KAAK,aAAa,mBAAmB,KAAK;GAO/D;GACA,cAPmB,KAAK,mBAAmB,qBAO/B;GACZ,eAAA,MAP0B,KAAK,mBAAmB,iBAAiB;GAQnE,gBAPqB,KAAK,uBAAuB,YAOnC;GACf;EAED,KAAK,gBAAgB,KAAK,YAAY,QAAQ,aAAa"}
@@ -1 +1 @@
1
- {"version":3,"file":"software-update.task.js","names":[],"sources":["../../src/tasks/software-update.task.ts"],"sourcesContent":["import { ServerReleaseService } from \"@/services/core/server-release.service\";\n\nexport class SoftwareUpdateTask {\n constructor(private readonly serverReleaseService: ServerReleaseService) {}\n\n async run() {\n await this.serverReleaseService.syncLatestRelease();\n this.serverReleaseService.logServerVersionState();\n }\n}\n"],"mappings":";AAEA,IAAa,qBAAb,MAAgC;CAC9B,YAAY,sBAA6D;AAA5C,OAAA,uBAAA;;CAE7B,MAAM,MAAM;AACV,QAAM,KAAK,qBAAqB,mBAAmB;AACnD,OAAK,qBAAqB,uBAAuB"}
1
+ {"version":3,"file":"software-update.task.js","names":[],"sources":["../../src/tasks/software-update.task.ts"],"sourcesContent":["import { ServerReleaseService } from \"@/services/core/server-release.service\";\n\nexport class SoftwareUpdateTask {\n constructor(private readonly serverReleaseService: ServerReleaseService) {}\n\n async run() {\n await this.serverReleaseService.syncLatestRelease();\n this.serverReleaseService.logServerVersionState();\n }\n}\n"],"mappings":";AAEA,IAAa,qBAAb,MAAgC;CAC9B,YAAY,sBAA6D;EAA5C,KAAA,uBAAA;;CAE7B,MAAM,MAAM;EACV,MAAM,KAAK,qBAAqB,mBAAmB;EACnD,KAAK,qBAAqB,uBAAuB"}
package/dist/tasks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tasks.js","names":["TaskPresets"],"sources":["../src/tasks.ts"],"sourcesContent":["import { TASK_PRESETS as TaskPresets } from \"./task.presets\";\nimport { DITokens } from \"./container.tokens\";\nimport { TimingPreset } from \"@/services/interfaces/task.interfaces\";\n\n/**\n * Register a task with a preset and timing (run immediate does not retry in case of failure)\n */\nexport function registerTask(task: any, preset: TimingPreset, milliseconds = 0, runImmediately = false) {\n let timingPreset = { ...preset };\n timingPreset.milliseconds = preset.milliseconds ?? milliseconds;\n timingPreset.runImmediately = runImmediately ?? false;\n return {\n id: task.name ?? task,\n task,\n preset: timingPreset,\n };\n}\n\nexport class ServerTasks {\n public static readonly SERVER_BOOT_TASK = registerTask(DITokens.bootTask, TaskPresets.PERIODIC_DISABLED, 5000, false);\n public static readonly BOOT_TASKS = [\n registerTask(DITokens.softwareUpdateTask, TaskPresets.RUNDELAYED, 1500),\n registerTask(DITokens.clientDistDownloadTask, TaskPresets.RUNONCE),\n registerTask(DITokens.socketIoTask, TaskPresets.PERIODIC, 500),\n // Every 2 seconds\n registerTask(DITokens.printerWebsocketTask, TaskPresets.PERIODIC, 2000, true),\n // Every 15 seconds\n registerTask(DITokens.printerWebsocketRestoreTask, TaskPresets.PERIODIC, 15 * 1000, false),\n // Analyze pending print jobs every 30 seconds\n registerTask(DITokens.printJobAnalysisTask, TaskPresets.PERIODIC, 30 * 1000, false),\n ];\n}\n"],"mappings":";;;;;;AAOA,SAAgB,aAAa,MAAW,QAAsB,eAAe,GAAG,iBAAiB,OAAO;CACtG,IAAI,eAAe,EAAE,GAAG,QAAQ;AAChC,cAAa,eAAe,OAAO,gBAAgB;AACnD,cAAa,iBAAiB,kBAAkB;AAChD,QAAO;EACL,IAAI,KAAK,QAAQ;EACjB;EACA,QAAQ;EACT;;AAGH,IAAa,cAAb,MAAyB;CACvB,OAAuB,mBAAmB,aAAa,SAAS,UAAUA,aAAY,mBAAmB,KAAM,MAAM;CACrH,OAAuB,aAAa;EAClC,aAAa,SAAS,oBAAoBA,aAAY,YAAY,KAAK;EACvE,aAAa,SAAS,wBAAwBA,aAAY,QAAQ;EAClE,aAAa,SAAS,cAAcA,aAAY,UAAU,IAAI;EAE9D,aAAa,SAAS,sBAAsBA,aAAY,UAAU,KAAM,KAAK;EAE7E,aAAa,SAAS,6BAA6BA,aAAY,UAAU,KAAK,KAAM,MAAM;EAE1F,aAAa,SAAS,sBAAsBA,aAAY,UAAU,KAAK,KAAM,MAAM;EACpF"}
1
+ {"version":3,"file":"tasks.js","names":["TaskPresets"],"sources":["../src/tasks.ts"],"sourcesContent":["import { TASK_PRESETS as TaskPresets } from \"./task.presets\";\nimport { DITokens } from \"./container.tokens\";\nimport { TimingPreset } from \"@/services/interfaces/task.interfaces\";\n\n/**\n * Register a task with a preset and timing (run immediate does not retry in case of failure)\n */\nexport function registerTask(task: any, preset: TimingPreset, milliseconds = 0, runImmediately = false) {\n let timingPreset = { ...preset };\n timingPreset.milliseconds = preset.milliseconds ?? milliseconds;\n timingPreset.runImmediately = runImmediately ?? false;\n return {\n id: task.name ?? task,\n task,\n preset: timingPreset,\n };\n}\n\nexport class ServerTasks {\n public static readonly SERVER_BOOT_TASK = registerTask(DITokens.bootTask, TaskPresets.PERIODIC_DISABLED, 5000, false);\n public static readonly BOOT_TASKS = [\n registerTask(DITokens.softwareUpdateTask, TaskPresets.RUNDELAYED, 1500),\n registerTask(DITokens.clientDistDownloadTask, TaskPresets.RUNONCE),\n registerTask(DITokens.socketIoTask, TaskPresets.PERIODIC, 500),\n // Every 2 seconds\n registerTask(DITokens.printerWebsocketTask, TaskPresets.PERIODIC, 2000, true),\n // Every 15 seconds\n registerTask(DITokens.printerWebsocketRestoreTask, TaskPresets.PERIODIC, 15 * 1000, false),\n // Analyze pending print jobs every 30 seconds\n registerTask(DITokens.printJobAnalysisTask, TaskPresets.PERIODIC, 30 * 1000, false),\n ];\n}\n"],"mappings":";;;;;;AAOA,SAAgB,aAAa,MAAW,QAAsB,eAAe,GAAG,iBAAiB,OAAO;CACtG,IAAI,eAAe,EAAE,GAAG,QAAQ;CAChC,aAAa,eAAe,OAAO,gBAAgB;CACnD,aAAa,iBAAiB,kBAAkB;CAChD,OAAO;EACL,IAAI,KAAK,QAAQ;EACjB;EACA,QAAQ;EACT;;AAGH,IAAa,cAAb,MAAyB;CACvB,OAAuB,mBAAmB,aAAa,SAAS,UAAUA,aAAY,mBAAmB,KAAM,MAAM;CACrH,OAAuB,aAAa;EAClC,aAAa,SAAS,oBAAoBA,aAAY,YAAY,KAAK;EACvE,aAAa,SAAS,wBAAwBA,aAAY,QAAQ;EAClE,aAAa,SAAS,cAAcA,aAAY,UAAU,IAAI;EAE9D,aAAa,SAAS,sBAAsBA,aAAY,UAAU,KAAM,KAAK;EAE7E,aAAa,SAAS,6BAA6BA,aAAY,UAAU,KAAK,KAAM,MAAM;EAE1F,aAAa,SAAS,sBAAsBA,aAAY,UAAU,KAAK,KAAM,MAAM;EACpF"}
@@ -1 +1 @@
1
- {"version":3,"file":"array.util.js","names":[],"sources":["../../src/utils/array.util.ts"],"sourcesContent":["export const groupArrayBy = <T>(array: T[], predicate: (value: T, index: number, array: T[]) => string) =>\n array.reduce(\n (acc, value, index, array) => {\n (acc[predicate(value, index, array)] ||= []).push(value);\n return acc;\n },\n {} as { [key: string]: T[] },\n );\n\nexport const distinct = (value: any, index: number, self: any) => {\n return self.indexOf(value) === index;\n};\n"],"mappings":";AAAA,MAAa,gBAAmB,OAAY,cAC1C,MAAM,QACH,KAAK,OAAO,OAAO,UAAU;AAC5B,EAAC,IAAI,UAAU,OAAO,OAAO,MAAM,MAAM,EAAE,EAAE,KAAK,MAAM;AACxD,QAAO;GAET,EAAE,CACH;AAEH,MAAa,YAAY,OAAY,OAAe,SAAc;AAChE,QAAO,KAAK,QAAQ,MAAM,KAAK"}
1
+ {"version":3,"file":"array.util.js","names":[],"sources":["../../src/utils/array.util.ts"],"sourcesContent":["export const groupArrayBy = <T>(array: T[], predicate: (value: T, index: number, array: T[]) => string) =>\n array.reduce(\n (acc, value, index, array) => {\n (acc[predicate(value, index, array)] ||= []).push(value);\n return acc;\n },\n {} as { [key: string]: T[] },\n );\n\nexport const distinct = (value: any, index: number, self: any) => {\n return self.indexOf(value) === index;\n};\n"],"mappings":";AAAA,MAAa,gBAAmB,OAAY,cAC1C,MAAM,QACH,KAAK,OAAO,OAAO,UAAU;CAC5B,CAAC,IAAI,UAAU,OAAO,OAAO,MAAM,MAAM,EAAE,EAAE,KAAK,MAAM;CACxD,OAAO;GAET,EAAE,CACH;AAEH,MAAa,YAAY,OAAY,OAAe,SAAc;CAChE,OAAO,KAAK,QAAQ,MAAM,KAAK"}
@@ -1 +1 @@
1
- {"version":3,"file":"bgcode-thumbnail.parser.js","names":[],"sources":["../../../src/utils/bgcode/bgcode-thumbnail.parser.ts"],"sourcesContent":["import { BgCodeThumbnailFormats, BgCodeThumbnailParameters, BgCodeThumbnailFormatExtension } from \"./bgcode.types\";\nimport { decodeQOI } from \"./qoi-decoder\";\nimport { encodePNG } from \"./png-encoder\";\n\nexport interface ThumbnailProcessResult {\n extension: string;\n data: Buffer;\n converted?: boolean;\n}\n\nexport function convertQoiToPng(qoiBuffer: Buffer): Buffer {\n const decoded = decodeQOI(qoiBuffer);\n return encodePNG(decoded.width, decoded.height, decoded.data);\n}\n\nexport function processThumbnail(data: Buffer, parameters: BgCodeThumbnailParameters): ThumbnailProcessResult {\n switch (parameters.format) {\n case BgCodeThumbnailFormats.PNG:\n return {\n extension: BgCodeThumbnailFormatExtension[BgCodeThumbnailFormats.PNG],\n data: data,\n };\n\n case BgCodeThumbnailFormats.JPG:\n return {\n extension: BgCodeThumbnailFormatExtension[BgCodeThumbnailFormats.JPG],\n data: data,\n };\n\n case BgCodeThumbnailFormats.QOI:\n const pngData = convertQoiToPng(data);\n return {\n extension: BgCodeThumbnailFormatExtension[BgCodeThumbnailFormats.PNG],\n data: pngData,\n converted: true,\n };\n\n default:\n throw new Error(`Unsupported thumbnail format: ${parameters.format}`);\n }\n}\n"],"mappings":";;;;AAUA,SAAgB,gBAAgB,WAA2B;CACzD,MAAM,UAAU,UAAU,UAAU;AACpC,QAAO,UAAU,QAAQ,OAAO,QAAQ,QAAQ,QAAQ,KAAK;;AAG/D,SAAgB,iBAAiB,MAAc,YAA+D;AAC5G,SAAQ,WAAW,QAAnB;EACE,KAAK,uBAAuB,IAC1B,QAAO;GACL,WAAW,+BAA+B,uBAAuB;GAC3D;GACP;EAEH,KAAK,uBAAuB,IAC1B,QAAO;GACL,WAAW,+BAA+B,uBAAuB;GAC3D;GACP;EAEH,KAAK,uBAAuB;GAC1B,MAAM,UAAU,gBAAgB,KAAK;AACrC,UAAO;IACL,WAAW,+BAA+B,uBAAuB;IACjE,MAAM;IACN,WAAW;IACZ;EAEH,QACE,OAAM,IAAI,MAAM,iCAAiC,WAAW,SAAS"}
1
+ {"version":3,"file":"bgcode-thumbnail.parser.js","names":[],"sources":["../../../src/utils/bgcode/bgcode-thumbnail.parser.ts"],"sourcesContent":["import { BgCodeThumbnailFormats, BgCodeThumbnailParameters, BgCodeThumbnailFormatExtension } from \"./bgcode.types\";\nimport { decodeQOI } from \"./qoi-decoder\";\nimport { encodePNG } from \"./png-encoder\";\n\nexport interface ThumbnailProcessResult {\n extension: string;\n data: Buffer;\n converted?: boolean;\n}\n\nexport function convertQoiToPng(qoiBuffer: Buffer): Buffer {\n const decoded = decodeQOI(qoiBuffer);\n return encodePNG(decoded.width, decoded.height, decoded.data);\n}\n\nexport function processThumbnail(data: Buffer, parameters: BgCodeThumbnailParameters): ThumbnailProcessResult {\n switch (parameters.format) {\n case BgCodeThumbnailFormats.PNG:\n return {\n extension: BgCodeThumbnailFormatExtension[BgCodeThumbnailFormats.PNG],\n data: data,\n };\n\n case BgCodeThumbnailFormats.JPG:\n return {\n extension: BgCodeThumbnailFormatExtension[BgCodeThumbnailFormats.JPG],\n data: data,\n };\n\n case BgCodeThumbnailFormats.QOI:\n const pngData = convertQoiToPng(data);\n return {\n extension: BgCodeThumbnailFormatExtension[BgCodeThumbnailFormats.PNG],\n data: pngData,\n converted: true,\n };\n\n default:\n throw new Error(`Unsupported thumbnail format: ${parameters.format}`);\n }\n}\n"],"mappings":";;;;AAUA,SAAgB,gBAAgB,WAA2B;CACzD,MAAM,UAAU,UAAU,UAAU;CACpC,OAAO,UAAU,QAAQ,OAAO,QAAQ,QAAQ,QAAQ,KAAK;;AAG/D,SAAgB,iBAAiB,MAAc,YAA+D;CAC5G,QAAQ,WAAW,QAAnB;EACE,KAAK,uBAAuB,KAC1B,OAAO;GACL,WAAW,+BAA+B,uBAAuB;GAC3D;GACP;EAEH,KAAK,uBAAuB,KAC1B,OAAO;GACL,WAAW,+BAA+B,uBAAuB;GAC3D;GACP;EAEH,KAAK,uBAAuB;GAC1B,MAAM,UAAU,gBAAgB,KAAK;GACrC,OAAO;IACL,WAAW,+BAA+B,uBAAuB;IACjE,MAAM;IACN,WAAW;IACZ;EAEH,SACE,MAAM,IAAI,MAAM,iCAAiC,WAAW,SAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"bgcode.utils.js","names":[],"sources":["../../../src/utils/bgcode/bgcode.utils.ts"],"sourcesContent":["import {\n BGCODE_EXPECTED_HEADERS,\n BGCODE_HEADER_MARKER,\n BGCODE_HEADER_SIZE,\n BGCODE_MAX_BLOCK_HEADER_SIZE,\n BGCODE_MAX_BLOCK_TYPE,\n BGCODE_MAX_CHECKSUM_TYPE,\n BGCODE_MAX_COMPRESSION,\n BGCODE_MIN_BLOCK_HEADER_SIZE,\n BGCODE_PARAMETER_THUMBNAIL_MAX_FORMAT,\n} from \"./bgcode.constants\";\nimport { FileHandle } from \"node:fs/promises\";\nimport {\n type BgCodeBlockHeader,\n type BgCodeBlockType,\n type BgCodeHeaderSize,\n type BgCodeChecksumType,\n BgCodeBlockTypes,\n BgCodeCompression,\n BgCodeCompressionInfo,\n BgCodeCompressionName,\n BgCodeChecksumTypeSize,\n BgCodeBlockParameterSizes,\n} from \"./bgcode.types\";\nimport { inflateSync } from \"node:zlib\";\nimport { HeatshrinkDecoder } from \"./heatshrink-decoder\";\n\nexport async function parseFileHeader(fileHandle: FileHandle): Promise<{\n magic: string;\n version: number;\n checksumType: number;\n}> {\n const buffer = Buffer.alloc(BGCODE_HEADER_SIZE);\n await fileHandle.read(buffer);\n\n if (buffer.length < BGCODE_HEADER_SIZE) {\n throw new Error(\"File too small to be a valid BGCode file\");\n }\n\n const magic = buffer.toString(\"ascii\", 0, 4);\n if (magic !== BGCODE_HEADER_MARKER) {\n throw new Error(`Invalid BGCode file: magic number not found (got \"${magic}\", expected ${BGCODE_HEADER_MARKER})`);\n }\n\n const version = buffer.readUInt32LE(4);\n const checksumType = buffer.readUInt16LE(8);\n\n return { magic, version, checksumType };\n}\n\nexport async function parseBlockHeaders(\n fileHandle: FileHandle,\n fileSize: number,\n checksumType: number,\n skipGcode: true,\n) {\n let offset = BGCODE_HEADER_SIZE;\n const blockHeaders: BgCodeBlockHeader[] = [];\n let currentExpectedHeaderIndex = 0;\n\n while (offset < fileSize) {\n const blockHeader = await parseBlockHeader(fileHandle, fileSize, offset, checksumType);\n if (skipGcode && blockHeader.type == BgCodeBlockTypes.GCode) {\n break;\n }\n\n const expectedType = BGCODE_EXPECTED_HEADERS[currentExpectedHeaderIndex];\n if (blockHeader.type !== expectedType) {\n const nextExpectedType = BGCODE_EXPECTED_HEADERS[currentExpectedHeaderIndex + 1];\n if (blockHeader.type !== nextExpectedType) {\n throw new Error(\n `Unexpected header header type ${blockHeader.type}, expected either ${expectedType} or next ${nextExpectedType}`,\n );\n }\n currentExpectedHeaderIndex++;\n }\n\n if (Number.isNaN(blockHeader.blockSize)) {\n throw new Error(`Unexpected blockHeader.blockSize ${blockHeader.blockSize}`);\n }\n\n offset += blockHeader.blockSize;\n blockHeaders.push(blockHeader);\n }\n\n return blockHeaders;\n}\n\nexport async function parseBlockHeader(\n fileHandle: FileHandle,\n fileSize: number,\n blockStartOffset: number,\n checksumType: BgCodeChecksumType,\n): Promise<BgCodeBlockHeader> {\n if (blockStartOffset + BGCODE_MAX_BLOCK_HEADER_SIZE > fileSize) {\n throw new Error(\"Not enough data for block header\");\n }\n\n const buffer = Buffer.alloc(BGCODE_MAX_BLOCK_HEADER_SIZE);\n await fileHandle.read({\n buffer,\n offset: 0,\n length: BGCODE_MAX_BLOCK_HEADER_SIZE,\n position: blockStartOffset,\n });\n\n const type = buffer.readUInt16LE(0) as BgCodeBlockType;\n if (type > BGCODE_MAX_BLOCK_TYPE) {\n throw new Error(`Block header type ${type} exceeds ${BGCODE_MAX_BLOCK_TYPE}, cant parse block`);\n }\n\n const compression = buffer.readUInt16LE(2) as BgCodeCompression;\n if (compression > BGCODE_MAX_COMPRESSION) {\n throw new Error(`Block header compression ${compression} exceeds ${BGCODE_MAX_COMPRESSION}, cant parse block`);\n }\n\n const uncompressedSize = buffer.readUInt32LE(4);\n if (uncompressedSize === 0) {\n throw new Error(`Uncompressed Size is 0`);\n }\n\n let compressedSize = 0;\n let headerSize = BGCODE_MIN_BLOCK_HEADER_SIZE as BgCodeHeaderSize;\n if (compression > 0) {\n compressedSize = buffer.readUInt32LE(8);\n if (compressedSize === 0) {\n throw new Error(`Compression is ${BgCodeCompressionName[compression]} but compressed size is ${compressedSize}}`);\n }\n headerSize = BGCODE_MAX_BLOCK_HEADER_SIZE;\n }\n\n const checksumSize = calculateChecksumSize(checksumType);\n const blockSize = calculateBlockSize(checksumSize, type, compression, uncompressedSize, compressedSize);\n const parameterOffset = blockStartOffset + headerSize;\n const parametersSize = calculateParameterSize(type);\n const parameterBuffer = await getBlockParsedParameters(fileHandle, parameterOffset, parametersSize);\n const parameters = parseBlockParameters(type, parameterBuffer);\n const dataOffset = parameterOffset + parametersSize;\n const dataSize = blockSize - parametersSize - checksumSize;\n const checksumOffset = dataOffset + dataSize;\n\n return {\n type,\n compression,\n uncompressedSize,\n compressedSize,\n blockSize,\n blockStartOffset,\n headerSize,\n parameterOffset,\n parameters,\n parametersSize,\n dataOffset,\n dataSize,\n checksumOffset,\n checksumSize,\n checksumType,\n };\n}\n\nexport async function getBlockParsedParameters(\n fileHandle: FileHandle,\n parameterOffset: number,\n parametersSize: number,\n) {\n const parameters = Buffer.alloc(parametersSize);\n if (parametersSize > 0) {\n await fileHandle.read({ buffer: parameters, offset: 0, length: parametersSize, position: parameterOffset });\n }\n\n return parameters;\n}\n\nexport async function getBlockData(fileHandle: FileHandle, blockHeader: BgCodeBlockHeader) {\n if (blockHeader.blockSize === 0) {\n throw new Error(\"Cant get block data for block with size 0\");\n }\n if (blockHeader.blockSize === 1e3) {\n throw new Error(`Cant get block data for block with size ${blockHeader.blockSize} > ${1e3}`);\n }\n\n const data = Buffer.alloc(blockHeader.dataSize);\n await fileHandle.read({ buffer: data, offset: 0, length: blockHeader.dataSize, position: blockHeader.dataOffset });\n return data;\n}\n\nexport function decompressBlock(compression: BgCodeCompression, data: Buffer) {\n const info = BgCodeCompressionInfo[compression];\n\n switch (info.kind) {\n case \"none\":\n return data;\n case \"deflate\":\n return inflateSync(data);\n case \"heatshrink\":\n const decoder = new HeatshrinkDecoder(info.window, info.lookahead);\n return decoder.decompress(data);\n default:\n throw new Error(`Unknown compression type: ${compression}`);\n }\n}\n\nexport function parseBlockParameters(blockType: BgCodeBlockType, parameters: Buffer) {\n const parameterSize = BgCodeBlockParameterSizes[blockType];\n if (parameters.length !== parameterSize) {\n throw new Error(`Block Parameters should be exactly ${parameterSize} bytes long`);\n }\n\n if (blockType === BgCodeBlockTypes.Thumbnail) {\n return {\n format: calculateThumbnailFormat(parameters.readUInt16LE(0)),\n width: parameters.readUInt16LE(2),\n height: parameters.readUInt16LE(4),\n };\n }\n\n return {\n encoding: parameters.readUInt16LE(0),\n };\n}\n\nfunction calculateBlockSize(\n checksumSize: number,\n blockType: BgCodeBlockType,\n compression: BgCodeCompression,\n uncompressedSize: number,\n compressedSize: number,\n) {\n const headerSize = compression > 0 ? 12 : 8;\n const dataSize = compression === 0 ? uncompressedSize : compressedSize;\n // Thumbnail type (5) has 3 fields of 2 bytes each\n const parametersSize = blockType === BgCodeBlockTypes.Thumbnail ? 6 : 2;\n\n return headerSize + parametersSize + dataSize + checksumSize;\n}\n\nfunction calculateChecksumSize(checksumType: BgCodeChecksumType) {\n if (checksumType > BGCODE_MAX_CHECKSUM_TYPE) {\n throw new Error(`Checksum type ${checksumType} exceeds max ${BGCODE_MAX_CHECKSUM_TYPE}`);\n }\n\n return BgCodeChecksumTypeSize[checksumType];\n}\n\nfunction calculateParameterSize(blockType: BgCodeBlockType) {\n if (blockType > BGCODE_MAX_BLOCK_TYPE) {\n throw new Error(`Checksum type ${blockType} exceeds max ${BGCODE_MAX_BLOCK_TYPE}`);\n }\n\n return BgCodeBlockParameterSizes[blockType];\n}\n\nfunction calculateThumbnailFormat(formatParameter: number) {\n if (formatParameter > BGCODE_PARAMETER_THUMBNAIL_MAX_FORMAT) {\n throw new Error(`Thumbnail format type ${formatParameter} exceeds max ${BGCODE_PARAMETER_THUMBNAIL_MAX_FORMAT}`);\n }\n return formatParameter;\n}\n\nexport async function extractMetadataFromBlocks(\n fileHandle: FileHandle,\n blockHeaders: BgCodeBlockHeader[],\n): Promise<Record<string, string>> {\n const metadata: Record<string, string> = {};\n\n const metadataBlocks = blockHeaders.filter(\n (b) =>\n b.type === BgCodeBlockTypes.FileMetadata ||\n b.type === BgCodeBlockTypes.SlicerMetadata ||\n b.type === BgCodeBlockTypes.PrinterMetadata ||\n b.type === BgCodeBlockTypes.PrintMetadata,\n );\n\n for (const header of metadataBlocks) {\n const blockData = await getBlockData(fileHandle, header);\n const data = decompressBlock(header.compression, blockData);\n const text = data.toString(\"utf8\");\n extractMetadataFromText(text, metadata);\n }\n\n return metadata;\n}\n\nconst METADATA_KEY_NORMALIZATION: Record<string, string> = {\n producer: \"producer\",\n \"produced on\": \"produced_on\",\n \"print time\": \"print_time\",\n \"estimated printing time (silent mode)\": \"estimated_printing_time_silent_mode\",\n \"estimated printing time (normal mode)\": \"estimated_printing_time_normal_mode\",\n \"layer height\": \"layer_height\",\n \"first layer height\": \"first_layer_height\",\n \"initial layer height\": \"initial_layer_height\",\n \"nozzle diameter\": \"nozzle_diameter\",\n \"filament diameter\": \"filament_diameter\",\n \"filament density\": \"filament_density\",\n \"filament used [mm]\": \"filament_used_mm\",\n \"filament used [cm3]\": \"filament_used_cm3\",\n \"filament used [g]\": \"filament_used_g\",\n \"bed temperature\": \"bed_temperature\",\n temperature: \"temperature\",\n \"fill density\": \"fill_density\",\n \"filament type\": \"filament_type\",\n \"printer model\": \"printer_model\",\n \"max layer z\": \"max_layer_z\",\n \"total layers\": \"total_layers\",\n \"layer count\": \"layer_count\",\n};\n\nfunction extractMetadataFromText(text: string, metadata: Record<string, string>): void {\n const lines = text.split(\"\\n\");\n\n for (const line of lines) {\n const keyValuePair = parseMetadataLine(line);\n if (!keyValuePair) {\n continue;\n }\n\n const { key, value } = keyValuePair;\n metadata[key] = value;\n }\n}\n\nfunction parseMetadataLine(line: string): { key: string; value: string } | null {\n const equalIndex = line.indexOf(\"=\");\n if (equalIndex === -1) {\n return null;\n }\n\n const rawKey = line.substring(0, equalIndex).trim().toLowerCase();\n const value = line.substring(equalIndex + 1).trim();\n\n if (!rawKey || !value) {\n return null;\n }\n\n const normalizedKey = METADATA_KEY_NORMALIZATION[rawKey] || rawKey.replace(/\\s+/g, \"_\");\n return { key: normalizedKey, value };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bgcode.utils.js","names":[],"sources":["../../../src/utils/bgcode/bgcode.utils.ts"],"sourcesContent":["import {\n BGCODE_EXPECTED_HEADERS,\n BGCODE_HEADER_MARKER,\n BGCODE_HEADER_SIZE,\n BGCODE_MAX_BLOCK_HEADER_SIZE,\n BGCODE_MAX_BLOCK_TYPE,\n BGCODE_MAX_CHECKSUM_TYPE,\n BGCODE_MAX_COMPRESSION,\n BGCODE_MIN_BLOCK_HEADER_SIZE,\n BGCODE_PARAMETER_THUMBNAIL_MAX_FORMAT,\n} from \"./bgcode.constants\";\nimport { FileHandle } from \"node:fs/promises\";\nimport {\n type BgCodeBlockHeader,\n type BgCodeBlockType,\n type BgCodeHeaderSize,\n type BgCodeChecksumType,\n BgCodeBlockTypes,\n BgCodeCompression,\n BgCodeCompressionInfo,\n BgCodeCompressionName,\n BgCodeChecksumTypeSize,\n BgCodeBlockParameterSizes,\n} from \"./bgcode.types\";\nimport { inflateSync } from \"node:zlib\";\nimport { HeatshrinkDecoder } from \"./heatshrink-decoder\";\n\nexport async function parseFileHeader(fileHandle: FileHandle): Promise<{\n magic: string;\n version: number;\n checksumType: number;\n}> {\n const buffer = Buffer.alloc(BGCODE_HEADER_SIZE);\n await fileHandle.read(buffer);\n\n if (buffer.length < BGCODE_HEADER_SIZE) {\n throw new Error(\"File too small to be a valid BGCode file\");\n }\n\n const magic = buffer.toString(\"ascii\", 0, 4);\n if (magic !== BGCODE_HEADER_MARKER) {\n throw new Error(`Invalid BGCode file: magic number not found (got \"${magic}\", expected ${BGCODE_HEADER_MARKER})`);\n }\n\n const version = buffer.readUInt32LE(4);\n const checksumType = buffer.readUInt16LE(8);\n\n return { magic, version, checksumType };\n}\n\nexport async function parseBlockHeaders(\n fileHandle: FileHandle,\n fileSize: number,\n checksumType: number,\n skipGcode: true,\n) {\n let offset = BGCODE_HEADER_SIZE;\n const blockHeaders: BgCodeBlockHeader[] = [];\n let currentExpectedHeaderIndex = 0;\n\n while (offset < fileSize) {\n const blockHeader = await parseBlockHeader(fileHandle, fileSize, offset, checksumType);\n if (skipGcode && blockHeader.type == BgCodeBlockTypes.GCode) {\n break;\n }\n\n const expectedType = BGCODE_EXPECTED_HEADERS[currentExpectedHeaderIndex];\n if (blockHeader.type !== expectedType) {\n const nextExpectedType = BGCODE_EXPECTED_HEADERS[currentExpectedHeaderIndex + 1];\n if (blockHeader.type !== nextExpectedType) {\n throw new Error(\n `Unexpected header header type ${blockHeader.type}, expected either ${expectedType} or next ${nextExpectedType}`,\n );\n }\n currentExpectedHeaderIndex++;\n }\n\n if (Number.isNaN(blockHeader.blockSize)) {\n throw new Error(`Unexpected blockHeader.blockSize ${blockHeader.blockSize}`);\n }\n\n offset += blockHeader.blockSize;\n blockHeaders.push(blockHeader);\n }\n\n return blockHeaders;\n}\n\nexport async function parseBlockHeader(\n fileHandle: FileHandle,\n fileSize: number,\n blockStartOffset: number,\n checksumType: BgCodeChecksumType,\n): Promise<BgCodeBlockHeader> {\n if (blockStartOffset + BGCODE_MAX_BLOCK_HEADER_SIZE > fileSize) {\n throw new Error(\"Not enough data for block header\");\n }\n\n const buffer = Buffer.alloc(BGCODE_MAX_BLOCK_HEADER_SIZE);\n await fileHandle.read({\n buffer,\n offset: 0,\n length: BGCODE_MAX_BLOCK_HEADER_SIZE,\n position: blockStartOffset,\n });\n\n const type = buffer.readUInt16LE(0) as BgCodeBlockType;\n if (type > BGCODE_MAX_BLOCK_TYPE) {\n throw new Error(`Block header type ${type} exceeds ${BGCODE_MAX_BLOCK_TYPE}, cant parse block`);\n }\n\n const compression = buffer.readUInt16LE(2) as BgCodeCompression;\n if (compression > BGCODE_MAX_COMPRESSION) {\n throw new Error(`Block header compression ${compression} exceeds ${BGCODE_MAX_COMPRESSION}, cant parse block`);\n }\n\n const uncompressedSize = buffer.readUInt32LE(4);\n if (uncompressedSize === 0) {\n throw new Error(`Uncompressed Size is 0`);\n }\n\n let compressedSize = 0;\n let headerSize = BGCODE_MIN_BLOCK_HEADER_SIZE as BgCodeHeaderSize;\n if (compression > 0) {\n compressedSize = buffer.readUInt32LE(8);\n if (compressedSize === 0) {\n throw new Error(`Compression is ${BgCodeCompressionName[compression]} but compressed size is ${compressedSize}}`);\n }\n headerSize = BGCODE_MAX_BLOCK_HEADER_SIZE;\n }\n\n const checksumSize = calculateChecksumSize(checksumType);\n const blockSize = calculateBlockSize(checksumSize, type, compression, uncompressedSize, compressedSize);\n const parameterOffset = blockStartOffset + headerSize;\n const parametersSize = calculateParameterSize(type);\n const parameterBuffer = await getBlockParsedParameters(fileHandle, parameterOffset, parametersSize);\n const parameters = parseBlockParameters(type, parameterBuffer);\n const dataOffset = parameterOffset + parametersSize;\n const dataSize = blockSize - parametersSize - checksumSize;\n const checksumOffset = dataOffset + dataSize;\n\n return {\n type,\n compression,\n uncompressedSize,\n compressedSize,\n blockSize,\n blockStartOffset,\n headerSize,\n parameterOffset,\n parameters,\n parametersSize,\n dataOffset,\n dataSize,\n checksumOffset,\n checksumSize,\n checksumType,\n };\n}\n\nexport async function getBlockParsedParameters(\n fileHandle: FileHandle,\n parameterOffset: number,\n parametersSize: number,\n) {\n const parameters = Buffer.alloc(parametersSize);\n if (parametersSize > 0) {\n await fileHandle.read({ buffer: parameters, offset: 0, length: parametersSize, position: parameterOffset });\n }\n\n return parameters;\n}\n\nexport async function getBlockData(fileHandle: FileHandle, blockHeader: BgCodeBlockHeader) {\n if (blockHeader.blockSize === 0) {\n throw new Error(\"Cant get block data for block with size 0\");\n }\n if (blockHeader.blockSize === 1e3) {\n throw new Error(`Cant get block data for block with size ${blockHeader.blockSize} > ${1e3}`);\n }\n\n const data = Buffer.alloc(blockHeader.dataSize);\n await fileHandle.read({ buffer: data, offset: 0, length: blockHeader.dataSize, position: blockHeader.dataOffset });\n return data;\n}\n\nexport function decompressBlock(compression: BgCodeCompression, data: Buffer) {\n const info = BgCodeCompressionInfo[compression];\n\n switch (info.kind) {\n case \"none\":\n return data;\n case \"deflate\":\n return inflateSync(data);\n case \"heatshrink\":\n const decoder = new HeatshrinkDecoder(info.window, info.lookahead);\n return decoder.decompress(data);\n default:\n throw new Error(`Unknown compression type: ${compression}`);\n }\n}\n\nexport function parseBlockParameters(blockType: BgCodeBlockType, parameters: Buffer) {\n const parameterSize = BgCodeBlockParameterSizes[blockType];\n if (parameters.length !== parameterSize) {\n throw new Error(`Block Parameters should be exactly ${parameterSize} bytes long`);\n }\n\n if (blockType === BgCodeBlockTypes.Thumbnail) {\n return {\n format: calculateThumbnailFormat(parameters.readUInt16LE(0)),\n width: parameters.readUInt16LE(2),\n height: parameters.readUInt16LE(4),\n };\n }\n\n return {\n encoding: parameters.readUInt16LE(0),\n };\n}\n\nfunction calculateBlockSize(\n checksumSize: number,\n blockType: BgCodeBlockType,\n compression: BgCodeCompression,\n uncompressedSize: number,\n compressedSize: number,\n) {\n const headerSize = compression > 0 ? 12 : 8;\n const dataSize = compression === 0 ? uncompressedSize : compressedSize;\n // Thumbnail type (5) has 3 fields of 2 bytes each\n const parametersSize = blockType === BgCodeBlockTypes.Thumbnail ? 6 : 2;\n\n return headerSize + parametersSize + dataSize + checksumSize;\n}\n\nfunction calculateChecksumSize(checksumType: BgCodeChecksumType) {\n if (checksumType > BGCODE_MAX_CHECKSUM_TYPE) {\n throw new Error(`Checksum type ${checksumType} exceeds max ${BGCODE_MAX_CHECKSUM_TYPE}`);\n }\n\n return BgCodeChecksumTypeSize[checksumType];\n}\n\nfunction calculateParameterSize(blockType: BgCodeBlockType) {\n if (blockType > BGCODE_MAX_BLOCK_TYPE) {\n throw new Error(`Checksum type ${blockType} exceeds max ${BGCODE_MAX_BLOCK_TYPE}`);\n }\n\n return BgCodeBlockParameterSizes[blockType];\n}\n\nfunction calculateThumbnailFormat(formatParameter: number) {\n if (formatParameter > BGCODE_PARAMETER_THUMBNAIL_MAX_FORMAT) {\n throw new Error(`Thumbnail format type ${formatParameter} exceeds max ${BGCODE_PARAMETER_THUMBNAIL_MAX_FORMAT}`);\n }\n return formatParameter;\n}\n\nexport async function extractMetadataFromBlocks(\n fileHandle: FileHandle,\n blockHeaders: BgCodeBlockHeader[],\n): Promise<Record<string, string>> {\n const metadata: Record<string, string> = {};\n\n const metadataBlocks = blockHeaders.filter(\n (b) =>\n b.type === BgCodeBlockTypes.FileMetadata ||\n b.type === BgCodeBlockTypes.SlicerMetadata ||\n b.type === BgCodeBlockTypes.PrinterMetadata ||\n b.type === BgCodeBlockTypes.PrintMetadata,\n );\n\n for (const header of metadataBlocks) {\n const blockData = await getBlockData(fileHandle, header);\n const data = decompressBlock(header.compression, blockData);\n const text = data.toString(\"utf8\");\n extractMetadataFromText(text, metadata);\n }\n\n return metadata;\n}\n\nconst METADATA_KEY_NORMALIZATION: Record<string, string> = {\n producer: \"producer\",\n \"produced on\": \"produced_on\",\n \"print time\": \"print_time\",\n \"estimated printing time (silent mode)\": \"estimated_printing_time_silent_mode\",\n \"estimated printing time (normal mode)\": \"estimated_printing_time_normal_mode\",\n \"layer height\": \"layer_height\",\n \"first layer height\": \"first_layer_height\",\n \"initial layer height\": \"initial_layer_height\",\n \"nozzle diameter\": \"nozzle_diameter\",\n \"filament diameter\": \"filament_diameter\",\n \"filament density\": \"filament_density\",\n \"filament used [mm]\": \"filament_used_mm\",\n \"filament used [cm3]\": \"filament_used_cm3\",\n \"filament used [g]\": \"filament_used_g\",\n \"bed temperature\": \"bed_temperature\",\n temperature: \"temperature\",\n \"fill density\": \"fill_density\",\n \"filament type\": \"filament_type\",\n \"printer model\": \"printer_model\",\n \"max layer z\": \"max_layer_z\",\n \"total layers\": \"total_layers\",\n \"layer count\": \"layer_count\",\n};\n\nfunction extractMetadataFromText(text: string, metadata: Record<string, string>): void {\n const lines = text.split(\"\\n\");\n\n for (const line of lines) {\n const keyValuePair = parseMetadataLine(line);\n if (!keyValuePair) {\n continue;\n }\n\n const { key, value } = keyValuePair;\n metadata[key] = value;\n }\n}\n\nfunction parseMetadataLine(line: string): { key: string; value: string } | null {\n const equalIndex = line.indexOf(\"=\");\n if (equalIndex === -1) {\n return null;\n }\n\n const rawKey = line.substring(0, equalIndex).trim().toLowerCase();\n const value = line.substring(equalIndex + 1).trim();\n\n if (!rawKey || !value) {\n return null;\n }\n\n const normalizedKey = METADATA_KEY_NORMALIZATION[rawKey] || rawKey.replace(/\\s+/g, \"_\");\n return { key: normalizedKey, value };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"heatshrink-decoder.js","names":[],"sources":["../../../src/utils/bgcode/heatshrink-decoder.ts"],"sourcesContent":["/**\n * Heatshrink LZSS decompressor implementation\n * Based on the Heatshrink compression library by Scott Vokes\n * https://github.com/atomicobject/heatshrink\n */\n\nenum HSState {\n TAG_BIT,\n YIELD_LITERAL,\n BACKREF_INDEX_MSB,\n BACKREF_INDEX_LSB,\n BACKREF_COUNT_MSB,\n BACKREF_COUNT_LSB,\n YIELD_BACKREF,\n}\n\nexport class HeatshrinkDecoder {\n private readonly windowBits: number;\n private readonly lookaheadBits: number;\n private readonly windowSize: number;\n\n private readonly window: Buffer;\n private head: number = 0;\n private state: HSState = HSState.TAG_BIT;\n\n private outputIndex: number = 0;\n private outputCount: number = 0;\n\n private inputBuffer: Buffer;\n private inputIndex: number = 0;\n private bitAccumulator: number = 0;\n private bitsAvailable: number = 0;\n\n constructor(windowBits: number, lookaheadBits: number) {\n this.windowBits = windowBits;\n this.lookaheadBits = lookaheadBits;\n this.windowSize = 1 << windowBits; // 2^windowBits\n\n this.window = Buffer.alloc(this.windowSize);\n this.inputBuffer = Buffer.alloc(0);\n }\n\n decompress(input: Buffer): Buffer {\n this.inputBuffer = input;\n this.inputIndex = 0;\n this.bitAccumulator = 0;\n this.bitsAvailable = 0;\n this.head = 0;\n this.state = HSState.TAG_BIT;\n\n const output: number[] = [];\n\n while (this.inputIndex < this.inputBuffer.length || this.bitsAvailable > 0) {\n const result = this.step();\n if (result === null) {\n break; // No more data\n }\n if (result >= 0) {\n output.push(result);\n }\n }\n\n return Buffer.from(output);\n }\n\n private step(): number | null {\n switch (this.state) {\n case HSState.TAG_BIT:\n return this.handleTagBit();\n case HSState.YIELD_LITERAL:\n return this.handleYieldLiteral();\n case HSState.BACKREF_INDEX_MSB:\n return this.handleBackrefIndexMSB();\n case HSState.BACKREF_INDEX_LSB:\n return this.handleBackrefIndexLSB();\n case HSState.BACKREF_COUNT_MSB:\n return this.handleBackrefCountMSB();\n case HSState.BACKREF_COUNT_LSB:\n return this.handleBackrefCountLSB();\n case HSState.YIELD_BACKREF:\n return this.handleYieldBackref();\n default:\n return null;\n }\n }\n\n private readBits(count: number): number | null {\n // Fill accumulator if needed\n while (this.bitsAvailable < count && this.inputIndex < this.inputBuffer.length) {\n this.bitAccumulator = (this.bitAccumulator << 8) | this.inputBuffer[this.inputIndex++];\n this.bitsAvailable += 8;\n }\n\n if (this.bitsAvailable < count) {\n return null; // Not enough bits\n }\n\n // Extract the requested bits\n this.bitsAvailable -= count;\n const result = (this.bitAccumulator >> this.bitsAvailable) & ((1 << count) - 1);\n this.bitAccumulator &= (1 << this.bitsAvailable) - 1;\n\n return result;\n }\n\n private handleTagBit(): number | null {\n const bit = this.readBits(1);\n if (bit === null) return null;\n\n if (bit === 1) {\n // Literal byte\n this.state = HSState.YIELD_LITERAL;\n } else {\n // Backref\n if (this.windowBits > 8) {\n this.state = HSState.BACKREF_INDEX_MSB;\n } else {\n this.outputIndex = 0;\n this.state = HSState.BACKREF_INDEX_LSB;\n }\n }\n return -1; // No output yet\n }\n\n private handleYieldLiteral(): number | null {\n const byte = this.readBits(8);\n if (byte === null) return null;\n\n this.pushByte(byte);\n this.state = HSState.TAG_BIT;\n return byte;\n }\n\n private handleBackrefIndexMSB(): number | null {\n const msb = this.readBits(this.windowBits - 8);\n if (msb === null) return null;\n\n this.outputIndex = msb << 8;\n this.state = HSState.BACKREF_INDEX_LSB;\n return -1;\n }\n\n private handleBackrefIndexLSB(): number | null {\n const bits = this.windowBits > 8 ? 8 : this.windowBits;\n const lsb = this.readBits(bits);\n if (lsb === null) return null;\n\n this.outputIndex |= lsb;\n this.outputCount = 0;\n\n if (this.lookaheadBits > 8) {\n this.state = HSState.BACKREF_COUNT_MSB;\n } else {\n this.state = HSState.BACKREF_COUNT_LSB;\n }\n return -1;\n }\n\n private handleBackrefCountMSB(): number | null {\n const msb = this.readBits(this.lookaheadBits - 8);\n if (msb === null) return null;\n\n this.outputCount = msb << 8;\n this.state = HSState.BACKREF_COUNT_LSB;\n return -1;\n }\n\n private handleBackrefCountLSB(): number | null {\n const bits = this.lookaheadBits > 8 ? 8 : this.lookaheadBits;\n const lsb = this.readBits(bits);\n if (lsb === null) return null;\n\n this.outputCount |= lsb;\n this.outputCount += 1; // Count is stored as length-1\n this.outputIndex += 1; // Index is stored as (actual_index - 1)\n\n this.state = HSState.YIELD_BACKREF;\n return -1;\n }\n\n private handleYieldBackref(): number | null {\n if (this.outputCount === 0) {\n this.state = HSState.TAG_BIT;\n return -1;\n }\n\n // Calculate the position in the window\n const pos = (this.head - this.outputIndex + this.windowSize) % this.windowSize;\n const byte = this.window[pos];\n\n this.pushByte(byte);\n this.outputCount--;\n\n if (this.outputCount === 0) {\n this.state = HSState.TAG_BIT;\n }\n\n return byte;\n }\n\n private pushByte(byte: number): void {\n this.window[this.head] = byte;\n this.head = (this.head + 1) % this.windowSize;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"heatshrink-decoder.js","names":[],"sources":["../../../src/utils/bgcode/heatshrink-decoder.ts"],"sourcesContent":["/**\n * Heatshrink LZSS decompressor implementation\n * Based on the Heatshrink compression library by Scott Vokes\n * https://github.com/atomicobject/heatshrink\n */\n\nenum HSState {\n TAG_BIT,\n YIELD_LITERAL,\n BACKREF_INDEX_MSB,\n BACKREF_INDEX_LSB,\n BACKREF_COUNT_MSB,\n BACKREF_COUNT_LSB,\n YIELD_BACKREF,\n}\n\nexport class HeatshrinkDecoder {\n private readonly windowBits: number;\n private readonly lookaheadBits: number;\n private readonly windowSize: number;\n\n private readonly window: Buffer;\n private head: number = 0;\n private state: HSState = HSState.TAG_BIT;\n\n private outputIndex: number = 0;\n private outputCount: number = 0;\n\n private inputBuffer: Buffer;\n private inputIndex: number = 0;\n private bitAccumulator: number = 0;\n private bitsAvailable: number = 0;\n\n constructor(windowBits: number, lookaheadBits: number) {\n this.windowBits = windowBits;\n this.lookaheadBits = lookaheadBits;\n this.windowSize = 1 << windowBits; // 2^windowBits\n\n this.window = Buffer.alloc(this.windowSize);\n this.inputBuffer = Buffer.alloc(0);\n }\n\n decompress(input: Buffer): Buffer {\n this.inputBuffer = input;\n this.inputIndex = 0;\n this.bitAccumulator = 0;\n this.bitsAvailable = 0;\n this.head = 0;\n this.state = HSState.TAG_BIT;\n\n const output: number[] = [];\n\n while (this.inputIndex < this.inputBuffer.length || this.bitsAvailable > 0) {\n const result = this.step();\n if (result === null) {\n break; // No more data\n }\n if (result >= 0) {\n output.push(result);\n }\n }\n\n return Buffer.from(output);\n }\n\n private step(): number | null {\n switch (this.state) {\n case HSState.TAG_BIT:\n return this.handleTagBit();\n case HSState.YIELD_LITERAL:\n return this.handleYieldLiteral();\n case HSState.BACKREF_INDEX_MSB:\n return this.handleBackrefIndexMSB();\n case HSState.BACKREF_INDEX_LSB:\n return this.handleBackrefIndexLSB();\n case HSState.BACKREF_COUNT_MSB:\n return this.handleBackrefCountMSB();\n case HSState.BACKREF_COUNT_LSB:\n return this.handleBackrefCountLSB();\n case HSState.YIELD_BACKREF:\n return this.handleYieldBackref();\n default:\n return null;\n }\n }\n\n private readBits(count: number): number | null {\n // Fill accumulator if needed\n while (this.bitsAvailable < count && this.inputIndex < this.inputBuffer.length) {\n this.bitAccumulator = (this.bitAccumulator << 8) | this.inputBuffer[this.inputIndex++];\n this.bitsAvailable += 8;\n }\n\n if (this.bitsAvailable < count) {\n return null; // Not enough bits\n }\n\n // Extract the requested bits\n this.bitsAvailable -= count;\n const result = (this.bitAccumulator >> this.bitsAvailable) & ((1 << count) - 1);\n this.bitAccumulator &= (1 << this.bitsAvailable) - 1;\n\n return result;\n }\n\n private handleTagBit(): number | null {\n const bit = this.readBits(1);\n if (bit === null) return null;\n\n if (bit === 1) {\n // Literal byte\n this.state = HSState.YIELD_LITERAL;\n } else {\n // Backref\n if (this.windowBits > 8) {\n this.state = HSState.BACKREF_INDEX_MSB;\n } else {\n this.outputIndex = 0;\n this.state = HSState.BACKREF_INDEX_LSB;\n }\n }\n return -1; // No output yet\n }\n\n private handleYieldLiteral(): number | null {\n const byte = this.readBits(8);\n if (byte === null) return null;\n\n this.pushByte(byte);\n this.state = HSState.TAG_BIT;\n return byte;\n }\n\n private handleBackrefIndexMSB(): number | null {\n const msb = this.readBits(this.windowBits - 8);\n if (msb === null) return null;\n\n this.outputIndex = msb << 8;\n this.state = HSState.BACKREF_INDEX_LSB;\n return -1;\n }\n\n private handleBackrefIndexLSB(): number | null {\n const bits = this.windowBits > 8 ? 8 : this.windowBits;\n const lsb = this.readBits(bits);\n if (lsb === null) return null;\n\n this.outputIndex |= lsb;\n this.outputCount = 0;\n\n if (this.lookaheadBits > 8) {\n this.state = HSState.BACKREF_COUNT_MSB;\n } else {\n this.state = HSState.BACKREF_COUNT_LSB;\n }\n return -1;\n }\n\n private handleBackrefCountMSB(): number | null {\n const msb = this.readBits(this.lookaheadBits - 8);\n if (msb === null) return null;\n\n this.outputCount = msb << 8;\n this.state = HSState.BACKREF_COUNT_LSB;\n return -1;\n }\n\n private handleBackrefCountLSB(): number | null {\n const bits = this.lookaheadBits > 8 ? 8 : this.lookaheadBits;\n const lsb = this.readBits(bits);\n if (lsb === null) return null;\n\n this.outputCount |= lsb;\n this.outputCount += 1; // Count is stored as length-1\n this.outputIndex += 1; // Index is stored as (actual_index - 1)\n\n this.state = HSState.YIELD_BACKREF;\n return -1;\n }\n\n private handleYieldBackref(): number | null {\n if (this.outputCount === 0) {\n this.state = HSState.TAG_BIT;\n return -1;\n }\n\n // Calculate the position in the window\n const pos = (this.head - this.outputIndex + this.windowSize) % this.windowSize;\n const byte = this.window[pos];\n\n this.pushByte(byte);\n this.outputCount--;\n\n if (this.outputCount === 0) {\n this.state = HSState.TAG_BIT;\n }\n\n return byte;\n }\n\n private pushByte(byte: number): void {\n this.window[this.head] = byte;\n this.head = (this.head + 1) % this.windowSize;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"png-encoder.js","names":[],"sources":["../../../src/utils/bgcode/png-encoder.ts"],"sourcesContent":["import { deflateSync } from \"node:zlib\";\n\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n\nfunction crc32(buffer: Buffer): number {\n let crc = 0xffffffff;\n for (const element of buffer) {\n crc ^= element;\n for (let j = 0; j < 8; j++) {\n crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));\n }\n }\n return (crc ^ 0xffffffff) >>> 0;\n}\n\nfunction writeChunk(type: string, data: Buffer): Buffer {\n const typeBuffer = Buffer.from(type, \"ascii\");\n const length = Buffer.alloc(4);\n length.writeUInt32BE(data.length, 0);\n\n const crcBuffer = Buffer.concat([typeBuffer, data]);\n const crc = Buffer.alloc(4);\n crc.writeUInt32BE(crc32(crcBuffer), 0);\n\n return Buffer.concat([length, typeBuffer, data, crc]);\n}\n\nexport function encodePNG(width: number, height: number, rgba: Buffer): Buffer {\n const ihdr = Buffer.alloc(13);\n ihdr.writeUInt32BE(width, 0);\n ihdr.writeUInt32BE(height, 4);\n ihdr.writeUInt8(8, 8); // bit depth\n ihdr.writeUInt8(6, 9); // color type (6 = RGBA)\n ihdr.writeUInt8(0, 10); // compression method\n ihdr.writeUInt8(0, 11); // filter method\n ihdr.writeUInt8(0, 12); // interlace method\n\n // Prepare image data with filter bytes (0 = no filter for each scanline)\n const bytesPerPixel = 4;\n const scanlineLength = width * bytesPerPixel;\n const filteredData = Buffer.alloc(height * (scanlineLength + 1));\n\n for (let y = 0; y < height; y++) {\n const scanlineOffset = y * (scanlineLength + 1);\n filteredData[scanlineOffset] = 0; // Filter type: None\n rgba.copy(filteredData, scanlineOffset + 1, y * scanlineLength, (y + 1) * scanlineLength);\n }\n\n const compressed = deflateSync(filteredData);\n const chunks = [\n PNG_SIGNATURE,\n writeChunk(\"IHDR\", ihdr),\n writeChunk(\"IDAT\", compressed),\n writeChunk(\"IEND\", Buffer.alloc(0)),\n ];\n\n return Buffer.concat(chunks);\n}\n"],"mappings":";;AAEA,MAAM,gBAAgB,OAAO,KAAK;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAK,CAAC;AAEnF,SAAS,MAAM,QAAwB;CACrC,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,QAAQ;AAC5B,SAAO;AACP,OAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IACrB,OAAO,QAAQ,IAAM,aAAa,EAAE,MAAM;;AAG9C,SAAQ,MAAM,gBAAgB;;AAGhC,SAAS,WAAW,MAAc,MAAsB;CACtD,MAAM,aAAa,OAAO,KAAK,MAAM,QAAQ;CAC7C,MAAM,SAAS,OAAO,MAAM,EAAE;AAC9B,QAAO,cAAc,KAAK,QAAQ,EAAE;CAEpC,MAAM,YAAY,OAAO,OAAO,CAAC,YAAY,KAAK,CAAC;CACnD,MAAM,MAAM,OAAO,MAAM,EAAE;AAC3B,KAAI,cAAc,MAAM,UAAU,EAAE,EAAE;AAEtC,QAAO,OAAO,OAAO;EAAC;EAAQ;EAAY;EAAM;EAAI,CAAC;;AAGvD,SAAgB,UAAU,OAAe,QAAgB,MAAsB;CAC7E,MAAM,OAAO,OAAO,MAAM,GAAG;AAC7B,MAAK,cAAc,OAAO,EAAE;AAC5B,MAAK,cAAc,QAAQ,EAAE;AAC7B,MAAK,WAAW,GAAG,EAAE;AACrB,MAAK,WAAW,GAAG,EAAE;AACrB,MAAK,WAAW,GAAG,GAAG;AACtB,MAAK,WAAW,GAAG,GAAG;AACtB,MAAK,WAAW,GAAG,GAAG;CAItB,MAAM,iBAAiB,QAAQ;CAC/B,MAAM,eAAe,OAAO,MAAM,UAAU,iBAAiB,GAAG;AAEhE,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,MAAM,iBAAiB,KAAK,iBAAiB;AAC7C,eAAa,kBAAkB;AAC/B,OAAK,KAAK,cAAc,iBAAiB,GAAG,IAAI,iBAAiB,IAAI,KAAK,eAAe;;CAG3F,MAAM,aAAa,YAAY,aAAa;CAC5C,MAAM,SAAS;EACb;EACA,WAAW,QAAQ,KAAK;EACxB,WAAW,QAAQ,WAAW;EAC9B,WAAW,QAAQ,OAAO,MAAM,EAAE,CAAC;EACpC;AAED,QAAO,OAAO,OAAO,OAAO"}
1
+ {"version":3,"file":"png-encoder.js","names":[],"sources":["../../../src/utils/bgcode/png-encoder.ts"],"sourcesContent":["import { deflateSync } from \"node:zlib\";\n\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n\nfunction crc32(buffer: Buffer): number {\n let crc = 0xffffffff;\n for (const element of buffer) {\n crc ^= element;\n for (let j = 0; j < 8; j++) {\n crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));\n }\n }\n return (crc ^ 0xffffffff) >>> 0;\n}\n\nfunction writeChunk(type: string, data: Buffer): Buffer {\n const typeBuffer = Buffer.from(type, \"ascii\");\n const length = Buffer.alloc(4);\n length.writeUInt32BE(data.length, 0);\n\n const crcBuffer = Buffer.concat([typeBuffer, data]);\n const crc = Buffer.alloc(4);\n crc.writeUInt32BE(crc32(crcBuffer), 0);\n\n return Buffer.concat([length, typeBuffer, data, crc]);\n}\n\nexport function encodePNG(width: number, height: number, rgba: Buffer): Buffer {\n const ihdr = Buffer.alloc(13);\n ihdr.writeUInt32BE(width, 0);\n ihdr.writeUInt32BE(height, 4);\n ihdr.writeUInt8(8, 8); // bit depth\n ihdr.writeUInt8(6, 9); // color type (6 = RGBA)\n ihdr.writeUInt8(0, 10); // compression method\n ihdr.writeUInt8(0, 11); // filter method\n ihdr.writeUInt8(0, 12); // interlace method\n\n // Prepare image data with filter bytes (0 = no filter for each scanline)\n const bytesPerPixel = 4;\n const scanlineLength = width * bytesPerPixel;\n const filteredData = Buffer.alloc(height * (scanlineLength + 1));\n\n for (let y = 0; y < height; y++) {\n const scanlineOffset = y * (scanlineLength + 1);\n filteredData[scanlineOffset] = 0; // Filter type: None\n rgba.copy(filteredData, scanlineOffset + 1, y * scanlineLength, (y + 1) * scanlineLength);\n }\n\n const compressed = deflateSync(filteredData);\n const chunks = [\n PNG_SIGNATURE,\n writeChunk(\"IHDR\", ihdr),\n writeChunk(\"IDAT\", compressed),\n writeChunk(\"IEND\", Buffer.alloc(0)),\n ];\n\n return Buffer.concat(chunks);\n}\n"],"mappings":";;AAEA,MAAM,gBAAgB,OAAO,KAAK;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAK,CAAC;AAEnF,SAAS,MAAM,QAAwB;CACrC,IAAI,MAAM;CACV,KAAK,MAAM,WAAW,QAAQ;EAC5B,OAAO;EACP,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACrB,MAAO,QAAQ,IAAM,aAAa,EAAE,MAAM;;CAG9C,QAAQ,MAAM,gBAAgB;;AAGhC,SAAS,WAAW,MAAc,MAAsB;CACtD,MAAM,aAAa,OAAO,KAAK,MAAM,QAAQ;CAC7C,MAAM,SAAS,OAAO,MAAM,EAAE;CAC9B,OAAO,cAAc,KAAK,QAAQ,EAAE;CAEpC,MAAM,YAAY,OAAO,OAAO,CAAC,YAAY,KAAK,CAAC;CACnD,MAAM,MAAM,OAAO,MAAM,EAAE;CAC3B,IAAI,cAAc,MAAM,UAAU,EAAE,EAAE;CAEtC,OAAO,OAAO,OAAO;EAAC;EAAQ;EAAY;EAAM;EAAI,CAAC;;AAGvD,SAAgB,UAAU,OAAe,QAAgB,MAAsB;CAC7E,MAAM,OAAO,OAAO,MAAM,GAAG;CAC7B,KAAK,cAAc,OAAO,EAAE;CAC5B,KAAK,cAAc,QAAQ,EAAE;CAC7B,KAAK,WAAW,GAAG,EAAE;CACrB,KAAK,WAAW,GAAG,EAAE;CACrB,KAAK,WAAW,GAAG,GAAG;CACtB,KAAK,WAAW,GAAG,GAAG;CACtB,KAAK,WAAW,GAAG,GAAG;CAItB,MAAM,iBAAiB,QAAQ;CAC/B,MAAM,eAAe,OAAO,MAAM,UAAU,iBAAiB,GAAG;CAEhE,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,MAAM,iBAAiB,KAAK,iBAAiB;EAC7C,aAAa,kBAAkB;EAC/B,KAAK,KAAK,cAAc,iBAAiB,GAAG,IAAI,iBAAiB,IAAI,KAAK,eAAe;;CAG3F,MAAM,aAAa,YAAY,aAAa;CAC5C,MAAM,SAAS;EACb;EACA,WAAW,QAAQ,KAAK;EACxB,WAAW,QAAQ,WAAW;EAC9B,WAAW,QAAQ,OAAO,MAAM,EAAE,CAAC;EACpC;CAED,OAAO,OAAO,OAAO,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"qoi-decoder.js","names":[],"sources":["../../../src/utils/bgcode/qoi-decoder.ts"],"sourcesContent":["interface QOIDecoded {\n width: number;\n height: number;\n channels: 3 | 4;\n colorspace: 0 | 1;\n data: Buffer;\n}\n\nconst QOI_OP_INDEX = 0x00; // 00xxxxxx\nconst QOI_OP_DIFF = 0x40; // 01xxxxxx\nconst QOI_OP_LUMA = 0x80; // 10xxxxxx\nconst QOI_OP_RUN = 0xc0; // 11xxxxxx\nconst QOI_OP_RGB = 0xfe; // 11111110\nconst QOI_OP_RGBA = 0xff; // 11111111\n\nconst QOI_MAGIC = 0x716f6966; // \"qoif\"\n\nfunction hashColor(r: number, g: number, b: number, a: number): number {\n return (r * 3 + g * 5 + b * 7 + a * 11) % 64;\n}\n\nexport function decodeQOI(buffer: Buffer): QOIDecoded {\n let pos = 0;\n\n // Read header (14 bytes)\n if (buffer.length < 14) {\n throw new Error(\"Invalid QOI file: too short\");\n }\n\n const magic = buffer.readUInt32BE(pos);\n pos += 4;\n if (magic !== QOI_MAGIC) {\n throw new Error(`Invalid QOI magic: expected ${QOI_MAGIC.toString(16)}, got ${magic.toString(16)}`);\n }\n\n const width = buffer.readUInt32BE(pos);\n pos += 4;\n const height = buffer.readUInt32BE(pos);\n pos += 4;\n const channels = buffer.readUInt8(pos) as 3 | 4;\n pos += 1;\n const colorspace = buffer.readUInt8(pos) as 0 | 1;\n pos += 1;\n\n if (channels !== 3 && channels !== 4) {\n throw new Error(`Invalid QOI channels: ${channels}`);\n }\n\n // Allocate output buffer\n const pixelCount = width * height;\n const outputSize = pixelCount * 4; // Always output RGBA\n const output = Buffer.alloc(outputSize);\n\n // Initialize state\n const colorArray: Array<[number, number, number, number]> = new Array(64);\n for (let i = 0; i < 64; i++) {\n colorArray[i] = [0, 0, 0, 0];\n }\n\n let r = 0;\n let g = 0;\n let b = 0;\n let a = 255;\n let outPos = 0;\n\n // Decode chunks\n while (outPos < outputSize) {\n // Check for end marker (7 bytes of 0x00 followed by 0x01)\n if (pos + 8 <= buffer.length) {\n let isEnd = true;\n for (let i = 0; i < 7; i++) {\n if (buffer[pos + i] !== 0x00) {\n isEnd = false;\n break;\n }\n }\n if (isEnd && buffer[pos + 7] === 0x01) {\n break;\n }\n }\n\n if (pos >= buffer.length) {\n throw new Error(\"Unexpected end of QOI data\");\n }\n\n const byte1 = buffer[pos++];\n\n if (byte1 === QOI_OP_RGB) {\n // QOI_OP_RGB: full RGB\n r = buffer[pos++];\n g = buffer[pos++];\n b = buffer[pos++];\n } else if (byte1 === QOI_OP_RGBA) {\n // QOI_OP_RGBA: full RGBA\n r = buffer[pos++];\n g = buffer[pos++];\n b = buffer[pos++];\n a = buffer[pos++];\n } else {\n const tag = byte1 & 0xc0;\n\n if (tag === QOI_OP_INDEX) {\n // QOI_OP_INDEX: 00xxxxxx\n const index = byte1 & 0x3f;\n [r, g, b, a] = colorArray[index];\n } else if (tag === QOI_OP_DIFF) {\n // QOI_OP_DIFF: 01xxxxxx\n const dr = ((byte1 >> 4) & 0x03) - 2;\n const dg = ((byte1 >> 2) & 0x03) - 2;\n const db = (byte1 & 0x03) - 2;\n r = (r + dr) & 0xff;\n g = (g + dg) & 0xff;\n b = (b + db) & 0xff;\n } else if (tag === QOI_OP_LUMA) {\n // QOI_OP_LUMA: 10xxxxxx\n const byte2 = buffer[pos++];\n const dg = (byte1 & 0x3f) - 32;\n const dr_dg = ((byte2 >> 4) & 0x0f) - 8;\n const db_dg = (byte2 & 0x0f) - 8;\n g = (g + dg) & 0xff;\n r = (r + dg + dr_dg) & 0xff;\n b = (b + dg + db_dg) & 0xff;\n } else if (tag === QOI_OP_RUN) {\n // QOI_OP_RUN: 11xxxxxx\n const run = (byte1 & 0x3f) + 1;\n for (let i = 0; i < run; i++) {\n output[outPos++] = r;\n output[outPos++] = g;\n output[outPos++] = b;\n output[outPos++] = a;\n }\n // Update color array for the last pixel in run\n const hash = hashColor(r, g, b, a);\n colorArray[hash] = [r, g, b, a];\n continue;\n }\n }\n\n // Write pixel\n output[outPos++] = r;\n output[outPos++] = g;\n output[outPos++] = b;\n output[outPos++] = a;\n\n // Update color array\n const hash = hashColor(r, g, b, a);\n colorArray[hash] = [r, g, b, a];\n }\n\n return {\n width,\n height,\n channels,\n colorspace,\n data: output,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"qoi-decoder.js","names":[],"sources":["../../../src/utils/bgcode/qoi-decoder.ts"],"sourcesContent":["interface QOIDecoded {\n width: number;\n height: number;\n channels: 3 | 4;\n colorspace: 0 | 1;\n data: Buffer;\n}\n\nconst QOI_OP_INDEX = 0x00; // 00xxxxxx\nconst QOI_OP_DIFF = 0x40; // 01xxxxxx\nconst QOI_OP_LUMA = 0x80; // 10xxxxxx\nconst QOI_OP_RUN = 0xc0; // 11xxxxxx\nconst QOI_OP_RGB = 0xfe; // 11111110\nconst QOI_OP_RGBA = 0xff; // 11111111\n\nconst QOI_MAGIC = 0x716f6966; // \"qoif\"\n\nfunction hashColor(r: number, g: number, b: number, a: number): number {\n return (r * 3 + g * 5 + b * 7 + a * 11) % 64;\n}\n\nexport function decodeQOI(buffer: Buffer): QOIDecoded {\n let pos = 0;\n\n // Read header (14 bytes)\n if (buffer.length < 14) {\n throw new Error(\"Invalid QOI file: too short\");\n }\n\n const magic = buffer.readUInt32BE(pos);\n pos += 4;\n if (magic !== QOI_MAGIC) {\n throw new Error(`Invalid QOI magic: expected ${QOI_MAGIC.toString(16)}, got ${magic.toString(16)}`);\n }\n\n const width = buffer.readUInt32BE(pos);\n pos += 4;\n const height = buffer.readUInt32BE(pos);\n pos += 4;\n const channels = buffer.readUInt8(pos) as 3 | 4;\n pos += 1;\n const colorspace = buffer.readUInt8(pos) as 0 | 1;\n pos += 1;\n\n if (channels !== 3 && channels !== 4) {\n throw new Error(`Invalid QOI channels: ${channels}`);\n }\n\n // Allocate output buffer\n const pixelCount = width * height;\n const outputSize = pixelCount * 4; // Always output RGBA\n const output = Buffer.alloc(outputSize);\n\n // Initialize state\n const colorArray: Array<[number, number, number, number]> = new Array(64);\n for (let i = 0; i < 64; i++) {\n colorArray[i] = [0, 0, 0, 0];\n }\n\n let r = 0;\n let g = 0;\n let b = 0;\n let a = 255;\n let outPos = 0;\n\n // Decode chunks\n while (outPos < outputSize) {\n // Check for end marker (7 bytes of 0x00 followed by 0x01)\n if (pos + 8 <= buffer.length) {\n let isEnd = true;\n for (let i = 0; i < 7; i++) {\n if (buffer[pos + i] !== 0x00) {\n isEnd = false;\n break;\n }\n }\n if (isEnd && buffer[pos + 7] === 0x01) {\n break;\n }\n }\n\n if (pos >= buffer.length) {\n throw new Error(\"Unexpected end of QOI data\");\n }\n\n const byte1 = buffer[pos++];\n\n if (byte1 === QOI_OP_RGB) {\n // QOI_OP_RGB: full RGB\n r = buffer[pos++];\n g = buffer[pos++];\n b = buffer[pos++];\n } else if (byte1 === QOI_OP_RGBA) {\n // QOI_OP_RGBA: full RGBA\n r = buffer[pos++];\n g = buffer[pos++];\n b = buffer[pos++];\n a = buffer[pos++];\n } else {\n const tag = byte1 & 0xc0;\n\n if (tag === QOI_OP_INDEX) {\n // QOI_OP_INDEX: 00xxxxxx\n const index = byte1 & 0x3f;\n [r, g, b, a] = colorArray[index];\n } else if (tag === QOI_OP_DIFF) {\n // QOI_OP_DIFF: 01xxxxxx\n const dr = ((byte1 >> 4) & 0x03) - 2;\n const dg = ((byte1 >> 2) & 0x03) - 2;\n const db = (byte1 & 0x03) - 2;\n r = (r + dr) & 0xff;\n g = (g + dg) & 0xff;\n b = (b + db) & 0xff;\n } else if (tag === QOI_OP_LUMA) {\n // QOI_OP_LUMA: 10xxxxxx\n const byte2 = buffer[pos++];\n const dg = (byte1 & 0x3f) - 32;\n const dr_dg = ((byte2 >> 4) & 0x0f) - 8;\n const db_dg = (byte2 & 0x0f) - 8;\n g = (g + dg) & 0xff;\n r = (r + dg + dr_dg) & 0xff;\n b = (b + dg + db_dg) & 0xff;\n } else if (tag === QOI_OP_RUN) {\n // QOI_OP_RUN: 11xxxxxx\n const run = (byte1 & 0x3f) + 1;\n for (let i = 0; i < run; i++) {\n output[outPos++] = r;\n output[outPos++] = g;\n output[outPos++] = b;\n output[outPos++] = a;\n }\n // Update color array for the last pixel in run\n const hash = hashColor(r, g, b, a);\n colorArray[hash] = [r, g, b, a];\n continue;\n }\n }\n\n // Write pixel\n output[outPos++] = r;\n output[outPos++] = g;\n output[outPos++] = b;\n output[outPos++] = a;\n\n // Update color array\n const hash = hashColor(r, g, b, a);\n colorArray[hash] = [r, g, b, a];\n }\n\n return {\n width,\n height,\n channels,\n colorspace,\n data: output,\n };\n}\n"],"mappings":"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"}