@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":"prusa-link-http-client.builder.js","names":[],"sources":["../../../../src/services/prusa-link/utils/prusa-link-http-client.builder.ts"],"sourcesContent":["import { DefaultHttpClientBuilder } from \"@/shared/default-http-client.builder\";\nimport {\n authorizationHeaderKey,\n wwwAuthenticationHeaderKey,\n} from \"@/services/octoprint/constants/octoprint-service.constants\";\nimport { AxiosError, AxiosInstance, AxiosRequestConfig } from \"axios\";\nimport { generateDigestAuthHeader } from \"./digest-auth.util\";\nimport { randomBytes } from \"node:crypto\";\n\nexport interface DigestAuthInfo {\n realm: string;\n nonce: string;\n qop?: string;\n hasQop: boolean;\n}\n\nexport class PrusaLinkHttpClientBuilder extends DefaultHttpClientBuilder {\n private readonly maxRetries: number = 1;\n private username?: string;\n private password?: string;\n private authHeaderContext?: DigestAuthInfo;\n private onAuthError?: (error: AxiosError) => void;\n private onAuthSuccess?: (authHeader: string) => void;\n private onRequestRetry?: (error: AxiosError, attemptCount: number) => void;\n\n public override build<D = any>(): AxiosInstance {\n if (!this.axiosOptions.baseURL) {\n throw new Error(\"Base URL is required\");\n }\n\n const axiosInstance = super.build<D>();\n\n // Add request interceptor for digest auth\n if (this.username && this.password) {\n axiosInstance.interceptors.request.use(async (config) => {\n // If we have auth info, add the digest header\n if (this.authHeaderContext) {\n const computedDigestHeader = this.generateDigestHeader(\n config.method?.toUpperCase() ?? \"GET\",\n config.url ?? \"/\",\n );\n\n config.headers[authorizationHeaderKey] = computedDigestHeader;\n }\n return config;\n });\n\n // Add response interceptor to handle 401s\n axiosInstance.interceptors.response.use(\n (response) => response,\n async (error: AxiosError) => {\n const originalRequest = error.config as AxiosRequestConfig & { _retryCount?: number };\n\n // If we get a 401 and have credentials but no auth info or retried less than once\n if (\n error.response?.status === 401 &&\n this.username?.length &&\n this.password?.length &&\n (!originalRequest._retryCount || originalRequest._retryCount < this.maxRetries)\n ) {\n // Extract WWW-Authenticate header\n const wwwAuthHeader = error.response.headers[wwwAuthenticationHeaderKey] as string;\n if (wwwAuthHeader) {\n // Allow caching the value for reuse\n if (typeof this.onAuthSuccess === \"function\") {\n this.onAuthSuccess(wwwAuthHeader);\n }\n\n this.saveParsedAuthHeaderContext(wwwAuthHeader);\n originalRequest._retryCount = (originalRequest._retryCount ?? 0) + 1;\n\n if (typeof this.onRequestRetry === \"function\") {\n this.onRequestRetry(error, originalRequest._retryCount);\n }\n return axiosInstance(originalRequest);\n }\n }\n\n // If this is an auth error, and we have a callback, invoke it\n if (error.response?.status === 401 && this.onAuthError && typeof this.onAuthError === \"function\") {\n this.onAuthError(error);\n }\n\n // If we can't handle it, pass the error on\n return Promise.reject(error);\n },\n );\n }\n\n return axiosInstance;\n }\n\n public withDigestAuth(\n username?: string,\n password?: string,\n onAuthError?: (error: AxiosError) => void,\n onRequestRetry?: (error: AxiosError, attemptCount: number) => void,\n onAuthSuccess?: (authHeader: string) => void,\n ): this {\n if (!username?.length) {\n throw new Error(\"username may not be an empty string\");\n }\n if (!password?.length) {\n throw new Error(\"password may not be an empty string\");\n }\n if (onAuthError && typeof onAuthError !== \"function\") {\n throw new Error(\"onAuthError must be a function\");\n }\n if (onAuthSuccess && typeof onAuthSuccess !== \"function\") {\n throw new Error(\"onAuthSuccess must be a function\");\n }\n if (onRequestRetry && typeof onRequestRetry !== \"function\") {\n throw new Error(\"onRequestRetry must be a function\");\n }\n\n this.username = username;\n this.password = password;\n this.onAuthError = onAuthError;\n this.onRequestRetry = onRequestRetry;\n this.onAuthSuccess = onAuthSuccess;\n\n return this;\n }\n\n public withAuthHeader(authHeader: string): this {\n if (!authHeader?.length) {\n throw new Error(\"Digest header may not be an empty string\");\n }\n\n this.saveParsedAuthHeaderContext(authHeader);\n return this;\n }\n\n private saveParsedAuthHeaderContext(authHeader: string): void {\n const headerValue = authHeader.startsWith(\"Digest \") ? authHeader.substring(7) : authHeader;\n\n const authParams = Object.fromEntries(\n headerValue.split(\", \").map((param) => {\n const parts = param.split(\"=\");\n if (parts.length === 2) {\n return [parts[0], parts[1].replace(/\"/g, \"\")];\n }\n return [parts[0], \"\"];\n }),\n );\n\n this.authHeaderContext = {\n realm: authParams.realm,\n nonce: authParams.nonce,\n qop: authParams.qop,\n hasQop: \"qop\" in authParams,\n };\n }\n\n private generateDigestHeader(method: string, uri: string): string {\n if (!this.authHeaderContext || !this.username || !this.password) {\n throw new Error(\"Digest auth not properly configured\");\n }\n\n const { realm, nonce, qop, hasQop } = this.authHeaderContext;\n\n return generateDigestAuthHeader({\n username: this.username,\n password: this.password,\n method,\n uri,\n realm,\n nonce,\n qop: hasQop ? qop : undefined,\n nc: hasQop ? \"00000001\" : undefined, // For simplicity, always use 00000001\n cnonce: hasQop ? randomBytes(8).toString(\"hex\") : undefined,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"prusa-link-http-client.builder.js","names":[],"sources":["../../../../src/services/prusa-link/utils/prusa-link-http-client.builder.ts"],"sourcesContent":["import { DefaultHttpClientBuilder } from \"@/shared/default-http-client.builder\";\nimport {\n authorizationHeaderKey,\n wwwAuthenticationHeaderKey,\n} from \"@/services/octoprint/constants/octoprint-service.constants\";\nimport { AxiosError, AxiosInstance, AxiosRequestConfig } from \"axios\";\nimport { generateDigestAuthHeader } from \"./digest-auth.util\";\nimport { randomBytes } from \"node:crypto\";\n\nexport interface DigestAuthInfo {\n realm: string;\n nonce: string;\n qop?: string;\n hasQop: boolean;\n opaque?: string;\n algorithm?: string;\n}\n\nexport class PrusaLinkHttpClientBuilder extends DefaultHttpClientBuilder {\n private readonly maxRetries: number = 1;\n private username?: string;\n private password?: string;\n private authHeaderContext?: DigestAuthInfo;\n private onAuthError?: (error: AxiosError) => void;\n private onAuthSuccess?: (authHeader: string) => void;\n private onRequestRetry?: (error: AxiosError, attemptCount: number) => void;\n\n public override build<D = any>(): AxiosInstance {\n if (!this.axiosOptions.baseURL) {\n throw new Error(\"Base URL is required\");\n }\n\n const axiosInstance = super.build<D>();\n\n // Add request interceptor for digest auth\n if (this.username && this.password) {\n axiosInstance.interceptors.request.use(async (config) => {\n if (this.authHeaderContext) {\n const method = config.method?.toUpperCase() ?? \"GET\";\n const rawUrl = config.url ?? \"/\";\n let uri = rawUrl;\n\n if (rawUrl.startsWith(\"http://\") || rawUrl.startsWith(\"https://\")) {\n try {\n const parsed = new URL(rawUrl);\n uri = `${parsed.pathname}${parsed.search ?? \"\"}`;\n } catch {\n // If URL parsing fails, use the raw URL\n }\n }\n\n config.headers[authorizationHeaderKey] = this.generateDigestHeader(method, uri);\n }\n return config;\n });\n\n axiosInstance.interceptors.response.use(\n (response) => response,\n async (error: AxiosError) => {\n const originalRequest = error.config as AxiosRequestConfig & { _retryCount?: number };\n\n if (\n error.response?.status === 401 &&\n this.username?.length &&\n this.password?.length &&\n (!originalRequest._retryCount || originalRequest._retryCount < this.maxRetries)\n ) {\n const wwwAuthHeader = error.response.headers[wwwAuthenticationHeaderKey] as string;\n if (wwwAuthHeader) {\n if (typeof this.onAuthSuccess === \"function\") {\n this.onAuthSuccess(wwwAuthHeader);\n }\n\n this.saveParsedAuthHeaderContext(wwwAuthHeader);\n originalRequest._retryCount = (originalRequest._retryCount ?? 0) + 1;\n\n if (typeof this.onRequestRetry === \"function\") {\n this.onRequestRetry(error, originalRequest._retryCount);\n }\n return axiosInstance(originalRequest);\n }\n }\n\n if (error.response?.status === 401 && this.onAuthError && typeof this.onAuthError === \"function\") {\n this.onAuthError(error);\n }\n\n return Promise.reject(error);\n },\n );\n }\n\n return axiosInstance;\n }\n\n public withDigestAuth(\n username?: string,\n password?: string,\n onAuthError?: (error: AxiosError) => void,\n onRequestRetry?: (error: AxiosError, attemptCount: number) => void,\n onAuthSuccess?: (authHeader: string) => void,\n ): this {\n if (!username?.length) {\n throw new Error(\"username may not be an empty string\");\n }\n if (!password?.length) {\n throw new Error(\"password may not be an empty string\");\n }\n if (onAuthError && typeof onAuthError !== \"function\") {\n throw new Error(\"onAuthError must be a function\");\n }\n if (onAuthSuccess && typeof onAuthSuccess !== \"function\") {\n throw new Error(\"onAuthSuccess must be a function\");\n }\n if (onRequestRetry && typeof onRequestRetry !== \"function\") {\n throw new Error(\"onRequestRetry must be a function\");\n }\n\n this.username = username;\n this.password = password;\n this.onAuthError = onAuthError;\n this.onRequestRetry = onRequestRetry;\n this.onAuthSuccess = onAuthSuccess;\n\n return this;\n }\n\n public withAuthHeader(authHeader: string): this {\n if (!authHeader?.length) {\n throw new Error(\"Digest header may not be an empty string\");\n }\n\n this.saveParsedAuthHeaderContext(authHeader);\n return this;\n }\n\n private saveParsedAuthHeaderContext(authHeader: string): void {\n const cleanedHeader = authHeader.trim();\n const headerValue = cleanedHeader.startsWith(\"Digest \") ? cleanedHeader.substring(7) : cleanedHeader;\n\n const tokens: string[] = [];\n let current = \"\";\n let inQuotes = false;\n\n for (const ch of headerValue) {\n if (ch === '\"') {\n inQuotes = !inQuotes;\n current += ch;\n continue;\n }\n if (ch === \",\" && !inQuotes) {\n if (current.trim().length) {\n tokens.push(current.trim());\n }\n current = \"\";\n continue;\n }\n current += ch;\n }\n if (current.trim().length) {\n tokens.push(current.trim());\n }\n\n const authParams = Object.fromEntries(\n tokens.map((param) => {\n const idx = param.indexOf(\"=\");\n if (idx === -1) {\n return [param.trim(), \"\"];\n }\n const key = param.slice(0, idx).trim();\n let value = param.slice(idx + 1).trim();\n // Remove quotes if present\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n return [key, value];\n }),\n );\n\n const qopRaw = authParams.qop;\n const qop = qopRaw\n ? (qopRaw\n .split(\",\")\n .map((q) => q.trim())\n .find((q) => q === \"auth\") ?? qopRaw.split(\",\")[0].trim())\n : undefined;\n\n this.authHeaderContext = {\n realm: authParams.realm,\n nonce: authParams.nonce,\n qop,\n hasQop: !!qop,\n opaque: authParams.opaque,\n algorithm: authParams.algorithm,\n };\n }\n\n private generateDigestHeader(method: string, uri: string): string {\n if (!this.authHeaderContext || !this.username || !this.password) {\n throw new Error(\"Digest auth not properly configured\");\n }\n\n const { realm, nonce, qop, hasQop, opaque, algorithm } = this.authHeaderContext;\n const needsCnonce = hasQop || algorithm?.toLowerCase() === \"md5-sess\";\n const cnonce = needsCnonce ? randomBytes(8).toString(\"hex\") : undefined;\n\n return generateDigestAuthHeader({\n username: this.username,\n password: this.password,\n method,\n uri,\n realm,\n nonce,\n qop: hasQop ? qop : undefined,\n nc: hasQop ? \"00000001\" : undefined,\n cnonce,\n opaque,\n algorithm,\n });\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"socket.factory.js","names":[],"sources":["../../src/services/socket.factory.ts"],"sourcesContent":["import { DITokens } from \"@/container.tokens\";\nimport { MoonrakerType, OctoprintType, PrusaLinkType, BambuType } from \"@/services/printer-api.interface\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport type { IWebsocketAdapter } from \"@/services/websocket-adapter.interface\";\nimport { CradleService } from \"@/services/core/cradle.service\";\n\nexport class SocketFactory {\n constructor(private readonly cradleService: CradleService) {}\n\n createInstance(printerType: number): IWebsocketAdapter {\n const settingsStore = this.cradleService.resolve<SettingsStore>(DITokens.settingsStore);\n const serverSettings = settingsStore.getServerSettings();\n const moonrakerSupport = serverSettings.experimentalMoonrakerSupport;\n const prusaLinkSupport = serverSettings.experimentalPrusaLinkSupport;\n const bambuSupport = serverSettings.experimentalBambuSupport;\n\n if (printerType === OctoprintType) {\n return this.cradleService.resolve(DITokens.octoPrintSockIoAdapter);\n } else if (moonrakerSupport && printerType === MoonrakerType) {\n return this.cradleService.resolve(DITokens.moonrakerWebsocketAdapter);\n } else if (prusaLinkSupport && printerType === PrusaLinkType) {\n return this.cradleService.resolve(DITokens.prusaLinkPollingAdapter);\n } else if (bambuSupport && printerType === BambuType) {\n return this.cradleService.resolve(DITokens.bambuMqttAdapter);\n } else {\n throw new Error(\"PrinterType is unknown, cant pick the right socket adapter\");\n }\n }\n}\n"],"mappings":";;;AAMA,IAAa,gBAAb,MAA2B;CACzB,YAAY,eAA+C;AAA9B,OAAA,gBAAA;;CAE7B,eAAe,aAAwC;EAErD,MAAM,iBADgB,KAAK,cAAc,QAAuB,SAAS,cACrC,CAAC,mBAAmB;EACxD,MAAM,mBAAmB,eAAe;EACxC,MAAM,mBAAmB,eAAe;EACxC,MAAM,eAAe,eAAe;AAEpC,MAAI,gBAAA,EACF,QAAO,KAAK,cAAc,QAAQ,SAAS,uBAAuB;WACzD,oBAAoB,gBAAA,EAC7B,QAAO,KAAK,cAAc,QAAQ,SAAS,0BAA0B;WAC5D,oBAAoB,gBAAA,EAC7B,QAAO,KAAK,cAAc,QAAQ,SAAS,wBAAwB;WAC1D,gBAAgB,gBAAA,EACzB,QAAO,KAAK,cAAc,QAAQ,SAAS,iBAAiB;MAE5D,OAAM,IAAI,MAAM,6DAA6D"}
1
+ {"version":3,"file":"socket.factory.js","names":[],"sources":["../../src/services/socket.factory.ts"],"sourcesContent":["import { DITokens } from \"@/container.tokens\";\nimport { MoonrakerType, OctoprintType, PrusaLinkType, BambuType } from \"@/services/printer-api.interface\";\nimport { SettingsStore } from \"@/state/settings.store\";\nimport type { IWebsocketAdapter } from \"@/services/websocket-adapter.interface\";\nimport { CradleService } from \"@/services/core/cradle.service\";\n\nexport class SocketFactory {\n constructor(private readonly cradleService: CradleService) {}\n\n createInstance(printerType: number): IWebsocketAdapter {\n const settingsStore = this.cradleService.resolve<SettingsStore>(DITokens.settingsStore);\n const serverSettings = settingsStore.getServerSettings();\n const moonrakerSupport = serverSettings.experimentalMoonrakerSupport;\n const prusaLinkSupport = serverSettings.experimentalPrusaLinkSupport;\n const bambuSupport = serverSettings.experimentalBambuSupport;\n\n if (printerType === OctoprintType) {\n return this.cradleService.resolve(DITokens.octoPrintSockIoAdapter);\n } else if (moonrakerSupport && printerType === MoonrakerType) {\n return this.cradleService.resolve(DITokens.moonrakerWebsocketAdapter);\n } else if (prusaLinkSupport && printerType === PrusaLinkType) {\n return this.cradleService.resolve(DITokens.prusaLinkPollingAdapter);\n } else if (bambuSupport && printerType === BambuType) {\n return this.cradleService.resolve(DITokens.bambuMqttAdapter);\n } else {\n throw new Error(\"PrinterType is unknown, cant pick the right socket adapter\");\n }\n }\n}\n"],"mappings":";;;AAMA,IAAa,gBAAb,MAA2B;CACzB,YAAY,eAA+C;EAA9B,KAAA,gBAAA;;CAE7B,eAAe,aAAwC;EAErD,MAAM,iBADgB,KAAK,cAAc,QAAuB,SAAS,cACrC,CAAC,mBAAmB;EACxD,MAAM,mBAAmB,eAAe;EACxC,MAAM,mBAAmB,eAAe;EACxC,MAAM,eAAe,eAAe;EAEpC,IAAI,gBAAA,GACF,OAAO,KAAK,cAAc,QAAQ,SAAS,uBAAuB;OAC7D,IAAI,oBAAoB,gBAAA,GAC7B,OAAO,KAAK,cAAc,QAAQ,SAAS,0BAA0B;OAChE,IAAI,oBAAoB,gBAAA,GAC7B,OAAO,KAAK,cAAc,QAAQ,SAAS,wBAAwB;OAC9D,IAAI,gBAAgB,gBAAA,GACzB,OAAO,KAAK,cAAc,QAAQ,SAAS,iBAAiB;OAE5D,MAAM,IAAI,MAAM,6DAA6D"}
@@ -1 +1 @@
1
- {"version":3,"file":"task-manager.service.js","names":[],"sources":["../../src/services/task-manager.service.ts"],"sourcesContent":["import { AsyncTask, SimpleIntervalJob, ToadScheduler } from \"toad-scheduler\";\nimport { AwilixResolutionError } from \"awilix\";\nimport { JobValidationException } from \"@/exceptions/job.exceptions\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { CradleService } from \"@/services/core/cradle.service\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { TaskRegistration, TaskSchedulerOptions, TaskService } from \"./interfaces/task.interfaces\";\nimport { DITokens } from \"@/container.tokens\";\nimport { errorSummary } from \"@/utils/error.utils\";\n\n/**\n * Internal state of a registered task\n */\ninterface TaskState {\n options: TaskSchedulerOptions;\n timedTask: AsyncTask;\n job?: SimpleIntervalJob;\n started?: number;\n duration?: number;\n firstCompletion?: number;\n lastError?: {\n time: number;\n error: Error;\n };\n}\n\nexport class TaskManagerService {\n private taskStates: Record<string, TaskState> = {};\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly cradleService: CradleService,\n private readonly toadScheduler: ToadScheduler,\n ) {\n this.logger = loggerFactory(TaskManagerService.name);\n }\n\n /**\n * Create a recurring job or one-time task\n * @param registration Task registration parameters\n */\n registerJobOrTask(registration: TaskRegistration): void {\n const { id: taskId, task: serviceIdentifier, preset: schedulerOptions } = registration;\n\n try {\n this.validateInput(taskId, serviceIdentifier, schedulerOptions);\n } catch (e) {\n this.logger.error(errorSummary(e), schedulerOptions);\n return;\n }\n\n const timedTask = this.getSafeTimedTask(taskId, serviceIdentifier);\n\n this.taskStates[taskId] = {\n options: schedulerOptions,\n timedTask,\n };\n\n if (schedulerOptions.runOnce) {\n timedTask.execute();\n } else if (schedulerOptions.runDelayed) {\n const delay = (schedulerOptions.milliseconds ?? 0) + (schedulerOptions.seconds ?? 0) * 1000;\n this.runTimeoutTaskInstance(taskId, delay);\n } else {\n // This must be 'periodic'\n this.scheduleEnabledPeriodicJob(taskId);\n }\n }\n\n /**\n * Enable the job which must be disabled at boot. Handy for conditional, heavy or long-running non-critical tasks\n * @param taskId Task identifier\n * @param failIfEnabled throws when the job is already running\n */\n scheduleDisabledJob(taskId: string, failIfEnabled = true): void {\n const taskState = this.getTaskState(taskId);\n const schedulerOptions = taskState?.options;\n\n if (schedulerOptions?.disabled !== true) {\n if (failIfEnabled) {\n throw new JobValidationException(\n `The requested task with ID ${taskId} was not explicitly disabled and must be running already.`,\n taskId,\n );\n }\n return;\n }\n\n taskState.options.disabled = false;\n this.scheduleEnabledPeriodicJob(taskId);\n }\n\n /**\n * Disable a running job\n * @param taskId Task identifier\n * @param failIfDisabled throws when the job is already disabled\n */\n disableJob(taskId: string, failIfDisabled = true): void {\n if (this.isTaskDisabled(taskId)) {\n if (failIfDisabled) {\n throw new JobValidationException(\"Can't disable a job which is already disabled\", taskId);\n }\n return;\n }\n\n const taskState = this.getTaskState(taskId);\n taskState.options.disabled = true;\n taskState.job?.stop();\n }\n\n /**\n * Check if a task is currently disabled\n * @param taskId Task identifier\n * @returns true if task is disabled\n */\n isTaskDisabled(taskId: string): boolean {\n return !!this.getTaskState(taskId).options.disabled;\n }\n\n /**\n * Remove a task from the scheduler and internal registry\n * @param taskId Task identifier\n */\n deregisterTask(taskId: string): void {\n this.getTaskState(taskId); // Validates task exists\n delete this.taskStates[taskId];\n this.toadScheduler.removeById(taskId);\n }\n\n /**\n * Get the internal state of a task\n * @param taskId Task identifier\n * @returns Task state\n */\n getTaskState(taskId: string): TaskState {\n const taskState = this.taskStates[taskId];\n if (!taskState) {\n throw new JobValidationException(`The requested task with ID ${taskId} was not registered`, taskId);\n }\n return taskState;\n }\n\n /**\n * Execute a task after a delay\n * @param taskId Task identifier\n * @param timeoutMs Delay in milliseconds\n */\n runTimeoutTaskInstance(taskId: string, timeoutMs: number): void {\n const taskState = this.getTaskState(taskId);\n this.logger.log(`Running delayed task '${taskId}' in ${timeoutMs}ms`);\n setTimeout(() => taskState.timedTask.execute(), timeoutMs);\n }\n\n /**\n * Stop all scheduled tasks\n */\n stopSchedulerTasks(): void {\n this.toadScheduler.stop();\n }\n\n /**\n * Validates task registration inputs\n */\n validateInput(taskId: string, serviceIdentifier: string, schedulerOptions: TaskSchedulerOptions): void {\n if (!taskId) {\n throw new JobValidationException(\"Task ID was not provided. Can't register task or schedule job.\", taskId);\n }\n\n const serviceName = serviceIdentifier || \"unknown\";\n const prefix = `Job '${schedulerOptions?.name ?? serviceName}' with ID '${taskId}'`;\n\n if (this.taskStates[taskId]) {\n throw new JobValidationException(`${prefix} was already registered. Can't register a key twice.`, taskId);\n }\n\n let resolvedService: TaskService;\n try {\n resolvedService = this.cradleService.resolve<TaskService>(serviceIdentifier as keyof typeof DITokens);\n } catch (e) {\n if (e instanceof AwilixResolutionError) {\n throw new JobValidationException(\n `${prefix} had an awilix dependency resolution error. It can't be scheduled without fixing this problem. Inner error:\\n` +\n e.stack,\n taskId,\n );\n } else {\n throw new JobValidationException(\n `${prefix} is not a registered awilix dependency. It can't be scheduled. Error:\\n` + (e as Error).stack,\n taskId,\n );\n }\n }\n\n if (typeof resolvedService?.run !== \"function\") {\n throw new JobValidationException(`${prefix} was resolved but it doesn't have a 'run()' method to call.`, taskId);\n }\n\n if (!schedulerOptions?.periodic && !schedulerOptions?.runOnce && !schedulerOptions?.runDelayed) {\n throw new JobValidationException(`${prefix} Provide 'periodic', 'runOnce', or 'runDelayed' option.`, taskId);\n }\n\n if (!schedulerOptions?.periodic && !!schedulerOptions.disabled) {\n throw new JobValidationException(`${prefix} Only tasks of type 'periodic' can be disabled at boot.`, taskId);\n }\n\n if (schedulerOptions?.runDelayed && !schedulerOptions.milliseconds && !schedulerOptions.seconds) {\n throw new JobValidationException(`${prefix} Provide a delayed timing parameter (milliseconds|seconds)`, taskId);\n }\n\n if (\n schedulerOptions?.periodic &&\n !schedulerOptions.milliseconds &&\n !schedulerOptions.seconds &&\n !schedulerOptions.minutes &&\n !schedulerOptions.hours &&\n !schedulerOptions.days\n ) {\n throw new JobValidationException(\n `${prefix} Provide a periodic timing parameter (milliseconds|seconds|minutes|hours|days)`,\n taskId,\n );\n }\n }\n\n /**\n * Create a safe timed task with error handling\n * @param taskId Task identifier\n * @param serviceIdentifier Service to resolve and execute\n * @returns AsyncTask instance\n */\n private getSafeTimedTask(taskId: string, serviceIdentifier: string): AsyncTask {\n const asyncHandler = async (): Promise<void> => {\n await this.timeTask(taskId, serviceIdentifier);\n };\n\n return new AsyncTask(taskId, asyncHandler, this.getErrorHandler(taskId));\n }\n\n /**\n * Execute a task and measure its execution time\n * @param taskId Task identifier\n * @param serviceIdentifier Service to resolve and execute\n */\n private async timeTask(taskId: string, serviceIdentifier: string): Promise<void> {\n const taskState = this.taskStates[taskId];\n taskState.started = Date.now();\n\n const taskService = this.cradleService.resolve<TaskService>(serviceIdentifier as keyof typeof DITokens);\n await taskService.run();\n\n taskState.duration = Date.now() - taskState.started;\n\n if (taskState.options?.logFirstCompletion !== false && !taskState?.firstCompletion) {\n this.logger.log(`Task '${taskId}' first completion. Duration ${taskState.duration}ms`);\n taskState.firstCompletion = Date.now();\n }\n }\n\n /**\n * Create an error handler for a task\n * @param taskId Task identifier\n * @returns Error handler function\n */\n private getErrorHandler(taskId: string): (error: Error) => void {\n return (error: Error): void => {\n const taskState = this.taskStates[taskId];\n\n taskState.lastError ??= {\n time: Date.now(),\n error,\n };\n\n this.logger.error(`Task '${taskId}' threw an exception: ${error.stack}`);\n };\n }\n\n /**\n * Schedule a periodic job that's not disabled\n * @param taskId Task identifier\n */\n private scheduleEnabledPeriodicJob(taskId: string): void {\n const taskState = this.getTaskState(taskId);\n\n if (!taskState?.timedTask || !taskState?.options) {\n throw new JobValidationException(\n `The requested task with ID ${taskId} was not registered properly ('timedTask' or 'options' missing).`,\n taskId,\n );\n }\n\n const schedulerOptions = taskState.options;\n const timedTask = taskState.timedTask;\n\n if (!schedulerOptions?.periodic) {\n throw new JobValidationException(\n `The requested task with ID ${taskId} is not periodic and cannot be enabled.`,\n taskId,\n );\n }\n\n if (!schedulerOptions.disabled) {\n this.logger.log(`Task '${taskId}' was scheduled (runImmediately: ${!!schedulerOptions.runImmediately}).`);\n const job = new SimpleIntervalJob(schedulerOptions, timedTask);\n taskState.job = job;\n this.toadScheduler.addSimpleIntervalJob(job);\n } else {\n this.logger.log(`Task '${taskId}' was marked as disabled (deferred execution).`);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"task-manager.service.js","names":[],"sources":["../../src/services/task-manager.service.ts"],"sourcesContent":["import { AsyncTask, SimpleIntervalJob, ToadScheduler } from \"toad-scheduler\";\nimport { AwilixResolutionError } from \"awilix\";\nimport { JobValidationException } from \"@/exceptions/job.exceptions\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { CradleService } from \"@/services/core/cradle.service\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { TaskRegistration, TaskSchedulerOptions, TaskService } from \"./interfaces/task.interfaces\";\nimport { DITokens } from \"@/container.tokens\";\nimport { errorSummary } from \"@/utils/error.utils\";\n\n/**\n * Internal state of a registered task\n */\ninterface TaskState {\n options: TaskSchedulerOptions;\n timedTask: AsyncTask;\n job?: SimpleIntervalJob;\n started?: number;\n duration?: number;\n firstCompletion?: number;\n lastError?: {\n time: number;\n error: Error;\n };\n}\n\nexport class TaskManagerService {\n private taskStates: Record<string, TaskState> = {};\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly cradleService: CradleService,\n private readonly toadScheduler: ToadScheduler,\n ) {\n this.logger = loggerFactory(TaskManagerService.name);\n }\n\n /**\n * Create a recurring job or one-time task\n * @param registration Task registration parameters\n */\n registerJobOrTask(registration: TaskRegistration): void {\n const { id: taskId, task: serviceIdentifier, preset: schedulerOptions } = registration;\n\n try {\n this.validateInput(taskId, serviceIdentifier, schedulerOptions);\n } catch (e) {\n this.logger.error(errorSummary(e), schedulerOptions);\n return;\n }\n\n const timedTask = this.getSafeTimedTask(taskId, serviceIdentifier);\n\n this.taskStates[taskId] = {\n options: schedulerOptions,\n timedTask,\n };\n\n if (schedulerOptions.runOnce) {\n timedTask.execute();\n } else if (schedulerOptions.runDelayed) {\n const delay = (schedulerOptions.milliseconds ?? 0) + (schedulerOptions.seconds ?? 0) * 1000;\n this.runTimeoutTaskInstance(taskId, delay);\n } else {\n // This must be 'periodic'\n this.scheduleEnabledPeriodicJob(taskId);\n }\n }\n\n /**\n * Enable the job which must be disabled at boot. Handy for conditional, heavy or long-running non-critical tasks\n * @param taskId Task identifier\n * @param failIfEnabled throws when the job is already running\n */\n scheduleDisabledJob(taskId: string, failIfEnabled = true): void {\n const taskState = this.getTaskState(taskId);\n const schedulerOptions = taskState?.options;\n\n if (schedulerOptions?.disabled !== true) {\n if (failIfEnabled) {\n throw new JobValidationException(\n `The requested task with ID ${taskId} was not explicitly disabled and must be running already.`,\n taskId,\n );\n }\n return;\n }\n\n taskState.options.disabled = false;\n this.scheduleEnabledPeriodicJob(taskId);\n }\n\n /**\n * Disable a running job\n * @param taskId Task identifier\n * @param failIfDisabled throws when the job is already disabled\n */\n disableJob(taskId: string, failIfDisabled = true): void {\n if (this.isTaskDisabled(taskId)) {\n if (failIfDisabled) {\n throw new JobValidationException(\"Can't disable a job which is already disabled\", taskId);\n }\n return;\n }\n\n const taskState = this.getTaskState(taskId);\n taskState.options.disabled = true;\n taskState.job?.stop();\n }\n\n /**\n * Check if a task is currently disabled\n * @param taskId Task identifier\n * @returns true if task is disabled\n */\n isTaskDisabled(taskId: string): boolean {\n return !!this.getTaskState(taskId).options.disabled;\n }\n\n /**\n * Remove a task from the scheduler and internal registry\n * @param taskId Task identifier\n */\n deregisterTask(taskId: string): void {\n this.getTaskState(taskId); // Validates task exists\n delete this.taskStates[taskId];\n this.toadScheduler.removeById(taskId);\n }\n\n /**\n * Get the internal state of a task\n * @param taskId Task identifier\n * @returns Task state\n */\n getTaskState(taskId: string): TaskState {\n const taskState = this.taskStates[taskId];\n if (!taskState) {\n throw new JobValidationException(`The requested task with ID ${taskId} was not registered`, taskId);\n }\n return taskState;\n }\n\n /**\n * Execute a task after a delay\n * @param taskId Task identifier\n * @param timeoutMs Delay in milliseconds\n */\n runTimeoutTaskInstance(taskId: string, timeoutMs: number): void {\n const taskState = this.getTaskState(taskId);\n this.logger.log(`Running delayed task '${taskId}' in ${timeoutMs}ms`);\n setTimeout(() => taskState.timedTask.execute(), timeoutMs);\n }\n\n /**\n * Stop all scheduled tasks\n */\n stopSchedulerTasks(): void {\n this.toadScheduler.stop();\n }\n\n /**\n * Validates task registration inputs\n */\n validateInput(taskId: string, serviceIdentifier: string, schedulerOptions: TaskSchedulerOptions): void {\n if (!taskId) {\n throw new JobValidationException(\"Task ID was not provided. Can't register task or schedule job.\", taskId);\n }\n\n const serviceName = serviceIdentifier || \"unknown\";\n const prefix = `Job '${schedulerOptions?.name ?? serviceName}' with ID '${taskId}'`;\n\n if (this.taskStates[taskId]) {\n throw new JobValidationException(`${prefix} was already registered. Can't register a key twice.`, taskId);\n }\n\n let resolvedService: TaskService;\n try {\n resolvedService = this.cradleService.resolve<TaskService>(serviceIdentifier as keyof typeof DITokens);\n } catch (e) {\n if (e instanceof AwilixResolutionError) {\n throw new JobValidationException(\n `${prefix} had an awilix dependency resolution error. It can't be scheduled without fixing this problem. Inner error:\\n` +\n e.stack,\n taskId,\n );\n } else {\n throw new JobValidationException(\n `${prefix} is not a registered awilix dependency. It can't be scheduled. Error:\\n` + (e as Error).stack,\n taskId,\n );\n }\n }\n\n if (typeof resolvedService?.run !== \"function\") {\n throw new JobValidationException(`${prefix} was resolved but it doesn't have a 'run()' method to call.`, taskId);\n }\n\n if (!schedulerOptions?.periodic && !schedulerOptions?.runOnce && !schedulerOptions?.runDelayed) {\n throw new JobValidationException(`${prefix} Provide 'periodic', 'runOnce', or 'runDelayed' option.`, taskId);\n }\n\n if (!schedulerOptions?.periodic && !!schedulerOptions.disabled) {\n throw new JobValidationException(`${prefix} Only tasks of type 'periodic' can be disabled at boot.`, taskId);\n }\n\n if (schedulerOptions?.runDelayed && !schedulerOptions.milliseconds && !schedulerOptions.seconds) {\n throw new JobValidationException(`${prefix} Provide a delayed timing parameter (milliseconds|seconds)`, taskId);\n }\n\n if (\n schedulerOptions?.periodic &&\n !schedulerOptions.milliseconds &&\n !schedulerOptions.seconds &&\n !schedulerOptions.minutes &&\n !schedulerOptions.hours &&\n !schedulerOptions.days\n ) {\n throw new JobValidationException(\n `${prefix} Provide a periodic timing parameter (milliseconds|seconds|minutes|hours|days)`,\n taskId,\n );\n }\n }\n\n /**\n * Create a safe timed task with error handling\n * @param taskId Task identifier\n * @param serviceIdentifier Service to resolve and execute\n * @returns AsyncTask instance\n */\n private getSafeTimedTask(taskId: string, serviceIdentifier: string): AsyncTask {\n const asyncHandler = async (): Promise<void> => {\n await this.timeTask(taskId, serviceIdentifier);\n };\n\n return new AsyncTask(taskId, asyncHandler, this.getErrorHandler(taskId));\n }\n\n /**\n * Execute a task and measure its execution time\n * @param taskId Task identifier\n * @param serviceIdentifier Service to resolve and execute\n */\n private async timeTask(taskId: string, serviceIdentifier: string): Promise<void> {\n const taskState = this.taskStates[taskId];\n taskState.started = Date.now();\n\n const taskService = this.cradleService.resolve<TaskService>(serviceIdentifier as keyof typeof DITokens);\n await taskService.run();\n\n taskState.duration = Date.now() - taskState.started;\n\n if (taskState.options?.logFirstCompletion !== false && !taskState?.firstCompletion) {\n this.logger.log(`Task '${taskId}' first completion. Duration ${taskState.duration}ms`);\n taskState.firstCompletion = Date.now();\n }\n }\n\n /**\n * Create an error handler for a task\n * @param taskId Task identifier\n * @returns Error handler function\n */\n private getErrorHandler(taskId: string): (error: Error) => void {\n return (error: Error): void => {\n const taskState = this.taskStates[taskId];\n\n taskState.lastError ??= {\n time: Date.now(),\n error,\n };\n\n this.logger.error(`Task '${taskId}' threw an exception: ${error.stack}`);\n };\n }\n\n /**\n * Schedule a periodic job that's not disabled\n * @param taskId Task identifier\n */\n private scheduleEnabledPeriodicJob(taskId: string): void {\n const taskState = this.getTaskState(taskId);\n\n if (!taskState?.timedTask || !taskState?.options) {\n throw new JobValidationException(\n `The requested task with ID ${taskId} was not registered properly ('timedTask' or 'options' missing).`,\n taskId,\n );\n }\n\n const schedulerOptions = taskState.options;\n const timedTask = taskState.timedTask;\n\n if (!schedulerOptions?.periodic) {\n throw new JobValidationException(\n `The requested task with ID ${taskId} is not periodic and cannot be enabled.`,\n taskId,\n );\n }\n\n if (!schedulerOptions.disabled) {\n this.logger.log(`Task '${taskId}' was scheduled (runImmediately: ${!!schedulerOptions.runImmediately}).`);\n const job = new SimpleIntervalJob(schedulerOptions, timedTask);\n taskState.job = job;\n this.toadScheduler.addSimpleIntervalJob(job);\n } else {\n this.logger.log(`Task '${taskId}' was marked as disabled (deferred execution).`);\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"typeorm.service.js","names":[],"sources":["../../../src/services/typeorm/typeorm.service.ts"],"sourcesContent":["import { DataSource } from \"typeorm\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { AppDataSource } from \"@/data-source\";\n\nexport class TypeormService {\n loaded = false;\n private dataSource?: DataSource;\n private readonly logger = new LoggerService(TypeormService.name);\n\n public hasConnected() {\n if (!this.dataSource) {\n this.loadDataSources();\n }\n\n return this.dataSource!.isInitialized ? 1 : 0;\n }\n\n public getDataSource() {\n if (!this.dataSource) {\n this.loadDataSources();\n }\n\n return this.dataSource!;\n }\n\n public async createConnection() {\n const dataSource = this.loadDataSources();\n if (dataSource.isInitialized) {\n this.logger.log(\"Typeorm connection already initialized, skipping\");\n } else {\n const connection = await dataSource.initialize();\n const migrations = await connection.runMigrations({ transaction: \"all\" });\n this.logger.log(`Typeorm connection initialized - ${migrations.length} migrations executed`);\n }\n this.loaded = true;\n }\n\n private loadDataSources() {\n this.dataSource = AppDataSource;\n return this.dataSource!;\n }\n}\n"],"mappings":";;;AAIA,IAAa,iBAAb,MAAa,eAAe;CAC1B,SAAS;CACT;CACA,SAA0B,IAAI,cAAc,eAAe,KAAK;CAEhE,eAAsB;AACpB,MAAI,CAAC,KAAK,WACR,MAAK,iBAAiB;AAGxB,SAAO,KAAK,WAAY,gBAAgB,IAAI;;CAG9C,gBAAuB;AACrB,MAAI,CAAC,KAAK,WACR,MAAK,iBAAiB;AAGxB,SAAO,KAAK;;CAGd,MAAa,mBAAmB;EAC9B,MAAM,aAAa,KAAK,iBAAiB;AACzC,MAAI,WAAW,cACb,MAAK,OAAO,IAAI,mDAAmD;OAC9D;GAEL,MAAM,aAAa,OAAM,MADA,WAAW,YAAY,EACZ,cAAc,EAAE,aAAa,OAAO,CAAC;AACzE,QAAK,OAAO,IAAI,oCAAoC,WAAW,OAAO,sBAAsB;;AAE9F,OAAK,SAAS;;CAGhB,kBAA0B;AACxB,OAAK,aAAa;AAClB,SAAO,KAAK"}
1
+ {"version":3,"file":"typeorm.service.js","names":[],"sources":["../../../src/services/typeorm/typeorm.service.ts"],"sourcesContent":["import { DataSource } from \"typeorm\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { AppDataSource } from \"@/data-source\";\n\nexport class TypeormService {\n loaded = false;\n private dataSource?: DataSource;\n private readonly logger = new LoggerService(TypeormService.name);\n\n public hasConnected() {\n if (!this.dataSource) {\n this.loadDataSources();\n }\n\n return this.dataSource!.isInitialized ? 1 : 0;\n }\n\n public getDataSource() {\n if (!this.dataSource) {\n this.loadDataSources();\n }\n\n return this.dataSource!;\n }\n\n public async createConnection() {\n const dataSource = this.loadDataSources();\n if (dataSource.isInitialized) {\n this.logger.log(\"Typeorm connection already initialized, skipping\");\n } else {\n const connection = await dataSource.initialize();\n const migrations = await connection.runMigrations({ transaction: \"all\" });\n this.logger.log(`Typeorm connection initialized - ${migrations.length} migrations executed`);\n }\n this.loaded = true;\n }\n\n private loadDataSources() {\n this.dataSource = AppDataSource;\n return this.dataSource!;\n }\n}\n"],"mappings":";;;AAIA,IAAa,iBAAb,MAAa,eAAe;CAC1B,SAAS;CACT;CACA,SAA0B,IAAI,cAAc,eAAe,KAAK;CAEhE,eAAsB;EACpB,IAAI,CAAC,KAAK,YACR,KAAK,iBAAiB;EAGxB,OAAO,KAAK,WAAY,gBAAgB,IAAI;;CAG9C,gBAAuB;EACrB,IAAI,CAAC,KAAK,YACR,KAAK,iBAAiB;EAGxB,OAAO,KAAK;;CAGd,MAAa,mBAAmB;EAC9B,MAAM,aAAa,KAAK,iBAAiB;EACzC,IAAI,WAAW,eACb,KAAK,OAAO,IAAI,mDAAmD;OAC9D;GAEL,MAAM,aAAa,OAAM,MADA,WAAW,YAAY,EACZ,cAAc,EAAE,aAAa,OAAO,CAAC;GACzE,KAAK,OAAO,IAAI,oCAAoC,WAAW,OAAO,sBAAsB;;EAE9F,KAAK,SAAS;;CAGhB,kBAA0B;EACxB,KAAK,aAAa;EAClB,OAAO,KAAK"}
@@ -1 +1 @@
1
- {"version":3,"file":"printer-service.validation.js","names":[],"sources":["../../../src/services/validators/printer-service.validation.ts"],"sourcesContent":["import { apiKeyLengthMaxDefault, apiKeyLengthMinDefault } from \"@/constants/service.constants\";\nimport { OctoprintType, PrinterTypesEnum, PrusaLinkType, BambuType } from \"@/services/printer-api.interface\";\nimport { RefinementCtx, z } from \"zod\";\n\nconst octoPrintApiKeySchema = z\n .string()\n .min(apiKeyLengthMinDefault)\n .max(apiKeyLengthMaxDefault)\n .regex(/^[a-zA-Z0-9_-]+$/, \"Alpha-numeric, dash, and underscore only\");\n\nexport const printerApiKeyValidator = z.string().optional();\nexport const printerNameValidator = z.string();\nexport const printerUsernameValidator = z.string().nullable();\nexport const printerPasswordValidator = z.string().nullable();\nexport const printerEnabledValidator = z.boolean();\nexport const printerDisabledReasonValidator = z.string().nullable();\nexport const printerUrlValidator = z.string().url();\nexport const printerTypeValidator = z.nativeEnum(PrinterTypesEnum);\nexport const printerDateAddedValidator = z.number().optional();\n\nconst prusaLinkAuthSchema = z.object({\n username: printerUsernameValidator,\n password: printerPasswordValidator,\n});\n\nconst basePrinterSchema = z\n .object({\n dateAdded: printerDateAddedValidator,\n printerURL: printerUrlValidator,\n printerType: printerTypeValidator,\n apiKey: printerApiKeyValidator,\n username: printerUsernameValidator.optional(),\n password: printerPasswordValidator.optional(),\n enabled: printerEnabledValidator.optional(),\n name: printerNameValidator,\n })\n .strip();\n\n// Infer base schema required to do superRefine\nconst apiKeyPrinterTypeSchema = z.object({\n apiKey: printerApiKeyValidator,\n printerType: printerTypeValidator,\n username: printerUsernameValidator.optional(),\n password: printerPasswordValidator.optional(),\n});\ntype ApiKeyPrinterTypeSchema = z.infer<typeof apiKeyPrinterTypeSchema>;\n\nexport const refineApiKeyValidator = <T extends ApiKeyPrinterTypeSchema>(data: T, ctx: RefinementCtx) => {\n // OctoPrint apiKey constraints\n if (data.printerType === OctoprintType) {\n const result = octoPrintApiKeySchema.safeParse(data.apiKey);\n if (!result.success) {\n result.error.issues.forEach((issue) => {\n ctx.addIssue({\n ...issue,\n // Nesting misses path under \"apiKey\"\n path: [\"apiKey\", ...issue.path],\n });\n });\n }\n } else if (data.printerType === PrusaLinkType || data.printerType === BambuType) {\n const result = prusaLinkAuthSchema.safeParse({\n username: data.username,\n password: data.password,\n });\n if (!result.success) {\n result.error.issues.forEach((issue) => {\n ctx.addIssue({\n ...issue,\n // Nesting misses path under \"username\"\n path: [\"username\", ...issue.path],\n });\n ctx.addIssue({\n ...issue,\n // Nesting misses path under \"password\"\n path: [\"password\", ...issue.path],\n });\n });\n }\n }\n};\n\nexport const createPrinterSchema = basePrinterSchema.superRefine(refineApiKeyValidator);\n\nexport const updatePrinterEnabledSchema = z.object({\n enabled: printerEnabledValidator,\n});\n\nexport const updatePrinterDisabledReasonSchema = z.object({\n disabledReason: printerDisabledReasonValidator,\n});\n"],"mappings":";;;;AAIA,MAAM,wBAAwB,EAC3B,QAAQ,CACR,IAAA,GAA2B,CAC3B,IAAA,GAA2B,CAC3B,MAAM,oBAAoB,2CAA2C;AAExE,MAAa,yBAAyB,EAAE,QAAQ,CAAC,UAAU;AAC3D,MAAa,uBAAuB,EAAE,QAAQ;AAC9C,MAAa,2BAA2B,EAAE,QAAQ,CAAC,UAAU;AAC7D,MAAa,2BAA2B,EAAE,QAAQ,CAAC,UAAU;AAC7D,MAAa,0BAA0B,EAAE,SAAS;AAClD,MAAa,iCAAiC,EAAE,QAAQ,CAAC,UAAU;AACnE,MAAa,sBAAsB,EAAE,QAAQ,CAAC,KAAK;AACnD,MAAa,uBAAuB,EAAE,WAAW,iBAAiB;AAClE,MAAa,4BAA4B,EAAE,QAAQ,CAAC,UAAU;AAE9D,MAAM,sBAAsB,EAAE,OAAO;CACnC,UAAU;CACV,UAAU;CACX,CAAC;AAEF,MAAM,oBAAoB,EACvB,OAAO;CACN,WAAW;CACX,YAAY;CACZ,aAAa;CACb,QAAQ;CACR,UAAU,yBAAyB,UAAU;CAC7C,UAAU,yBAAyB,UAAU;CAC7C,SAAS,wBAAwB,UAAU;CAC3C,MAAM;CACP,CAAC,CACD,OAAO;AAGsB,EAAE,OAAO;CACvC,QAAQ;CACR,aAAa;CACb,UAAU,yBAAyB,UAAU;CAC7C,UAAU,yBAAyB,UAAU;CAC9C,CAAC;AAGF,MAAa,yBAA4D,MAAS,QAAuB;AAEvG,KAAI,KAAK,gBAAA,GAA+B;EACtC,MAAM,SAAS,sBAAsB,UAAU,KAAK,OAAO;AAC3D,MAAI,CAAC,OAAO,QACV,QAAO,MAAM,OAAO,SAAS,UAAU;AACrC,OAAI,SAAS;IACX,GAAG;IAEH,MAAM,CAAC,UAAU,GAAG,MAAM,KAAK;IAChC,CAAC;IACF;YAEK,KAAK,gBAAA,KAAiC,KAAK,gBAAA,GAA2B;EAC/E,MAAM,SAAS,oBAAoB,UAAU;GAC3C,UAAU,KAAK;GACf,UAAU,KAAK;GAChB,CAAC;AACF,MAAI,CAAC,OAAO,QACV,QAAO,MAAM,OAAO,SAAS,UAAU;AACrC,OAAI,SAAS;IACX,GAAG;IAEH,MAAM,CAAC,YAAY,GAAG,MAAM,KAAK;IAClC,CAAC;AACF,OAAI,SAAS;IACX,GAAG;IAEH,MAAM,CAAC,YAAY,GAAG,MAAM,KAAK;IAClC,CAAC;IACF;;;AAKR,MAAa,sBAAsB,kBAAkB,YAAY,sBAAsB;AAEvF,MAAa,6BAA6B,EAAE,OAAO,EACjD,SAAS,yBACV,CAAC;AAEF,MAAa,oCAAoC,EAAE,OAAO,EACxD,gBAAgB,gCACjB,CAAC"}
1
+ {"version":3,"file":"printer-service.validation.js","names":[],"sources":["../../../src/services/validators/printer-service.validation.ts"],"sourcesContent":["import { apiKeyLengthMaxDefault, apiKeyLengthMinDefault } from \"@/constants/service.constants\";\nimport { OctoprintType, PrinterTypesEnum, PrusaLinkType, BambuType } from \"@/services/printer-api.interface\";\nimport { RefinementCtx, z } from \"zod\";\n\nconst octoPrintApiKeySchema = z\n .string()\n .min(apiKeyLengthMinDefault)\n .max(apiKeyLengthMaxDefault)\n .regex(/^[a-zA-Z0-9_-]+$/, \"Alpha-numeric, dash, and underscore only\");\n\nexport const printerApiKeyValidator = z.string().optional();\nexport const printerNameValidator = z.string();\nexport const printerUsernameValidator = z.string().nullable();\nexport const printerPasswordValidator = z.string().nullable();\nexport const printerEnabledValidator = z.boolean();\nexport const printerDisabledReasonValidator = z.string().nullable();\nexport const printerUrlValidator = z.string().url();\nexport const printerTypeValidator = z.nativeEnum(PrinterTypesEnum);\nexport const printerDateAddedValidator = z.number().optional();\n\nconst prusaLinkAuthSchema = z.object({\n username: printerUsernameValidator,\n password: printerPasswordValidator,\n});\n\nconst basePrinterSchema = z\n .object({\n dateAdded: printerDateAddedValidator,\n printerURL: printerUrlValidator,\n printerType: printerTypeValidator,\n apiKey: printerApiKeyValidator,\n username: printerUsernameValidator.optional(),\n password: printerPasswordValidator.optional(),\n enabled: printerEnabledValidator.optional(),\n name: printerNameValidator,\n })\n .strip();\n\n// Infer base schema required to do superRefine\nconst apiKeyPrinterTypeSchema = z.object({\n apiKey: printerApiKeyValidator,\n printerType: printerTypeValidator,\n username: printerUsernameValidator.optional(),\n password: printerPasswordValidator.optional(),\n});\ntype ApiKeyPrinterTypeSchema = z.infer<typeof apiKeyPrinterTypeSchema>;\n\nexport const refineApiKeyValidator = <T extends ApiKeyPrinterTypeSchema>(data: T, ctx: RefinementCtx) => {\n // OctoPrint apiKey constraints\n if (data.printerType === OctoprintType) {\n const result = octoPrintApiKeySchema.safeParse(data.apiKey);\n if (!result.success) {\n result.error.issues.forEach((issue) => {\n ctx.addIssue({\n ...issue,\n // Nesting misses path under \"apiKey\"\n path: [\"apiKey\", ...issue.path],\n });\n });\n }\n } else if (data.printerType === PrusaLinkType || data.printerType === BambuType) {\n const result = prusaLinkAuthSchema.safeParse({\n username: data.username,\n password: data.password,\n });\n if (!result.success) {\n result.error.issues.forEach((issue) => {\n ctx.addIssue({\n ...issue,\n // Nesting misses path under \"username\"\n path: [\"username\", ...issue.path],\n });\n ctx.addIssue({\n ...issue,\n // Nesting misses path under \"password\"\n path: [\"password\", ...issue.path],\n });\n });\n }\n }\n};\n\nexport const createPrinterSchema = basePrinterSchema.superRefine(refineApiKeyValidator);\n\nexport const updatePrinterEnabledSchema = z.object({\n enabled: printerEnabledValidator,\n});\n\nexport const updatePrinterDisabledReasonSchema = z.object({\n disabledReason: printerDisabledReasonValidator,\n});\n"],"mappings":";;;;AAIA,MAAM,wBAAwB,EAC3B,QAAQ,CACR,IAAA,GAA2B,CAC3B,IAAA,GAA2B,CAC3B,MAAM,oBAAoB,2CAA2C;AAExE,MAAa,yBAAyB,EAAE,QAAQ,CAAC,UAAU;AAC3D,MAAa,uBAAuB,EAAE,QAAQ;AAC9C,MAAa,2BAA2B,EAAE,QAAQ,CAAC,UAAU;AAC7D,MAAa,2BAA2B,EAAE,QAAQ,CAAC,UAAU;AAC7D,MAAa,0BAA0B,EAAE,SAAS;AAClD,MAAa,iCAAiC,EAAE,QAAQ,CAAC,UAAU;AACnE,MAAa,sBAAsB,EAAE,QAAQ,CAAC,KAAK;AACnD,MAAa,uBAAuB,EAAE,WAAW,iBAAiB;AAClE,MAAa,4BAA4B,EAAE,QAAQ,CAAC,UAAU;AAE9D,MAAM,sBAAsB,EAAE,OAAO;CACnC,UAAU;CACV,UAAU;CACX,CAAC;AAEF,MAAM,oBAAoB,EACvB,OAAO;CACN,WAAW;CACX,YAAY;CACZ,aAAa;CACb,QAAQ;CACR,UAAU,yBAAyB,UAAU;CAC7C,UAAU,yBAAyB,UAAU;CAC7C,SAAS,wBAAwB,UAAU;CAC3C,MAAM;CACP,CAAC,CACD,OAAO;AAGsB,EAAE,OAAO;CACvC,QAAQ;CACR,aAAa;CACb,UAAU,yBAAyB,UAAU;CAC7C,UAAU,yBAAyB,UAAU;CAC9C,CAAC;AAGF,MAAa,yBAA4D,MAAS,QAAuB;CAEvG,IAAI,KAAK,gBAAA,GAA+B;EACtC,MAAM,SAAS,sBAAsB,UAAU,KAAK,OAAO;EAC3D,IAAI,CAAC,OAAO,SACV,OAAO,MAAM,OAAO,SAAS,UAAU;GACrC,IAAI,SAAS;IACX,GAAG;IAEH,MAAM,CAAC,UAAU,GAAG,MAAM,KAAK;IAChC,CAAC;IACF;QAEC,IAAI,KAAK,gBAAA,KAAiC,KAAK,gBAAA,GAA2B;EAC/E,MAAM,SAAS,oBAAoB,UAAU;GAC3C,UAAU,KAAK;GACf,UAAU,KAAK;GAChB,CAAC;EACF,IAAI,CAAC,OAAO,SACV,OAAO,MAAM,OAAO,SAAS,UAAU;GACrC,IAAI,SAAS;IACX,GAAG;IAEH,MAAM,CAAC,YAAY,GAAG,MAAM,KAAK;IAClC,CAAC;GACF,IAAI,SAAS;IACX,GAAG;IAEH,MAAM,CAAC,YAAY,GAAG,MAAM,KAAK;IAClC,CAAC;IACF;;;AAKR,MAAa,sBAAsB,kBAAkB,YAAY,sBAAsB;AAEvF,MAAa,6BAA6B,EAAE,OAAO,EACjD,SAAS,yBACV,CAAC;AAEF,MAAa,oCAAoC,EAAE,OAAO,EACxD,gBAAgB,gCACjB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"default-http-client.builder.js","names":[],"sources":["../../src/shared/default-http-client.builder.ts"],"sourcesContent":["import { getDefaultTimeout } from \"@/constants/server-settings.constants\";\nimport axios, { AxiosInstance, AxiosProgressEvent, AxiosRequestConfig } from \"axios\";\nimport {\n contentTypeHeaderKey,\n jsonContentType,\n multiPartContentType,\n} from \"@/services/octoprint/constants/octoprint-service.constants\";\n\nexport interface IHttpClientBuilder {\n build(): AxiosInstance;\n}\n\nexport class DefaultHttpClientBuilder implements IHttpClientBuilder {\n protected axiosOptions: AxiosRequestConfig;\n\n constructor() {\n this.axiosOptions = {\n baseURL: \"\",\n timeout: getDefaultTimeout().apiTimeout,\n headers: {\n [contentTypeHeaderKey]: jsonContentType,\n },\n };\n }\n\n build<D = any>(): AxiosInstance {\n const axiosConfig: AxiosRequestConfig<D> = {\n baseURL: this.axiosOptions.baseURL,\n timeout: this.axiosOptions.timeout,\n headers: this.axiosOptions.headers,\n data: this.axiosOptions.data as D,\n maxBodyLength: this.axiosOptions.maxBodyLength ?? 1000 * 1000 * 1000, // 1GB,\n maxContentLength: this.axiosOptions.maxContentLength ?? 1000 * 1000 * 1000, // 1GB\n responseType: this.axiosOptions.responseType,\n onUploadProgress: this.axiosOptions.onUploadProgress,\n };\n\n return axios.create(axiosConfig);\n }\n\n withMaxBodyLength(maxBodyLength: number): this {\n this.axiosOptions.maxBodyLength = maxBodyLength;\n return this;\n }\n\n withMaxContentLength(maxContentLength: number): this {\n this.axiosOptions.maxContentLength = maxContentLength;\n return this;\n }\n\n withBaseUrl(baseUrl: string): this {\n if (!baseUrl?.length) {\n throw new Error(\"Base address may not be an empty string\");\n }\n\n this.axiosOptions.baseURL = baseUrl;\n return this;\n }\n\n /**\n * Set the timeout for the http client.\n * @param timeout the value for timeout in milliseconds.\n */\n withTimeout(timeout: number): this {\n if (!timeout || timeout <= 0) {\n throw new Error(\"Timeout value (milliseconds) must be greater than 0\");\n }\n\n this.axiosOptions.timeout = timeout;\n return this;\n }\n\n withMultiPartFormData(): this {\n this.withHeaders({\n \"Content-Type\": multiPartContentType,\n });\n return this;\n }\n\n withStreamResponse(): this {\n this.axiosOptions.responseType = \"stream\";\n return this;\n }\n\n withOnUploadProgress(handler: (progressEvent: AxiosProgressEvent) => void): this {\n this.axiosOptions.onUploadProgress = handler;\n return this;\n }\n\n withJsonContentTypeHeader(): this {\n this.withHeaders({ [contentTypeHeaderKey]: jsonContentType });\n return this;\n }\n\n withHeaders(headers: Record<string, string>): this {\n this.axiosOptions.headers = {\n ...this.axiosOptions.headers,\n ...headers,\n };\n return this;\n }\n}\n"],"mappings":";;;;AAYA,IAAa,2BAAb,MAAoE;CAClE;CAEA,cAAc;AACZ,OAAK,eAAe;GAClB,SAAS;GACT,SAAS,mBAAmB,CAAC;GAC7B,SAAS,GACN,uBAAuB,iBACzB;GACF;;CAGH,QAAgC;EAC9B,MAAM,cAAqC;GACzC,SAAS,KAAK,aAAa;GAC3B,SAAS,KAAK,aAAa;GAC3B,SAAS,KAAK,aAAa;GAC3B,MAAM,KAAK,aAAa;GACxB,eAAe,KAAK,aAAa,iBAAiB,MAAO,MAAO;GAChE,kBAAkB,KAAK,aAAa,oBAAoB,MAAO,MAAO;GACtE,cAAc,KAAK,aAAa;GAChC,kBAAkB,KAAK,aAAa;GACrC;AAED,SAAO,MAAM,OAAO,YAAY;;CAGlC,kBAAkB,eAA6B;AAC7C,OAAK,aAAa,gBAAgB;AAClC,SAAO;;CAGT,qBAAqB,kBAAgC;AACnD,OAAK,aAAa,mBAAmB;AACrC,SAAO;;CAGT,YAAY,SAAuB;AACjC,MAAI,CAAC,SAAS,OACZ,OAAM,IAAI,MAAM,0CAA0C;AAG5D,OAAK,aAAa,UAAU;AAC5B,SAAO;;;;;;CAOT,YAAY,SAAuB;AACjC,MAAI,CAAC,WAAW,WAAW,EACzB,OAAM,IAAI,MAAM,sDAAsD;AAGxE,OAAK,aAAa,UAAU;AAC5B,SAAO;;CAGT,wBAA8B;AAC5B,OAAK,YAAY,EACf,gBAAgB,sBACjB,CAAC;AACF,SAAO;;CAGT,qBAA2B;AACzB,OAAK,aAAa,eAAe;AACjC,SAAO;;CAGT,qBAAqB,SAA4D;AAC/E,OAAK,aAAa,mBAAmB;AACrC,SAAO;;CAGT,4BAAkC;AAChC,OAAK,YAAY,GAAG,uBAAuB,iBAAiB,CAAC;AAC7D,SAAO;;CAGT,YAAY,SAAuC;AACjD,OAAK,aAAa,UAAU;GAC1B,GAAG,KAAK,aAAa;GACrB,GAAG;GACJ;AACD,SAAO"}
1
+ {"version":3,"file":"default-http-client.builder.js","names":[],"sources":["../../src/shared/default-http-client.builder.ts"],"sourcesContent":["import { getDefaultTimeout } from \"@/constants/server-settings.constants\";\nimport axios, { AxiosInstance, AxiosProgressEvent, AxiosRequestConfig } from \"axios\";\nimport {\n contentTypeHeaderKey,\n jsonContentType,\n multiPartContentType,\n} from \"@/services/octoprint/constants/octoprint-service.constants\";\n\nexport interface IHttpClientBuilder {\n build(): AxiosInstance;\n}\n\nexport class DefaultHttpClientBuilder implements IHttpClientBuilder {\n protected axiosOptions: AxiosRequestConfig;\n\n constructor() {\n this.axiosOptions = {\n baseURL: \"\",\n timeout: getDefaultTimeout().apiTimeout,\n headers: {\n [contentTypeHeaderKey]: jsonContentType,\n },\n };\n }\n\n build<D = any>(): AxiosInstance {\n const axiosConfig: AxiosRequestConfig<D> = {\n baseURL: this.axiosOptions.baseURL,\n timeout: this.axiosOptions.timeout,\n headers: this.axiosOptions.headers,\n data: this.axiosOptions.data as D,\n maxBodyLength: this.axiosOptions.maxBodyLength ?? 1000 * 1000 * 1000, // 1GB,\n maxContentLength: this.axiosOptions.maxContentLength ?? 1000 * 1000 * 1000, // 1GB\n responseType: this.axiosOptions.responseType,\n onUploadProgress: this.axiosOptions.onUploadProgress,\n };\n\n return axios.create(axiosConfig);\n }\n\n withMaxBodyLength(maxBodyLength: number): this {\n this.axiosOptions.maxBodyLength = maxBodyLength;\n return this;\n }\n\n withMaxContentLength(maxContentLength: number): this {\n this.axiosOptions.maxContentLength = maxContentLength;\n return this;\n }\n\n withBaseUrl(baseUrl: string): this {\n if (!baseUrl?.length) {\n throw new Error(\"Base address may not be an empty string\");\n }\n\n this.axiosOptions.baseURL = baseUrl;\n return this;\n }\n\n /**\n * Set the timeout for the http client.\n * @param timeout the value for timeout in milliseconds.\n */\n withTimeout(timeout: number): this {\n if (!timeout || timeout <= 0) {\n throw new Error(\"Timeout value (milliseconds) must be greater than 0\");\n }\n\n this.axiosOptions.timeout = timeout;\n return this;\n }\n\n withMultiPartFormData(): this {\n this.withHeaders({\n \"Content-Type\": multiPartContentType,\n });\n return this;\n }\n\n withStreamResponse(): this {\n this.axiosOptions.responseType = \"stream\";\n return this;\n }\n\n withOnUploadProgress(handler: (progressEvent: AxiosProgressEvent) => void): this {\n this.axiosOptions.onUploadProgress = handler;\n return this;\n }\n\n withJsonContentTypeHeader(): this {\n this.withHeaders({ [contentTypeHeaderKey]: jsonContentType });\n return this;\n }\n\n withHeaders(headers: Record<string, string>): this {\n this.axiosOptions.headers = {\n ...this.axiosOptions.headers,\n ...headers,\n };\n return this;\n }\n}\n"],"mappings":";;;;AAYA,IAAa,2BAAb,MAAoE;CAClE;CAEA,cAAc;EACZ,KAAK,eAAe;GAClB,SAAS;GACT,SAAS,mBAAmB,CAAC;GAC7B,SAAS,GACN,uBAAuB,iBACzB;GACF;;CAGH,QAAgC;EAC9B,MAAM,cAAqC;GACzC,SAAS,KAAK,aAAa;GAC3B,SAAS,KAAK,aAAa;GAC3B,SAAS,KAAK,aAAa;GAC3B,MAAM,KAAK,aAAa;GACxB,eAAe,KAAK,aAAa,iBAAiB,MAAO,MAAO;GAChE,kBAAkB,KAAK,aAAa,oBAAoB,MAAO,MAAO;GACtE,cAAc,KAAK,aAAa;GAChC,kBAAkB,KAAK,aAAa;GACrC;EAED,OAAO,MAAM,OAAO,YAAY;;CAGlC,kBAAkB,eAA6B;EAC7C,KAAK,aAAa,gBAAgB;EAClC,OAAO;;CAGT,qBAAqB,kBAAgC;EACnD,KAAK,aAAa,mBAAmB;EACrC,OAAO;;CAGT,YAAY,SAAuB;EACjC,IAAI,CAAC,SAAS,QACZ,MAAM,IAAI,MAAM,0CAA0C;EAG5D,KAAK,aAAa,UAAU;EAC5B,OAAO;;;;;;CAOT,YAAY,SAAuB;EACjC,IAAI,CAAC,WAAW,WAAW,GACzB,MAAM,IAAI,MAAM,sDAAsD;EAGxE,KAAK,aAAa,UAAU;EAC5B,OAAO;;CAGT,wBAA8B;EAC5B,KAAK,YAAY,EACf,gBAAgB,sBACjB,CAAC;EACF,OAAO;;CAGT,qBAA2B;EACzB,KAAK,aAAa,eAAe;EACjC,OAAO;;CAGT,qBAAqB,SAA4D;EAC/E,KAAK,aAAa,mBAAmB;EACrC,OAAO;;CAGT,4BAAkC;EAChC,KAAK,YAAY,GAAG,uBAAuB,iBAAiB,CAAC;EAC7D,OAAO;;CAGT,YAAY,SAAuC;EACjD,KAAK,aAAa,UAAU;GAC1B,GAAG,KAAK,aAAa;GACrB,GAAG;GACJ;EACD,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"load-controllers.js","names":["controllerModules"],"sources":["../../src/shared/load-controllers.ts"],"sourcesContent":["import {\n FindControllersResult,\n getStateAndTarget,\n HttpVerbs,\n IStateAndTarget,\n makeInvoker,\n rollUpState,\n} from \"awilix-express\";\nimport { Router } from \"express\";\nimport { ClassOrFunctionReturning } from \"awilix\";\nimport controllerModules from \"virtual:controllers\";\n\nexport async function loadControllersFunc(): Promise<Router> {\n const found = await findControllers();\n const router = Router();\n found.forEach(_registerController.bind(null, router));\n return router;\n}\n\nexport async function findControllers(): Promise<FindControllersResult> {\n return controllerModules.map(extractStateAndTargetFromExports).flat();\n}\n\nfunction extractStateAndTargetFromExports(exports: unknown): FindControllersResult {\n const items: FindControllersResult = [];\n\n if (exports) {\n const stateAndTarget = getStateAndTarget(exports);\n if (stateAndTarget) {\n items.push(stateAndTarget);\n return items;\n }\n\n for (const key of Object.keys(exports as object)) {\n const stateAndTarget = getStateAndTarget((exports as Record<string, unknown>)[key]);\n if (stateAndTarget) {\n items.push(stateAndTarget);\n }\n }\n }\n\n return items;\n}\n\nfunction _registerController(router: Router, stateAndTarget: IStateAndTarget | null): void {\n if (!stateAndTarget) {\n return;\n }\n\n const { state, target } = stateAndTarget;\n const rolledUp = rollUpState(state);\n rolledUp.forEach((methodCfg, methodName) => {\n methodCfg.verbs.forEach((httpVerb) => {\n let method = httpVerb.toLowerCase();\n if (httpVerb === HttpVerbs.ALL) {\n method = \"all\";\n }\n\n (router as any)[method](\n methodCfg.paths,\n ...methodCfg.beforeMiddleware,\n makeInvoker(target as ClassOrFunctionReturning<any>)(methodName as any),\n ...methodCfg.afterMiddleware,\n );\n });\n });\n}\n"],"mappings":";;;;AAYA,eAAsB,sBAAuC;CAC3D,MAAM,QAAQ,MAAM,iBAAiB;CACrC,MAAM,SAAS,QAAQ;AACvB,OAAM,QAAQ,oBAAoB,KAAK,MAAM,OAAO,CAAC;AACrD,QAAO;;AAGT,eAAsB,kBAAkD;AACtE,QAAOA,6BAAkB,IAAI,iCAAiC,CAAC,MAAM;;AAGvE,SAAS,iCAAiC,SAAyC;CACjF,MAAM,QAA+B,EAAE;AAEvC,KAAI,SAAS;EACX,MAAM,iBAAiB,kBAAkB,QAAQ;AACjD,MAAI,gBAAgB;AAClB,SAAM,KAAK,eAAe;AAC1B,UAAO;;AAGT,OAAK,MAAM,OAAO,OAAO,KAAK,QAAkB,EAAE;GAChD,MAAM,iBAAiB,kBAAmB,QAAoC,KAAK;AACnF,OAAI,eACF,OAAM,KAAK,eAAe;;;AAKhC,QAAO;;AAGT,SAAS,oBAAoB,QAAgB,gBAA8C;AACzF,KAAI,CAAC,eACH;CAGF,MAAM,EAAE,OAAO,WAAW;AACT,aAAY,MACrB,CAAC,SAAS,WAAW,eAAe;AAC1C,YAAU,MAAM,SAAS,aAAa;GACpC,IAAI,SAAS,SAAS,aAAa;AACnC,OAAI,aAAa,UAAU,IACzB,UAAS;AAGV,UAAe,QACd,UAAU,OACV,GAAG,UAAU,kBACb,YAAY,OAAwC,CAAC,WAAkB,EACvE,GAAG,UAAU,gBACd;IACD;GACF"}
1
+ {"version":3,"file":"load-controllers.js","names":["controllerModules"],"sources":["../../src/shared/load-controllers.ts"],"sourcesContent":["import {\n FindControllersResult,\n getStateAndTarget,\n HttpVerbs,\n IStateAndTarget,\n makeInvoker,\n rollUpState,\n} from \"awilix-express\";\nimport { Router } from \"express\";\nimport { ClassOrFunctionReturning } from \"awilix\";\nimport controllerModules from \"virtual:controllers\";\n\nexport async function loadControllersFunc(): Promise<Router> {\n const found = await findControllers();\n const router = Router();\n found.forEach(_registerController.bind(null, router));\n return router;\n}\n\nexport async function findControllers(): Promise<FindControllersResult> {\n return controllerModules.map(extractStateAndTargetFromExports).flat();\n}\n\nfunction extractStateAndTargetFromExports(exports: unknown): FindControllersResult {\n const items: FindControllersResult = [];\n\n if (exports) {\n const stateAndTarget = getStateAndTarget(exports);\n if (stateAndTarget) {\n items.push(stateAndTarget);\n return items;\n }\n\n for (const key of Object.keys(exports as object)) {\n const stateAndTarget = getStateAndTarget((exports as Record<string, unknown>)[key]);\n if (stateAndTarget) {\n items.push(stateAndTarget);\n }\n }\n }\n\n return items;\n}\n\nfunction _registerController(router: Router, stateAndTarget: IStateAndTarget | null): void {\n if (!stateAndTarget) {\n return;\n }\n\n const { state, target } = stateAndTarget;\n const rolledUp = rollUpState(state);\n rolledUp.forEach((methodCfg, methodName) => {\n methodCfg.verbs.forEach((httpVerb) => {\n let method = httpVerb.toLowerCase();\n if (httpVerb === HttpVerbs.ALL) {\n method = \"all\";\n }\n\n (router as any)[method](\n methodCfg.paths,\n ...methodCfg.beforeMiddleware,\n makeInvoker(target as ClassOrFunctionReturning<any>)(methodName as any),\n ...methodCfg.afterMiddleware,\n );\n });\n });\n}\n"],"mappings":";;;;AAYA,eAAsB,sBAAuC;CAC3D,MAAM,QAAQ,MAAM,iBAAiB;CACrC,MAAM,SAAS,QAAQ;CACvB,MAAM,QAAQ,oBAAoB,KAAK,MAAM,OAAO,CAAC;CACrD,OAAO;;AAGT,eAAsB,kBAAkD;CACtE,OAAOA,6BAAkB,IAAI,iCAAiC,CAAC,MAAM;;AAGvE,SAAS,iCAAiC,SAAyC;CACjF,MAAM,QAA+B,EAAE;CAEvC,IAAI,SAAS;EACX,MAAM,iBAAiB,kBAAkB,QAAQ;EACjD,IAAI,gBAAgB;GAClB,MAAM,KAAK,eAAe;GAC1B,OAAO;;EAGT,KAAK,MAAM,OAAO,OAAO,KAAK,QAAkB,EAAE;GAChD,MAAM,iBAAiB,kBAAmB,QAAoC,KAAK;GACnF,IAAI,gBACF,MAAM,KAAK,eAAe;;;CAKhC,OAAO;;AAGT,SAAS,oBAAoB,QAAgB,gBAA8C;CACzF,IAAI,CAAC,gBACH;CAGF,MAAM,EAAE,OAAO,WAAW;CAE1B,YAD6B,MACrB,CAAC,SAAS,WAAW,eAAe;EAC1C,UAAU,MAAM,SAAS,aAAa;GACpC,IAAI,SAAS,SAAS,aAAa;GACnC,IAAI,aAAa,UAAU,KACzB,SAAS;GAGX,OAAgB,QACd,UAAU,OACV,GAAG,UAAU,kBACb,YAAY,OAAwC,CAAC,WAAkB,EACvE,GAAG,UAAU,gBACd;IACD;GACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-settings.migration.js","names":["uuidv4"],"sources":["../../src/shared/runtime-settings.migration.ts"],"sourcesContent":["import {\n credentialSettingsKey,\n frontendSettingKey,\n getDefaultCredentialSettings,\n getDefaultFrontendSettings,\n getDefaultServerSettings,\n getDefaultTimeout,\n getDefaultWizardSettings,\n serverSettingsKey,\n timeoutSettingKey,\n wizardSettingKey,\n} from \"@/constants/server-settings.constants\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { Settings } from \"@/entities\";\n\nexport function migrateSettingsRuntime(knownSettings: Partial<Settings>): Settings {\n const entity = knownSettings;\n\n entity[wizardSettingKey] ??= getDefaultWizardSettings();\n entity[timeoutSettingKey] ??= getDefaultTimeout();\n\n if (entity[timeoutSettingKey]) {\n const defaultTimeoutSettings = getDefaultTimeout();\n entity[timeoutSettingKey] = {\n apiTimeout: entity[timeoutSettingKey].apiTimeout ?? defaultTimeoutSettings.apiTimeout,\n apiUploadTimeout: entity[timeoutSettingKey].apiUploadTimeout ?? defaultTimeoutSettings.apiUploadTimeout,\n };\n }\n\n entity[serverSettingsKey] ??= getDefaultServerSettings();\n entity[frontendSettingKey] ??= getDefaultFrontendSettings();\n entity[credentialSettingsKey] ??= {\n ...getDefaultCredentialSettings(),\n // Verification and signing of JWT tokens, can be changed on the fly\n jwtSecret: uuidv4(),\n };\n\n if (entity[frontendSettingKey]) {\n const defaultFrontendSettings = getDefaultFrontendSettings();\n\n // Ensure all frontend settings have default values\n entity[frontendSettingKey] = {\n gridCols: entity[frontendSettingKey].gridCols ?? defaultFrontendSettings.gridCols,\n gridRows: entity[frontendSettingKey].gridRows ?? defaultFrontendSettings.gridRows,\n largeTiles: entity[frontendSettingKey].largeTiles ?? defaultFrontendSettings.largeTiles,\n tilePreferCancelOverQuickStop:\n entity[frontendSettingKey].tilePreferCancelOverQuickStop ??\n defaultFrontendSettings.tilePreferCancelOverQuickStop,\n gridNameSortDirection:\n entity[frontendSettingKey].gridNameSortDirection ?? defaultFrontendSettings.gridNameSortDirection,\n };\n }\n\n if (entity[serverSettingsKey]) {\n const defaultServerSettings = getDefaultServerSettings();\n\n // Remove superfluous settings and provide default values if properties are missing\n entity[serverSettingsKey] = {\n loginRequired: entity[serverSettingsKey].loginRequired ?? defaultServerSettings.loginRequired,\n registration: entity[serverSettingsKey].registration ?? defaultServerSettings.registration,\n experimentalMoonrakerSupport:\n entity[serverSettingsKey].experimentalMoonrakerSupport ?? defaultServerSettings.experimentalMoonrakerSupport,\n experimentalPrusaLinkSupport:\n entity[serverSettingsKey].experimentalPrusaLinkSupport ?? defaultServerSettings.experimentalPrusaLinkSupport,\n experimentalBambuSupport:\n entity[serverSettingsKey].experimentalBambuSupport ?? defaultServerSettings.experimentalBambuSupport,\n sentryDiagnosticsEnabled:\n entity[serverSettingsKey].sentryDiagnosticsEnabled ?? defaultServerSettings.sentryDiagnosticsEnabled,\n };\n }\n\n return entity as Settings;\n}\n"],"mappings":";;;AAeA,SAAgB,uBAAuB,eAA4C;CACjF,MAAM,SAAS;AAEf,QAAO,sBAAsB,0BAA0B;AACvD,QAAO,uBAAuB,mBAAmB;AAEjD,KAAI,OAAA,YAA2B;EAC7B,MAAM,yBAAyB,mBAAmB;AAClD,SAAO,qBAAqB;GAC1B,YAAY,OAAA,WAA0B,cAAc,uBAAuB;GAC3E,kBAAkB,OAAA,WAA0B,oBAAoB,uBAAuB;GACxF;;AAGH,QAAO,uBAAuB,0BAA0B;AACxD,QAAO,wBAAwB,4BAA4B;AAC3D,QAAO,2BAA2B;EAChC,GAAG,8BAA8B;EAEjC,WAAWA,IAAQ;EACpB;AAED,KAAI,OAAA,aAA4B;EAC9B,MAAM,0BAA0B,4BAA4B;AAG5D,SAAO,sBAAsB;GAC3B,UAAU,OAAA,YAA2B,YAAY,wBAAwB;GACzE,UAAU,OAAA,YAA2B,YAAY,wBAAwB;GACzE,YAAY,OAAA,YAA2B,cAAc,wBAAwB;GAC7E,+BACE,OAAA,YAA2B,iCAC3B,wBAAwB;GAC1B,uBACE,OAAA,YAA2B,yBAAyB,wBAAwB;GAC/E;;AAGH,KAAI,OAAA,WAA2B;EAC7B,MAAM,wBAAwB,0BAA0B;AAGxD,SAAO,qBAAqB;GAC1B,eAAe,OAAA,UAA0B,iBAAiB,sBAAsB;GAChF,cAAc,OAAA,UAA0B,gBAAgB,sBAAsB;GAC9E,8BACE,OAAA,UAA0B,gCAAgC,sBAAsB;GAClF,8BACE,OAAA,UAA0B,gCAAgC,sBAAsB;GAClF,0BACE,OAAA,UAA0B,4BAA4B,sBAAsB;GAC9E,0BACE,OAAA,UAA0B,4BAA4B,sBAAsB;GAC/E;;AAGH,QAAO"}
1
+ {"version":3,"file":"runtime-settings.migration.js","names":["uuidv4"],"sources":["../../src/shared/runtime-settings.migration.ts"],"sourcesContent":["import {\n credentialSettingsKey,\n frontendSettingKey,\n getDefaultCredentialSettings,\n getDefaultFrontendSettings,\n getDefaultServerSettings,\n getDefaultTimeout,\n getDefaultWizardSettings,\n serverSettingsKey,\n timeoutSettingKey,\n wizardSettingKey,\n} from \"@/constants/server-settings.constants\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { Settings } from \"@/entities\";\n\nexport function migrateSettingsRuntime(knownSettings: Partial<Settings>): Settings {\n const entity = knownSettings;\n\n entity[wizardSettingKey] ??= getDefaultWizardSettings();\n entity[timeoutSettingKey] ??= getDefaultTimeout();\n\n if (entity[timeoutSettingKey]) {\n const defaultTimeoutSettings = getDefaultTimeout();\n entity[timeoutSettingKey] = {\n apiTimeout: entity[timeoutSettingKey].apiTimeout ?? defaultTimeoutSettings.apiTimeout,\n apiUploadTimeout: entity[timeoutSettingKey].apiUploadTimeout ?? defaultTimeoutSettings.apiUploadTimeout,\n };\n }\n\n entity[serverSettingsKey] ??= getDefaultServerSettings();\n entity[frontendSettingKey] ??= getDefaultFrontendSettings();\n entity[credentialSettingsKey] ??= {\n ...getDefaultCredentialSettings(),\n // Verification and signing of JWT tokens, can be changed on the fly\n jwtSecret: uuidv4(),\n };\n\n if (entity[frontendSettingKey]) {\n const defaultFrontendSettings = getDefaultFrontendSettings();\n\n // Ensure all frontend settings have default values\n entity[frontendSettingKey] = {\n gridCols: entity[frontendSettingKey].gridCols ?? defaultFrontendSettings.gridCols,\n gridRows: entity[frontendSettingKey].gridRows ?? defaultFrontendSettings.gridRows,\n largeTiles: entity[frontendSettingKey].largeTiles ?? defaultFrontendSettings.largeTiles,\n tilePreferCancelOverQuickStop:\n entity[frontendSettingKey].tilePreferCancelOverQuickStop ??\n defaultFrontendSettings.tilePreferCancelOverQuickStop,\n gridNameSortDirection:\n entity[frontendSettingKey].gridNameSortDirection ?? defaultFrontendSettings.gridNameSortDirection,\n };\n }\n\n if (entity[serverSettingsKey]) {\n const defaultServerSettings = getDefaultServerSettings();\n\n // Remove superfluous settings and provide default values if properties are missing\n entity[serverSettingsKey] = {\n loginRequired: entity[serverSettingsKey].loginRequired ?? defaultServerSettings.loginRequired,\n registration: entity[serverSettingsKey].registration ?? defaultServerSettings.registration,\n experimentalMoonrakerSupport:\n entity[serverSettingsKey].experimentalMoonrakerSupport ?? defaultServerSettings.experimentalMoonrakerSupport,\n experimentalPrusaLinkSupport:\n entity[serverSettingsKey].experimentalPrusaLinkSupport ?? defaultServerSettings.experimentalPrusaLinkSupport,\n experimentalBambuSupport:\n entity[serverSettingsKey].experimentalBambuSupport ?? defaultServerSettings.experimentalBambuSupport,\n sentryDiagnosticsEnabled:\n entity[serverSettingsKey].sentryDiagnosticsEnabled ?? defaultServerSettings.sentryDiagnosticsEnabled,\n };\n }\n\n return entity as Settings;\n}\n"],"mappings":";;;AAeA,SAAgB,uBAAuB,eAA4C;CACjF,MAAM,SAAS;CAEf,OAAO,sBAAsB,0BAA0B;CACvD,OAAO,uBAAuB,mBAAmB;CAEjD,IAAI,OAAA,YAA2B;EAC7B,MAAM,yBAAyB,mBAAmB;EAClD,OAAO,qBAAqB;GAC1B,YAAY,OAAA,WAA0B,cAAc,uBAAuB;GAC3E,kBAAkB,OAAA,WAA0B,oBAAoB,uBAAuB;GACxF;;CAGH,OAAO,uBAAuB,0BAA0B;CACxD,OAAO,wBAAwB,4BAA4B;CAC3D,OAAO,2BAA2B;EAChC,GAAG,8BAA8B;EAEjC,WAAWA,IAAQ;EACpB;CAED,IAAI,OAAA,aAA4B;EAC9B,MAAM,0BAA0B,4BAA4B;EAG5D,OAAO,sBAAsB;GAC3B,UAAU,OAAA,YAA2B,YAAY,wBAAwB;GACzE,UAAU,OAAA,YAA2B,YAAY,wBAAwB;GACzE,YAAY,OAAA,YAA2B,cAAc,wBAAwB;GAC7E,+BACE,OAAA,YAA2B,iCAC3B,wBAAwB;GAC1B,uBACE,OAAA,YAA2B,yBAAyB,wBAAwB;GAC/E;;CAGH,IAAI,OAAA,WAA2B;EAC7B,MAAM,wBAAwB,0BAA0B;EAGxD,OAAO,qBAAqB;GAC1B,eAAe,OAAA,UAA0B,iBAAiB,sBAAsB;GAChF,cAAc,OAAA,UAA0B,gBAAgB,sBAAsB;GAC9E,8BACE,OAAA,UAA0B,gCAAgC,sBAAsB;GAClF,8BACE,OAAA,UAA0B,gCAAgC,sBAAsB;GAClF,0BACE,OAAA,UAA0B,4BAA4B,sBAAsB;GAC9E,0BACE,OAAA,UAA0B,4BAA4B,sBAAsB;GAC/E;;CAGH,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-rpc-extended.adapter.js","names":[],"sources":["../../src/shared/websocket-rpc-extended.adapter.ts"],"sourcesContent":["import { WebsocketAdapter } from \"@/shared/websocket.adapter\";\nimport type { JsonRpcResponseDto } from \"@/services/moonraker/dto/rpc/json-rpc-response.dto\";\nimport { Data } from \"ws\";\nimport type { JsonRpcRequestDto } from \"@/services/moonraker/dto/rpc/json-rpc-request.dto\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { JsonRpcEventDto } from \"@/services/moonraker/dto/websocket/json-rpc-event.dto\";\n\nexport abstract class WebsocketRpcExtendedAdapter extends WebsocketAdapter {\n protected logger: LoggerService;\n private readonly requestMap: Map<\n number,\n {\n resolve: (value: JsonRpcResponseDto<any>) => void;\n reject: (reason: any) => void;\n timeout: NodeJS.Timeout;\n }\n >;\n\n protected constructor(loggerFactory: ILoggerFactory) {\n super(loggerFactory);\n\n this.logger = loggerFactory(WebsocketRpcExtendedAdapter.name);\n\n this.requestMap = new Map();\n }\n\n sendRequest<R, I = any>(\n request: JsonRpcRequestDto<I>,\n options?: { timeout: number },\n ): Promise<JsonRpcResponseDto<R>> {\n if (!this.socket) {\n throw new Error(\"Websocket was not created, cannot send request\");\n }\n\n request.id = request.id++;\n\n // Create a registered promise for the response\n const requestId = request.id;\n const promise = new Promise<JsonRpcResponseDto<R>>((resolve, reject) => {\n const timeout = setTimeout(\n () => {\n this.clearRequest(requestId);\n reject(new Error(`Websocket RPC Request by ID ${requestId} timed out`));\n },\n Math.min(3000, options?.timeout ?? 10000),\n );\n\n this.requestMap.set(requestId, { resolve, reject, timeout });\n });\n\n // Send the request\n this.socket.send(JSON.stringify(request));\n\n return promise;\n }\n\n /**\n * @abstract\n * @param message\n * @protected\n */\n protected abstract onEventMessage(message: JsonRpcEventDto): Promise<void>;\n\n protected async onMessage(message: Data): Promise<void> {\n const response: JsonRpcResponseDto<any> = JSON.parse(message.toString());\n const requestId = response.id;\n if (!requestId) {\n const event = response as unknown as JsonRpcEventDto;\n return await this.onEventMessage(event);\n }\n\n const request = this.requestMap.get(requestId);\n if (!request) {\n this.logger.warn(\n `No request was associated with the provided request id ${response.id}, websocket RPC response has been dropped`,\n );\n return;\n }\n\n if (request) {\n clearTimeout(request.timeout);\n request.resolve(response);\n this.requestMap.delete(requestId);\n }\n }\n\n private clearRequest(requestId: number): void {\n const request = this.requestMap.get(requestId);\n if (request) {\n clearTimeout(request.timeout);\n this.requestMap.delete(requestId);\n }\n }\n\n /**\n * Clear all pending requests and their timeouts\n * This should be called when closing the adapter to prevent memory/timeout leaks\n */\n private clearAllRequests(): void {\n for (const [, request] of this.requestMap.entries()) {\n clearTimeout(request.timeout);\n request.reject(new Error(\"WebSocket adapter closed\"));\n }\n this.requestMap.clear();\n }\n\n /**\n * Override close to ensure proper cleanup of pending requests\n */\n close(): void {\n this.clearAllRequests();\n super.close();\n }\n}\n"],"mappings":";;AAQA,IAAsB,8BAAtB,MAAsB,oCAAoC,iBAAiB;CACzE;CACA;CASA,YAAsB,eAA+B;AACnD,QAAM,cAAc;AAEpB,OAAK,SAAS,cAAc,4BAA4B,KAAK;AAE7D,OAAK,6BAAa,IAAI,KAAK;;CAG7B,YACE,SACA,SACgC;AAChC,MAAI,CAAC,KAAK,OACR,OAAM,IAAI,MAAM,iDAAiD;AAGnE,UAAQ,KAAK,QAAQ;EAGrB,MAAM,YAAY,QAAQ;EAC1B,MAAM,UAAU,IAAI,SAAgC,SAAS,WAAW;GACtE,MAAM,UAAU,iBACR;AACJ,SAAK,aAAa,UAAU;AAC5B,2BAAO,IAAI,MAAM,+BAA+B,UAAU,YAAY,CAAC;MAEzE,KAAK,IAAI,KAAM,SAAS,WAAW,IAAM,CAC1C;AAED,QAAK,WAAW,IAAI,WAAW;IAAE;IAAS;IAAQ;IAAS,CAAC;IAC5D;AAGF,OAAK,OAAO,KAAK,KAAK,UAAU,QAAQ,CAAC;AAEzC,SAAO;;CAUT,MAAgB,UAAU,SAA8B;EACtD,MAAM,WAAoC,KAAK,MAAM,QAAQ,UAAU,CAAC;EACxE,MAAM,YAAY,SAAS;AAC3B,MAAI,CAAC,WAAW;GACd,MAAM,QAAQ;AACd,UAAO,MAAM,KAAK,eAAe,MAAM;;EAGzC,MAAM,UAAU,KAAK,WAAW,IAAI,UAAU;AAC9C,MAAI,CAAC,SAAS;AACZ,QAAK,OAAO,KACV,0DAA0D,SAAS,GAAG,2CACvE;AACD;;AAGF,MAAI,SAAS;AACX,gBAAa,QAAQ,QAAQ;AAC7B,WAAQ,QAAQ,SAAS;AACzB,QAAK,WAAW,OAAO,UAAU;;;CAIrC,aAAqB,WAAyB;EAC5C,MAAM,UAAU,KAAK,WAAW,IAAI,UAAU;AAC9C,MAAI,SAAS;AACX,gBAAa,QAAQ,QAAQ;AAC7B,QAAK,WAAW,OAAO,UAAU;;;;;;;CAQrC,mBAAiC;AAC/B,OAAK,MAAM,GAAG,YAAY,KAAK,WAAW,SAAS,EAAE;AACnD,gBAAa,QAAQ,QAAQ;AAC7B,WAAQ,uBAAO,IAAI,MAAM,2BAA2B,CAAC;;AAEvD,OAAK,WAAW,OAAO;;;;;CAMzB,QAAc;AACZ,OAAK,kBAAkB;AACvB,QAAM,OAAO"}
1
+ {"version":3,"file":"websocket-rpc-extended.adapter.js","names":[],"sources":["../../src/shared/websocket-rpc-extended.adapter.ts"],"sourcesContent":["import { WebsocketAdapter } from \"@/shared/websocket.adapter\";\nimport type { JsonRpcResponseDto } from \"@/services/moonraker/dto/rpc/json-rpc-response.dto\";\nimport { Data } from \"ws\";\nimport type { JsonRpcRequestDto } from \"@/services/moonraker/dto/rpc/json-rpc-request.dto\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { JsonRpcEventDto } from \"@/services/moonraker/dto/websocket/json-rpc-event.dto\";\n\nexport abstract class WebsocketRpcExtendedAdapter extends WebsocketAdapter {\n protected logger: LoggerService;\n private readonly requestMap: Map<\n number,\n {\n resolve: (value: JsonRpcResponseDto<any>) => void;\n reject: (reason: any) => void;\n timeout: NodeJS.Timeout;\n }\n >;\n\n protected constructor(loggerFactory: ILoggerFactory) {\n super(loggerFactory);\n\n this.logger = loggerFactory(WebsocketRpcExtendedAdapter.name);\n\n this.requestMap = new Map();\n }\n\n sendRequest<R, I = any>(\n request: JsonRpcRequestDto<I>,\n options?: { timeout: number },\n ): Promise<JsonRpcResponseDto<R>> {\n if (!this.socket) {\n throw new Error(\"Websocket was not created, cannot send request\");\n }\n\n request.id = request.id++;\n\n // Create a registered promise for the response\n const requestId = request.id;\n const promise = new Promise<JsonRpcResponseDto<R>>((resolve, reject) => {\n const timeout = setTimeout(\n () => {\n this.clearRequest(requestId);\n reject(new Error(`Websocket RPC Request by ID ${requestId} timed out`));\n },\n Math.min(3000, options?.timeout ?? 10000),\n );\n\n this.requestMap.set(requestId, { resolve, reject, timeout });\n });\n\n // Send the request\n this.socket.send(JSON.stringify(request));\n\n return promise;\n }\n\n /**\n * @abstract\n * @param message\n * @protected\n */\n protected abstract onEventMessage(message: JsonRpcEventDto): Promise<void>;\n\n protected async onMessage(message: Data): Promise<void> {\n const response: JsonRpcResponseDto<any> = JSON.parse(message.toString());\n const requestId = response.id;\n if (!requestId) {\n const event = response as unknown as JsonRpcEventDto;\n return await this.onEventMessage(event);\n }\n\n const request = this.requestMap.get(requestId);\n if (!request) {\n this.logger.warn(\n `No request was associated with the provided request id ${response.id}, websocket RPC response has been dropped`,\n );\n return;\n }\n\n if (request) {\n clearTimeout(request.timeout);\n request.resolve(response);\n this.requestMap.delete(requestId);\n }\n }\n\n private clearRequest(requestId: number): void {\n const request = this.requestMap.get(requestId);\n if (request) {\n clearTimeout(request.timeout);\n this.requestMap.delete(requestId);\n }\n }\n\n /**\n * Clear all pending requests and their timeouts\n * This should be called when closing the adapter to prevent memory/timeout leaks\n */\n private clearAllRequests(): void {\n for (const [, request] of this.requestMap.entries()) {\n clearTimeout(request.timeout);\n request.reject(new Error(\"WebSocket adapter closed\"));\n }\n this.requestMap.clear();\n }\n\n /**\n * Override close to ensure proper cleanup of pending requests\n */\n close(): void {\n this.clearAllRequests();\n super.close();\n }\n}\n"],"mappings":";;AAQA,IAAsB,8BAAtB,MAAsB,oCAAoC,iBAAiB;CACzE;CACA;CASA,YAAsB,eAA+B;EACnD,MAAM,cAAc;EAEpB,KAAK,SAAS,cAAc,4BAA4B,KAAK;EAE7D,KAAK,6BAAa,IAAI,KAAK;;CAG7B,YACE,SACA,SACgC;EAChC,IAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,iDAAiD;EAGnE,QAAQ,KAAK,QAAQ;EAGrB,MAAM,YAAY,QAAQ;EAC1B,MAAM,UAAU,IAAI,SAAgC,SAAS,WAAW;GACtE,MAAM,UAAU,iBACR;IACJ,KAAK,aAAa,UAAU;IAC5B,uBAAO,IAAI,MAAM,+BAA+B,UAAU,YAAY,CAAC;MAEzE,KAAK,IAAI,KAAM,SAAS,WAAW,IAAM,CAC1C;GAED,KAAK,WAAW,IAAI,WAAW;IAAE;IAAS;IAAQ;IAAS,CAAC;IAC5D;EAGF,KAAK,OAAO,KAAK,KAAK,UAAU,QAAQ,CAAC;EAEzC,OAAO;;CAUT,MAAgB,UAAU,SAA8B;EACtD,MAAM,WAAoC,KAAK,MAAM,QAAQ,UAAU,CAAC;EACxE,MAAM,YAAY,SAAS;EAC3B,IAAI,CAAC,WAAW;GACd,MAAM,QAAQ;GACd,OAAO,MAAM,KAAK,eAAe,MAAM;;EAGzC,MAAM,UAAU,KAAK,WAAW,IAAI,UAAU;EAC9C,IAAI,CAAC,SAAS;GACZ,KAAK,OAAO,KACV,0DAA0D,SAAS,GAAG,2CACvE;GACD;;EAGF,IAAI,SAAS;GACX,aAAa,QAAQ,QAAQ;GAC7B,QAAQ,QAAQ,SAAS;GACzB,KAAK,WAAW,OAAO,UAAU;;;CAIrC,aAAqB,WAAyB;EAC5C,MAAM,UAAU,KAAK,WAAW,IAAI,UAAU;EAC9C,IAAI,SAAS;GACX,aAAa,QAAQ,QAAQ;GAC7B,KAAK,WAAW,OAAO,UAAU;;;;;;;CAQrC,mBAAiC;EAC/B,KAAK,MAAM,GAAG,YAAY,KAAK,WAAW,SAAS,EAAE;GACnD,aAAa,QAAQ,QAAQ;GAC7B,QAAQ,uBAAO,IAAI,MAAM,2BAA2B,CAAC;;EAEvD,KAAK,WAAW,OAAO;;;;;CAMzB,QAAc;EACZ,KAAK,kBAAkB;EACvB,MAAM,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.adapter.js","names":[],"sources":["../../src/shared/websocket.adapter.ts"],"sourcesContent":["import { AppConstants } from \"@/server.constants\";\nimport { CloseEvent, Data, ErrorEvent, Event as WsEvent, WebSocket } from \"ws\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\n\nexport abstract class WebsocketAdapter {\n socket?: WebSocket;\n protected logger: LoggerService;\n eventEmittingAllowed: boolean = true;\n\n protected constructor(loggerFactory: ILoggerFactory) {\n this.logger = loggerFactory(WebsocketAdapter.name);\n }\n\n get isOpened() {\n return this.socket?.readyState === WebSocket.OPEN;\n }\n\n public close() {\n this.socket?.close();\n delete this.socket;\n }\n\n public allowEmittingEvents() {\n this.eventEmittingAllowed = true;\n }\n\n public disallowEmittingEvents() {\n this.eventEmittingAllowed = false;\n }\n\n /**\n * @protected\n * Open a WebSocket connection.\n *\n * @param {string|URL} url The URL to connect to.\n * @returns {void}\n */\n open(url?: URL): void {\n if (!url) {\n throw new Error(\"Cant setup up websocket, URL may not be empty.\");\n }\n this.socket = new WebSocket(url, {\n handshakeTimeout: AppConstants.defaultWebsocketHandshakeTimeout,\n });\n this.socket.onopen = (event) => this.onOpen(event);\n this.socket.onerror = (error) => this.onError(error);\n this.socket.onclose = (event) => this.onClose(event);\n this.socket.onmessage = (message) => this.onMessage(message.data);\n }\n\n /**\n * @protected\n * Send a message through the WebSocket connection.\n *\n * @param {string} payload - The message payload to send.\n * @returns {Promise<void>} A promise that resolves when the message is sent successfully.\n */\n protected async sendMessage(payload: string): Promise<void> {\n if (!this.isOpened || !this.socket) {\n this.logger.error(\"Websocket is not opened, cant send a message\");\n return;\n }\n return await new Promise((resolve, reject) => {\n this.socket!.send(payload, (error) => {\n if (error) reject(error);\n resolve();\n });\n });\n }\n\n /**\n * Handle error event.\n * @protected\n * @abstract\n * @param {Event} error - The error event object.\n * @returns {Promise<void> | void} A promise that resolves when the error handling is complete, or void if no promise is returned.\n */\n protected abstract onError(error: ErrorEvent): Promise<void> | void;\n\n /**\n * Handle after opened event.\n * @protected\n * @abstract\n * @param {Event} event - The event object.\n * @returns {Promise<void> | void} A promise that resolves when the after opened handling is complete, or void if no promise is returned.\n */\n protected abstract afterOpened(event: WsEvent): Promise<void> | void;\n\n /**\n * Handle after closed event.\n * @protected\n * @abstract\n * @param {CloseEvent} event - The event object.\n * @returns {Promise<void> | void} A promise that resolves when the after closed handling is complete, or void if no promise is returned.\n */\n protected abstract afterClosed(event: CloseEvent): Promise<void> | void;\n\n /**\n * Handle message event.\n * @protected\n * @abstract\n * @param {Data} event - The event object.\n * @returns {Promise<void> | void} A promise that resolves when the message handling is complete, or void if no promise is returned.\n */\n protected abstract onMessage(event: Data): Promise<void> | void;\n\n /**\n * Handle open event.\n * @private\n * @param {Event} event - The event object.\n * @returns {Promise<void>} A promise that resolves when the open handling is complete.\n */\n private async onOpen(event: WsEvent): Promise<void> {\n await this.afterOpened(event);\n }\n\n /**\n * Handle close event.\n * @private\n * @param {CloseEvent} event - The event object.\n * @returns {Promise<void>} A promise that resolves when the close handling is complete.\n */\n private async onClose(event: CloseEvent): Promise<void> {\n await this.afterClosed(event);\n }\n}\n"],"mappings":";;;AAKA,IAAsB,mBAAtB,MAAsB,iBAAiB;CACrC;CACA;CACA,uBAAgC;CAEhC,YAAsB,eAA+B;AACnD,OAAK,SAAS,cAAc,iBAAiB,KAAK;;CAGpD,IAAI,WAAW;AACb,SAAO,KAAK,QAAQ,eAAe,UAAU;;CAG/C,QAAe;AACb,OAAK,QAAQ,OAAO;AACpB,SAAO,KAAK;;CAGd,sBAA6B;AAC3B,OAAK,uBAAuB;;CAG9B,yBAAgC;AAC9B,OAAK,uBAAuB;;;;;;;;;CAU9B,KAAK,KAAiB;AACpB,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,iDAAiD;AAEnE,OAAK,SAAS,IAAI,UAAU,KAAK,EAC/B,kBAAkB,aAAa,kCAChC,CAAC;AACF,OAAK,OAAO,UAAU,UAAU,KAAK,OAAO,MAAM;AAClD,OAAK,OAAO,WAAW,UAAU,KAAK,QAAQ,MAAM;AACpD,OAAK,OAAO,WAAW,UAAU,KAAK,QAAQ,MAAM;AACpD,OAAK,OAAO,aAAa,YAAY,KAAK,UAAU,QAAQ,KAAK;;;;;;;;;CAUnE,MAAgB,YAAY,SAAgC;AAC1D,MAAI,CAAC,KAAK,YAAY,CAAC,KAAK,QAAQ;AAClC,QAAK,OAAO,MAAM,+CAA+C;AACjE;;AAEF,SAAO,MAAM,IAAI,SAAS,SAAS,WAAW;AAC5C,QAAK,OAAQ,KAAK,UAAU,UAAU;AACpC,QAAI,MAAO,QAAO,MAAM;AACxB,aAAS;KACT;IACF;;;;;;;;CA6CJ,MAAc,OAAO,OAA+B;AAClD,QAAM,KAAK,YAAY,MAAM;;;;;;;;CAS/B,MAAc,QAAQ,OAAkC;AACtD,QAAM,KAAK,YAAY,MAAM"}
1
+ {"version":3,"file":"websocket.adapter.js","names":[],"sources":["../../src/shared/websocket.adapter.ts"],"sourcesContent":["import { AppConstants } from \"@/server.constants\";\nimport { CloseEvent, Data, ErrorEvent, Event as WsEvent, WebSocket } from \"ws\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\n\nexport abstract class WebsocketAdapter {\n socket?: WebSocket;\n protected logger: LoggerService;\n eventEmittingAllowed: boolean = true;\n\n protected constructor(loggerFactory: ILoggerFactory) {\n this.logger = loggerFactory(WebsocketAdapter.name);\n }\n\n get isOpened() {\n return this.socket?.readyState === WebSocket.OPEN;\n }\n\n public close() {\n this.socket?.close();\n delete this.socket;\n }\n\n public allowEmittingEvents() {\n this.eventEmittingAllowed = true;\n }\n\n public disallowEmittingEvents() {\n this.eventEmittingAllowed = false;\n }\n\n /**\n * @protected\n * Open a WebSocket connection.\n *\n * @param {string|URL} url The URL to connect to.\n * @returns {void}\n */\n open(url?: URL): void {\n if (!url) {\n throw new Error(\"Cant setup up websocket, URL may not be empty.\");\n }\n this.socket = new WebSocket(url, {\n handshakeTimeout: AppConstants.defaultWebsocketHandshakeTimeout,\n });\n this.socket.onopen = (event) => this.onOpen(event);\n this.socket.onerror = (error) => this.onError(error);\n this.socket.onclose = (event) => this.onClose(event);\n this.socket.onmessage = (message) => this.onMessage(message.data);\n }\n\n /**\n * @protected\n * Send a message through the WebSocket connection.\n *\n * @param {string} payload - The message payload to send.\n * @returns {Promise<void>} A promise that resolves when the message is sent successfully.\n */\n protected async sendMessage(payload: string): Promise<void> {\n if (!this.isOpened || !this.socket) {\n this.logger.error(\"Websocket is not opened, cant send a message\");\n return;\n }\n return await new Promise((resolve, reject) => {\n this.socket!.send(payload, (error) => {\n if (error) reject(error);\n resolve();\n });\n });\n }\n\n /**\n * Handle error event.\n * @protected\n * @abstract\n * @param {Event} error - The error event object.\n * @returns {Promise<void> | void} A promise that resolves when the error handling is complete, or void if no promise is returned.\n */\n protected abstract onError(error: ErrorEvent): Promise<void> | void;\n\n /**\n * Handle after opened event.\n * @protected\n * @abstract\n * @param {Event} event - The event object.\n * @returns {Promise<void> | void} A promise that resolves when the after opened handling is complete, or void if no promise is returned.\n */\n protected abstract afterOpened(event: WsEvent): Promise<void> | void;\n\n /**\n * Handle after closed event.\n * @protected\n * @abstract\n * @param {CloseEvent} event - The event object.\n * @returns {Promise<void> | void} A promise that resolves when the after closed handling is complete, or void if no promise is returned.\n */\n protected abstract afterClosed(event: CloseEvent): Promise<void> | void;\n\n /**\n * Handle message event.\n * @protected\n * @abstract\n * @param {Data} event - The event object.\n * @returns {Promise<void> | void} A promise that resolves when the message handling is complete, or void if no promise is returned.\n */\n protected abstract onMessage(event: Data): Promise<void> | void;\n\n /**\n * Handle open event.\n * @private\n * @param {Event} event - The event object.\n * @returns {Promise<void>} A promise that resolves when the open handling is complete.\n */\n private async onOpen(event: WsEvent): Promise<void> {\n await this.afterOpened(event);\n }\n\n /**\n * Handle close event.\n * @private\n * @param {CloseEvent} event - The event object.\n * @returns {Promise<void>} A promise that resolves when the close handling is complete.\n */\n private async onClose(event: CloseEvent): Promise<void> {\n await this.afterClosed(event);\n }\n}\n"],"mappings":";;;AAKA,IAAsB,mBAAtB,MAAsB,iBAAiB;CACrC;CACA;CACA,uBAAgC;CAEhC,YAAsB,eAA+B;EACnD,KAAK,SAAS,cAAc,iBAAiB,KAAK;;CAGpD,IAAI,WAAW;EACb,OAAO,KAAK,QAAQ,eAAe,UAAU;;CAG/C,QAAe;EACb,KAAK,QAAQ,OAAO;EACpB,OAAO,KAAK;;CAGd,sBAA6B;EAC3B,KAAK,uBAAuB;;CAG9B,yBAAgC;EAC9B,KAAK,uBAAuB;;;;;;;;;CAU9B,KAAK,KAAiB;EACpB,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,iDAAiD;EAEnE,KAAK,SAAS,IAAI,UAAU,KAAK,EAC/B,kBAAkB,aAAa,kCAChC,CAAC;EACF,KAAK,OAAO,UAAU,UAAU,KAAK,OAAO,MAAM;EAClD,KAAK,OAAO,WAAW,UAAU,KAAK,QAAQ,MAAM;EACpD,KAAK,OAAO,WAAW,UAAU,KAAK,QAAQ,MAAM;EACpD,KAAK,OAAO,aAAa,YAAY,KAAK,UAAU,QAAQ,KAAK;;;;;;;;;CAUnE,MAAgB,YAAY,SAAgC;EAC1D,IAAI,CAAC,KAAK,YAAY,CAAC,KAAK,QAAQ;GAClC,KAAK,OAAO,MAAM,+CAA+C;GACjE;;EAEF,OAAO,MAAM,IAAI,SAAS,SAAS,WAAW;GAC5C,KAAK,OAAQ,KAAK,UAAU,UAAU;IACpC,IAAI,OAAO,OAAO,MAAM;IACxB,SAAS;KACT;IACF;;;;;;;;CA6CJ,MAAc,OAAO,OAA+B;EAClD,MAAM,KAAK,YAAY,MAAM;;;;;;;;CAS/B,MAAc,QAAQ,OAAkC;EACtD,MAAM,KAAK,YAAY,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-upload-tracker.cache.js","names":[],"sources":["../../src/state/file-upload-tracker.cache.ts"],"sourcesContent":["import { generateCorrelationToken } from \"@/utils/correlation-token.util\";\nimport { uploadDoneEvent, uploadFailedEvent, uploadProgressEvent } from \"@/constants/event.constants\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { AxiosProgressEvent } from \"axios\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { TrackedUpload } from \"@/services/interfaces/file-upload-tracker.interface\";\n\nexport class FileUploadTrackerCache {\n private readonly currentUploads: TrackedUpload[] = [];\n\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly eventEmitter2: EventEmitter2,\n ) {\n this.logger = loggerFactory(FileUploadTrackerCache.name);\n\n this.eventEmitter2.on(uploadProgressEvent(\"*\"), (token, progress) => this.handleUploadProgress(token, progress));\n this.eventEmitter2.on(uploadDoneEvent(\"*\"), (token: string) => this.handleUploadDone(token));\n this.eventEmitter2.on(uploadFailedEvent(\"*\"), (token: string, reason?: string) =>\n this.handleUploadFailed(token, reason),\n );\n }\n\n getUploads() {\n return {\n current: this.currentUploads,\n };\n }\n\n getUpload(correlationToken: string) {\n return this.currentUploads.find((cu) => cu.correlationToken === correlationToken);\n }\n\n addUploadTracker(multerFile: Express.Multer.File, printerId: number) {\n const correlationToken = generateCorrelationToken();\n this.logger.log(`Starting upload session with token ${correlationToken}`);\n\n this.currentUploads.push({\n correlationToken,\n printerId,\n startedAt: Date.now(),\n multerFile,\n progress: 0,\n completed: false,\n completedAt: null,\n success: null,\n reason: null,\n });\n\n return correlationToken;\n }\n\n handleUploadProgress(token: string, event: AxiosProgressEvent) {\n const upload = this.getUpload(token);\n if (!upload) {\n return;\n }\n upload.progress = event.progress ?? null;\n }\n\n handleUploadFailed(token: string, reason?: string) {\n this.logger.log(`Upload tracker ${token} completed with failure`);\n this.markUploadDone(token, false, reason);\n }\n\n handleUploadDone(token: string) {\n this.logger.log(`Upload tracker ${token} completed with success`);\n this.markUploadDone(token, true);\n }\n\n markUploadDone(token: string, success: boolean, reason?: string) {\n const trackedUploadIndex = this.currentUploads.findIndex((cu) => cu.correlationToken === token);\n if (trackedUploadIndex === -1) {\n this.logger.warn(`Could not mark upload tracker with correlation token '${token}' as done as it was not found.`);\n return;\n }\n\n const trackedUpload = this.currentUploads[trackedUploadIndex];\n\n trackedUpload.completed = true;\n trackedUpload.success = success;\n trackedUpload.completedAt = Date.now();\n trackedUpload.reason = reason ?? null;\n }\n}\n"],"mappings":";;;AAQA,IAAa,yBAAb,MAAa,uBAAuB;CAClC,iBAAmD,EAAE;CAErD;CAEA,YACE,eACA,eACA;AADiB,OAAA,gBAAA;AAEjB,OAAK,SAAS,cAAc,uBAAuB,KAAK;AAExD,OAAK,cAAc,GAAG,oBAAoB,IAAI,GAAG,OAAO,aAAa,KAAK,qBAAqB,OAAO,SAAS,CAAC;AAChH,OAAK,cAAc,GAAG,gBAAgB,IAAI,GAAG,UAAkB,KAAK,iBAAiB,MAAM,CAAC;AAC5F,OAAK,cAAc,GAAG,kBAAkB,IAAI,GAAG,OAAe,WAC5D,KAAK,mBAAmB,OAAO,OAAO,CACvC;;CAGH,aAAa;AACX,SAAO,EACL,SAAS,KAAK,gBACf;;CAGH,UAAU,kBAA0B;AAClC,SAAO,KAAK,eAAe,MAAM,OAAO,GAAG,qBAAqB,iBAAiB;;CAGnF,iBAAiB,YAAiC,WAAmB;EACnE,MAAM,mBAAmB,0BAA0B;AACnD,OAAK,OAAO,IAAI,sCAAsC,mBAAmB;AAEzE,OAAK,eAAe,KAAK;GACvB;GACA;GACA,WAAW,KAAK,KAAK;GACrB;GACA,UAAU;GACV,WAAW;GACX,aAAa;GACb,SAAS;GACT,QAAQ;GACT,CAAC;AAEF,SAAO;;CAGT,qBAAqB,OAAe,OAA2B;EAC7D,MAAM,SAAS,KAAK,UAAU,MAAM;AACpC,MAAI,CAAC,OACH;AAEF,SAAO,WAAW,MAAM,YAAY;;CAGtC,mBAAmB,OAAe,QAAiB;AACjD,OAAK,OAAO,IAAI,kBAAkB,MAAM,yBAAyB;AACjE,OAAK,eAAe,OAAO,OAAO,OAAO;;CAG3C,iBAAiB,OAAe;AAC9B,OAAK,OAAO,IAAI,kBAAkB,MAAM,yBAAyB;AACjE,OAAK,eAAe,OAAO,KAAK;;CAGlC,eAAe,OAAe,SAAkB,QAAiB;EAC/D,MAAM,qBAAqB,KAAK,eAAe,WAAW,OAAO,GAAG,qBAAqB,MAAM;AAC/F,MAAI,uBAAuB,IAAI;AAC7B,QAAK,OAAO,KAAK,yDAAyD,MAAM,gCAAgC;AAChH;;EAGF,MAAM,gBAAgB,KAAK,eAAe;AAE1C,gBAAc,YAAY;AAC1B,gBAAc,UAAU;AACxB,gBAAc,cAAc,KAAK,KAAK;AACtC,gBAAc,SAAS,UAAU"}
1
+ {"version":3,"file":"file-upload-tracker.cache.js","names":[],"sources":["../../src/state/file-upload-tracker.cache.ts"],"sourcesContent":["import { generateCorrelationToken } from \"@/utils/correlation-token.util\";\nimport { uploadDoneEvent, uploadFailedEvent, uploadProgressEvent } from \"@/constants/event.constants\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { AxiosProgressEvent } from \"axios\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { TrackedUpload } from \"@/services/interfaces/file-upload-tracker.interface\";\n\nexport class FileUploadTrackerCache {\n private readonly currentUploads: TrackedUpload[] = [];\n\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly eventEmitter2: EventEmitter2,\n ) {\n this.logger = loggerFactory(FileUploadTrackerCache.name);\n\n this.eventEmitter2.on(uploadProgressEvent(\"*\"), (token, progress) => this.handleUploadProgress(token, progress));\n this.eventEmitter2.on(uploadDoneEvent(\"*\"), (token: string) => this.handleUploadDone(token));\n this.eventEmitter2.on(uploadFailedEvent(\"*\"), (token: string, reason?: string) =>\n this.handleUploadFailed(token, reason),\n );\n }\n\n getUploads() {\n return {\n current: this.currentUploads,\n };\n }\n\n getUpload(correlationToken: string) {\n return this.currentUploads.find((cu) => cu.correlationToken === correlationToken);\n }\n\n addUploadTracker(multerFile: Express.Multer.File, printerId: number) {\n const correlationToken = generateCorrelationToken();\n this.logger.log(`Starting upload session with token ${correlationToken}`);\n\n this.currentUploads.push({\n correlationToken,\n printerId,\n startedAt: Date.now(),\n multerFile,\n progress: 0,\n completed: false,\n completedAt: null,\n success: null,\n reason: null,\n });\n\n return correlationToken;\n }\n\n handleUploadProgress(token: string, event: AxiosProgressEvent) {\n const upload = this.getUpload(token);\n if (!upload) {\n return;\n }\n upload.progress = event.progress ?? null;\n }\n\n handleUploadFailed(token: string, reason?: string) {\n this.logger.log(`Upload tracker ${token} completed with failure`);\n this.markUploadDone(token, false, reason);\n }\n\n handleUploadDone(token: string) {\n this.logger.log(`Upload tracker ${token} completed with success`);\n this.markUploadDone(token, true);\n }\n\n markUploadDone(token: string, success: boolean, reason?: string) {\n const trackedUploadIndex = this.currentUploads.findIndex((cu) => cu.correlationToken === token);\n if (trackedUploadIndex === -1) {\n this.logger.warn(`Could not mark upload tracker with correlation token '${token}' as done as it was not found.`);\n return;\n }\n\n const trackedUpload = this.currentUploads[trackedUploadIndex];\n\n trackedUpload.completed = true;\n trackedUpload.success = success;\n trackedUpload.completedAt = Date.now();\n trackedUpload.reason = reason ?? null;\n }\n}\n"],"mappings":";;;AAQA,IAAa,yBAAb,MAAa,uBAAuB;CAClC,iBAAmD,EAAE;CAErD;CAEA,YACE,eACA,eACA;EADiB,KAAA,gBAAA;EAEjB,KAAK,SAAS,cAAc,uBAAuB,KAAK;EAExD,KAAK,cAAc,GAAG,oBAAoB,IAAI,GAAG,OAAO,aAAa,KAAK,qBAAqB,OAAO,SAAS,CAAC;EAChH,KAAK,cAAc,GAAG,gBAAgB,IAAI,GAAG,UAAkB,KAAK,iBAAiB,MAAM,CAAC;EAC5F,KAAK,cAAc,GAAG,kBAAkB,IAAI,GAAG,OAAe,WAC5D,KAAK,mBAAmB,OAAO,OAAO,CACvC;;CAGH,aAAa;EACX,OAAO,EACL,SAAS,KAAK,gBACf;;CAGH,UAAU,kBAA0B;EAClC,OAAO,KAAK,eAAe,MAAM,OAAO,GAAG,qBAAqB,iBAAiB;;CAGnF,iBAAiB,YAAiC,WAAmB;EACnE,MAAM,mBAAmB,0BAA0B;EACnD,KAAK,OAAO,IAAI,sCAAsC,mBAAmB;EAEzE,KAAK,eAAe,KAAK;GACvB;GACA;GACA,WAAW,KAAK,KAAK;GACrB;GACA,UAAU;GACV,WAAW;GACX,aAAa;GACb,SAAS;GACT,QAAQ;GACT,CAAC;EAEF,OAAO;;CAGT,qBAAqB,OAAe,OAA2B;EAC7D,MAAM,SAAS,KAAK,UAAU,MAAM;EACpC,IAAI,CAAC,QACH;EAEF,OAAO,WAAW,MAAM,YAAY;;CAGtC,mBAAmB,OAAe,QAAiB;EACjD,KAAK,OAAO,IAAI,kBAAkB,MAAM,yBAAyB;EACjE,KAAK,eAAe,OAAO,OAAO,OAAO;;CAG3C,iBAAiB,OAAe;EAC9B,KAAK,OAAO,IAAI,kBAAkB,MAAM,yBAAyB;EACjE,KAAK,eAAe,OAAO,KAAK;;CAGlC,eAAe,OAAe,SAAkB,QAAiB;EAC/D,MAAM,qBAAqB,KAAK,eAAe,WAAW,OAAO,GAAG,qBAAqB,MAAM;EAC/F,IAAI,uBAAuB,IAAI;GAC7B,KAAK,OAAO,KAAK,yDAAyD,MAAM,gCAAgC;GAChH;;EAGF,MAAM,gBAAgB,KAAK,eAAe;EAE1C,cAAc,YAAY;EAC1B,cAAc,UAAU;EACxB,cAAc,cAAc,KAAK,KAAK;EACtC,cAAc,SAAS,UAAU"}
@@ -1 +1 @@
1
- {"version":3,"file":"floor.store.js","names":[],"sources":["../../src/state/floor.store.ts"],"sourcesContent":["import { KeyDiffCache } from \"@/utils/cache/key-diff.cache\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { IFloorService } from \"@/services/interfaces/floor.service.interface\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport type { CreateFloorDto, FloorDto, PositionDto, UpdateFloorDto } from \"@/services/interfaces/floor.dto\";\n\nexport class FloorStore extends KeyDiffCache<FloorDto> {\n private readonly logger: LoggerService;\n\n constructor(\n private readonly floorService: IFloorService,\n loggerFactory: ILoggerFactory,\n ) {\n super();\n this.logger = loggerFactory(FloorStore.name);\n }\n\n async loadStore() {\n const floors = await this.floorService.list();\n\n const keyValues = floors.map((floor) => ({\n key: floor.id,\n value: this.floorService.toDto(floor),\n }));\n await this.setKeyValuesBatch(keyValues, true);\n }\n\n async listCache() {\n const floors = await this.getAllValues();\n if (floors?.length) {\n return floors;\n }\n\n await this.loadStore();\n return await this.getAllValues();\n }\n\n async create(input: CreateFloorDto) {\n const floor = await this.floorService.create(input);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floor.id, floorDto, true);\n return floorDto;\n }\n\n async delete(floorId: number) {\n await this.floorService.delete(floorId);\n await this.deleteKeyValue(floorId);\n }\n\n async getFloor(floorId: number) {\n let floor = await this.getValue(floorId);\n if (floor) {\n return floor;\n }\n\n const dbFloor = await this.floorService.get(floorId);\n const floorDto = this.floorService.toDto(dbFloor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async update(floorId: number, input: UpdateFloorDto) {\n const floor = await this.floorService.update(floorId, input);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async updateName(floorId: number, name: string) {\n const floor = await this.floorService.updateName(floorId, name);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async updateFloorOrder(floorId: number, order: number) {\n const floor = await this.floorService.updateOrder(floorId, order);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async addOrUpdatePrinter(floorId: number, position: PositionDto) {\n const floor = await this.floorService.addOrUpdatePrinter(floorId, position);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async removePrinter(floorId: number, printerId: number) {\n const floor = await this.floorService.removePrinter(floorId, printerId);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async removePrinterFromAnyFloor(printerId: number) {\n await this.floorService.deletePrinterFromAnyFloor(printerId);\n\n // Bit harsh, but we need to reload the entire store\n await this.loadStore();\n }\n}\n"],"mappings":";;AAMA,IAAa,aAAb,MAAa,mBAAmB,aAAuB;CACrD;CAEA,YACE,cACA,eACA;AACA,SAAO;AAHU,OAAA,eAAA;AAIjB,OAAK,SAAS,cAAc,WAAW,KAAK;;CAG9C,MAAM,YAAY;EAGhB,MAAM,aAAY,MAFG,KAAK,aAAa,MAAM,EAEpB,KAAK,WAAW;GACvC,KAAK,MAAM;GACX,OAAO,KAAK,aAAa,MAAM,MAAM;GACtC,EAAE;AACH,QAAM,KAAK,kBAAkB,WAAW,KAAK;;CAG/C,MAAM,YAAY;EAChB,MAAM,SAAS,MAAM,KAAK,cAAc;AACxC,MAAI,QAAQ,OACV,QAAO;AAGT,QAAM,KAAK,WAAW;AACtB,SAAO,MAAM,KAAK,cAAc;;CAGlC,MAAM,OAAO,OAAuB;EAClC,MAAM,QAAQ,MAAM,KAAK,aAAa,OAAO,MAAM;EACnD,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;AAC/C,QAAM,KAAK,YAAY,MAAM,IAAI,UAAU,KAAK;AAChD,SAAO;;CAGT,MAAM,OAAO,SAAiB;AAC5B,QAAM,KAAK,aAAa,OAAO,QAAQ;AACvC,QAAM,KAAK,eAAe,QAAQ;;CAGpC,MAAM,SAAS,SAAiB;EAC9B,IAAI,QAAQ,MAAM,KAAK,SAAS,QAAQ;AACxC,MAAI,MACF,QAAO;EAGT,MAAM,UAAU,MAAM,KAAK,aAAa,IAAI,QAAQ;EACpD,MAAM,WAAW,KAAK,aAAa,MAAM,QAAQ;AACjD,QAAM,KAAK,YAAY,SAAS,UAAU,KAAK;AAC/C,SAAO;;CAGT,MAAM,OAAO,SAAiB,OAAuB;EACnD,MAAM,QAAQ,MAAM,KAAK,aAAa,OAAO,SAAS,MAAM;EAC5D,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;AAC/C,QAAM,KAAK,YAAY,SAAS,UAAU,KAAK;AAC/C,SAAO;;CAGT,MAAM,WAAW,SAAiB,MAAc;EAC9C,MAAM,QAAQ,MAAM,KAAK,aAAa,WAAW,SAAS,KAAK;EAC/D,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;AAC/C,QAAM,KAAK,YAAY,SAAS,UAAU,KAAK;AAC/C,SAAO;;CAGT,MAAM,iBAAiB,SAAiB,OAAe;EACrD,MAAM,QAAQ,MAAM,KAAK,aAAa,YAAY,SAAS,MAAM;EACjE,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;AAC/C,QAAM,KAAK,YAAY,SAAS,UAAU,KAAK;AAC/C,SAAO;;CAGT,MAAM,mBAAmB,SAAiB,UAAuB;EAC/D,MAAM,QAAQ,MAAM,KAAK,aAAa,mBAAmB,SAAS,SAAS;EAC3E,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;AAC/C,QAAM,KAAK,YAAY,SAAS,UAAU,KAAK;AAC/C,SAAO;;CAGT,MAAM,cAAc,SAAiB,WAAmB;EACtD,MAAM,QAAQ,MAAM,KAAK,aAAa,cAAc,SAAS,UAAU;EACvE,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;AAC/C,QAAM,KAAK,YAAY,SAAS,UAAU,KAAK;AAC/C,SAAO;;CAGT,MAAM,0BAA0B,WAAmB;AACjD,QAAM,KAAK,aAAa,0BAA0B,UAAU;AAG5D,QAAM,KAAK,WAAW"}
1
+ {"version":3,"file":"floor.store.js","names":[],"sources":["../../src/state/floor.store.ts"],"sourcesContent":["import { KeyDiffCache } from \"@/utils/cache/key-diff.cache\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { IFloorService } from \"@/services/interfaces/floor.service.interface\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport type { CreateFloorDto, FloorDto, PositionDto, UpdateFloorDto } from \"@/services/interfaces/floor.dto\";\n\nexport class FloorStore extends KeyDiffCache<FloorDto> {\n private readonly logger: LoggerService;\n\n constructor(\n private readonly floorService: IFloorService,\n loggerFactory: ILoggerFactory,\n ) {\n super();\n this.logger = loggerFactory(FloorStore.name);\n }\n\n async loadStore() {\n const floors = await this.floorService.list();\n\n const keyValues = floors.map((floor) => ({\n key: floor.id,\n value: this.floorService.toDto(floor),\n }));\n await this.setKeyValuesBatch(keyValues, true);\n }\n\n async listCache() {\n const floors = await this.getAllValues();\n if (floors?.length) {\n return floors;\n }\n\n await this.loadStore();\n return await this.getAllValues();\n }\n\n async create(input: CreateFloorDto) {\n const floor = await this.floorService.create(input);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floor.id, floorDto, true);\n return floorDto;\n }\n\n async delete(floorId: number) {\n await this.floorService.delete(floorId);\n await this.deleteKeyValue(floorId);\n }\n\n async getFloor(floorId: number) {\n let floor = await this.getValue(floorId);\n if (floor) {\n return floor;\n }\n\n const dbFloor = await this.floorService.get(floorId);\n const floorDto = this.floorService.toDto(dbFloor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async update(floorId: number, input: UpdateFloorDto) {\n const floor = await this.floorService.update(floorId, input);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async updateName(floorId: number, name: string) {\n const floor = await this.floorService.updateName(floorId, name);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async updateFloorOrder(floorId: number, order: number) {\n const floor = await this.floorService.updateOrder(floorId, order);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async addOrUpdatePrinter(floorId: number, position: PositionDto) {\n const floor = await this.floorService.addOrUpdatePrinter(floorId, position);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async removePrinter(floorId: number, printerId: number) {\n const floor = await this.floorService.removePrinter(floorId, printerId);\n const floorDto = this.floorService.toDto(floor);\n await this.setKeyValue(floorId, floorDto, true);\n return floorDto;\n }\n\n async removePrinterFromAnyFloor(printerId: number) {\n await this.floorService.deletePrinterFromAnyFloor(printerId);\n\n // Bit harsh, but we need to reload the entire store\n await this.loadStore();\n }\n}\n"],"mappings":";;AAMA,IAAa,aAAb,MAAa,mBAAmB,aAAuB;CACrD;CAEA,YACE,cACA,eACA;EACA,OAAO;EAHU,KAAA,eAAA;EAIjB,KAAK,SAAS,cAAc,WAAW,KAAK;;CAG9C,MAAM,YAAY;EAGhB,MAAM,aAAY,MAFG,KAAK,aAAa,MAAM,EAEpB,KAAK,WAAW;GACvC,KAAK,MAAM;GACX,OAAO,KAAK,aAAa,MAAM,MAAM;GACtC,EAAE;EACH,MAAM,KAAK,kBAAkB,WAAW,KAAK;;CAG/C,MAAM,YAAY;EAChB,MAAM,SAAS,MAAM,KAAK,cAAc;EACxC,IAAI,QAAQ,QACV,OAAO;EAGT,MAAM,KAAK,WAAW;EACtB,OAAO,MAAM,KAAK,cAAc;;CAGlC,MAAM,OAAO,OAAuB;EAClC,MAAM,QAAQ,MAAM,KAAK,aAAa,OAAO,MAAM;EACnD,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;EAC/C,MAAM,KAAK,YAAY,MAAM,IAAI,UAAU,KAAK;EAChD,OAAO;;CAGT,MAAM,OAAO,SAAiB;EAC5B,MAAM,KAAK,aAAa,OAAO,QAAQ;EACvC,MAAM,KAAK,eAAe,QAAQ;;CAGpC,MAAM,SAAS,SAAiB;EAC9B,IAAI,QAAQ,MAAM,KAAK,SAAS,QAAQ;EACxC,IAAI,OACF,OAAO;EAGT,MAAM,UAAU,MAAM,KAAK,aAAa,IAAI,QAAQ;EACpD,MAAM,WAAW,KAAK,aAAa,MAAM,QAAQ;EACjD,MAAM,KAAK,YAAY,SAAS,UAAU,KAAK;EAC/C,OAAO;;CAGT,MAAM,OAAO,SAAiB,OAAuB;EACnD,MAAM,QAAQ,MAAM,KAAK,aAAa,OAAO,SAAS,MAAM;EAC5D,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;EAC/C,MAAM,KAAK,YAAY,SAAS,UAAU,KAAK;EAC/C,OAAO;;CAGT,MAAM,WAAW,SAAiB,MAAc;EAC9C,MAAM,QAAQ,MAAM,KAAK,aAAa,WAAW,SAAS,KAAK;EAC/D,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;EAC/C,MAAM,KAAK,YAAY,SAAS,UAAU,KAAK;EAC/C,OAAO;;CAGT,MAAM,iBAAiB,SAAiB,OAAe;EACrD,MAAM,QAAQ,MAAM,KAAK,aAAa,YAAY,SAAS,MAAM;EACjE,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;EAC/C,MAAM,KAAK,YAAY,SAAS,UAAU,KAAK;EAC/C,OAAO;;CAGT,MAAM,mBAAmB,SAAiB,UAAuB;EAC/D,MAAM,QAAQ,MAAM,KAAK,aAAa,mBAAmB,SAAS,SAAS;EAC3E,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;EAC/C,MAAM,KAAK,YAAY,SAAS,UAAU,KAAK;EAC/C,OAAO;;CAGT,MAAM,cAAc,SAAiB,WAAmB;EACtD,MAAM,QAAQ,MAAM,KAAK,aAAa,cAAc,SAAS,UAAU;EACvE,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;EAC/C,MAAM,KAAK,YAAY,SAAS,UAAU,KAAK;EAC/C,OAAO;;CAGT,MAAM,0BAA0B,WAAmB;EACjD,MAAM,KAAK,aAAa,0BAA0B,UAAU;EAG5D,MAAM,KAAK,WAAW"}
@@ -1 +1 @@
1
- {"version":3,"file":"printer-events.cache.js","names":[],"sources":["../../src/state/printer-events.cache.ts"],"sourcesContent":["import { KeyDiffCache } from \"@/utils/cache/key-diff.cache\";\nimport { printerEvents, PrintersDeletedEvent } from \"@/constants/event.constants\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { WsMessage, messages, type OctoPrintEventDto } from \"@/services/octoprint/dto/octoprint-event.dto\";\nimport { MR_WsMessage, type MoonrakerEventDto } from \"@/services/moonraker/constants/moonraker-event.dto\";\nimport type { PrinterObjectsQueryDto } from \"@/services/moonraker/dto/objects/printer-objects-query.dto\";\nimport { SubscriptionType } from \"@/services/moonraker/moonraker-websocket.adapter\";\nimport { octoPrintEvent } from \"@/services/octoprint/octoprint-websocket.adapter\";\nimport { moonrakerEvent } from \"@/services/moonraker/constants/moonraker.constants\";\nimport { prusaLinkEvent } from \"@/services/prusa-link/constants/prusalink.constants\";\nimport type { PrusaLinkEventDto } from \"@/services/prusa-link/constants/prusalink-event.dto\";\nimport { bambuEvent, type BambuEventDto } from \"@/services/bambu/bambu-mqtt.adapter\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { PrintJobService } from \"@/services/orm/print-job.service\";\nimport { PrinterCache } from \"@/state/printer.cache\";\nimport { PrinterThumbnailCache } from \"@/state/printer-thumbnail.cache\";\nimport type { CurrentMessageDto } from \"@/services/octoprint/dto/websocket/current-message.dto\";\n\nexport type WsMessageWithoutEventAndPlugin = Exclude<WsMessage, \"event\" | \"plugin\">;\nexport type PrinterEventsCacheDto = Record<WsMessageWithoutEventAndPlugin, any>;\n\nexport class PrinterEventsCache extends KeyDiffCache<PrinterEventsCacheDto> {\n private readonly logger: LoggerService;\n\n constructor(\n private readonly eventEmitter2: EventEmitter2,\n loggerFactory: ILoggerFactory,\n private readonly printJobService: PrintJobService,\n private readonly printerCache: PrinterCache,\n private readonly printerThumbnailCache: PrinterThumbnailCache,\n ) {\n super();\n\n this.logger = loggerFactory(PrinterEventsCache.name);\n this.subscribeToEvents();\n }\n\n async deletePrinterSocketEvents(id: number) {\n await this.deleteKeyValue(id, true);\n }\n\n async getPrinterSocketEvents(id: number) {\n return this.keyValueStore.get(id);\n }\n\n async getOrCreateEvents(printerId: number) {\n let ref = await this.getValue(printerId);\n if (!ref) {\n ref = {\n connected: null,\n reauthRequired: null,\n // slicingProgress: null,\n notify_status_update: null,\n current: null,\n history: null,\n // timelapse: null,\n // event: {},\n // plugin: {},\n API_STATE_UPDATED: null,\n WS_CLOSED: null,\n WS_ERROR: null,\n WS_OPENED: null,\n WS_STATE_UPDATED: null,\n };\n await this.setKeyValue(printerId, ref);\n }\n return ref;\n }\n\n async setEvent(printerId: number, label: WsMessageWithoutEventAndPlugin, payload: any) {\n const ref = await this.getOrCreateEvents(printerId);\n ref[label] = {\n payload,\n receivedAt: Date.now(),\n };\n await this.setKeyValue(printerId, ref);\n }\n\n async setEventPartial(\n printerId: number,\n label: WsMessageWithoutEventAndPlugin,\n payload: any,\n keysToUpdate: string[],\n ) {\n const ref = await this.getOrCreateEvents(printerId);\n if (!ref[label]) {\n ref[label] = {\n payload: {},\n receivedAt: Date.now(),\n };\n }\n\n for (const key of keysToUpdate) {\n ref[label].payload[key] = payload[key];\n }\n\n ref[label].receivedAt = Date.now();\n await this.setKeyValue(printerId, ref);\n }\n\n private async handlePrintersDeleted(event: PrintersDeletedEvent) {\n await this.deleteKeysBatch(event.printerIds);\n }\n\n private subscribeToEvents() {\n this.eventEmitter2.on(octoPrintEvent(\"*\"), (e) => this.onOctoPrintSocketMessage(e));\n this.eventEmitter2.on(moonrakerEvent(\"*\"), (e) => this.onMoonrakerSocketMessage(e));\n this.eventEmitter2.on(prusaLinkEvent(\"*\"), (e) => this.onPrusaLinkPollMessage(e));\n this.eventEmitter2.on(bambuEvent(\"*\"), (e) => this.onBambuSocketMessage(e));\n this.eventEmitter2.on(printerEvents.printersDeleted, this.handlePrintersDeleted.bind(this));\n }\n\n private async getPrinterName(printerId: number): Promise<string | undefined> {\n try {\n const printer = await this.printerCache.getValue(printerId);\n return printer?.name;\n } catch (error) {\n this.logger.debug(`Could not get printer name for ${printerId}: ${error}`);\n return undefined;\n }\n }\n\n private async onOctoPrintSocketMessage(e: OctoPrintEventDto) {\n const printerId = e.printerId;\n if (e.event === messages.current && e.payload) {\n const payload = e.payload as CurrentMessageDto;\n\n const keysToUpdate = [\"state\", \"job\", \"progress\", \"currentZ\", \"offsets\", \"resends\"];\n\n if (Array.isArray(payload.temps) && payload.temps.length > 0) {\n keysToUpdate.push(\"temps\");\n }\n\n await this.setEventPartial(printerId, messages.current, payload, keysToUpdate);\n\n const flags = payload?.state?.flags;\n const filePath = payload?.job?.file?.path;\n const completion = payload?.progress?.completion;\n\n if (flags?.printing && filePath) {\n const printerName = await this.getPrinterName(printerId);\n const job = await this.printJobService.markStarted(printerId, filePath, printerName);\n\n if (job) {\n await this.printerThumbnailCache.handleJobStarted(printerId, job.id);\n }\n\n if (job && !job.fileStorageId && job.analysisState === \"NOT_ANALYZED\") {\n this.logger.log(`Job ${job.id} has no local file - triggering download and analysis`);\n await this.printJobService.triggerFileAnalysis(job.id);\n }\n\n // Update metadata from OctoPrint job data if available (as fallback)\n if (job && !job.metadata?.gcodePrintTimeSeconds) {\n const estimatedTime = payload?.job?.estimatedPrintTime;\n const filament = payload?.job?.filament?.tool0;\n\n await this.printJobService.updateJobMetadata(printerId, filePath, {\n gcodePrintTimeSeconds: estimatedTime ? Math.round(estimatedTime) : null,\n nozzleDiameterMm: null,\n filamentDiameterMm: null,\n filamentDensityGramsCm3: null,\n filamentUsedMm: filament?.length ? Math.round(filament.length) : null,\n filamentUsedCm3: filament?.volume ? Math.round(filament.volume * 100) / 100 : null,\n filamentUsedGrams: null,\n totalFilamentUsedGrams: null,\n });\n }\n }\n if (typeof completion === \"number\" && filePath) {\n await this.printJobService.markProgress(printerId, filePath, completion);\n }\n if ((flags?.finishing || flags?.error || flags?.cancelling) && filePath) {\n if (flags?.finishing) {\n const job = await this.printJobService.markFinished(printerId, filePath);\n if (job) {\n await this.printerThumbnailCache.handleJobCompleted(printerId, job.id);\n }\n } else if (flags?.cancelling) {\n await this.printJobService.markFailed(printerId, filePath, \"Cancelled\");\n } else {\n await this.printJobService.markFailed(printerId, filePath, \"Error\");\n }\n }\n }\n }\n\n private async onMoonrakerSocketMessage(\n e: MoonrakerEventDto<MR_WsMessage, PrinterObjectsQueryDto<SubscriptionType | null>>,\n ) {\n const printerId = e.printerId;\n const eventType = e.event;\n // https://github.com/mainsail-crew/mainsail/blob/fa61d4ef92 97426a404dd845a1a4d5e4525c43dc/src/components/panels/StatusPanel.vue#L199\n if ([messages.notify_status_update, messages.current].includes(eventType as any)) {\n await this.setEvent(printerId, eventType as \"notify_status_update\" | \"current\", e.payload);\n const payload = e.payload as any;\n const status = payload?.status ?? payload?.result ?? {};\n const filename = status?.print_stats?.filename;\n const progress = status?.display_status?.progress ?? status?.print_stats?.progress;\n const state = status?.print_stats?.state;\n if (state === \"printing\" && filename) {\n const printerName = await this.getPrinterName(printerId);\n const job = await this.printJobService.markStarted(printerId, filename, printerName);\n\n if (job) {\n await this.printerThumbnailCache.handleJobStarted(printerId, job.id);\n }\n\n if (job && !job.fileStorageId && job.analysisState === \"NOT_ANALYZED\") {\n this.logger.log(`Job ${job.id} has no local file - triggering download and analysis`);\n await this.printJobService.triggerFileAnalysis(job.id);\n }\n }\n if (typeof progress === \"number\" && filename) {\n await this.printJobService.markProgress(printerId, filename, Math.round(progress * 100));\n }\n if ([\"complete\", \"cancelled\", \"error\"].includes(state) && filename) {\n if (state === \"complete\") {\n const job = await this.printJobService.markFinished(printerId, filename);\n // Update printer thumbnail from completed job\n if (job) {\n await this.printerThumbnailCache.handleJobCompleted(printerId, job.id);\n }\n } else {\n await this.printJobService.markFailed(printerId, filename, state);\n }\n }\n }\n }\n\n private async onPrusaLinkPollMessage(e: PrusaLinkEventDto) {\n const printerId = e.printerId;\n if (e.event === messages.current) {\n await this.setEvent(printerId, messages.current, e.payload);\n const payload = e.payload as any;\n const state = payload?.state;\n const filename = payload?.job?.file?.path ?? payload?.job?.file?.display;\n const completion = payload?.progress?.completion;\n if (state === \"Printing\" && filename) {\n const printerName = await this.getPrinterName(printerId);\n const job = await this.printJobService.markStarted(printerId, filename, printerName);\n\n if (job) {\n await this.printerThumbnailCache.handleJobStarted(printerId, job.id);\n }\n\n // Trigger file download and analysis if needed\n if (job && !job.fileStorageId && job.analysisState === \"NOT_ANALYZED\") {\n this.logger.log(`Job ${job.id} has no local file - triggering download and analysis`);\n await this.printJobService.triggerFileAnalysis(job.id);\n }\n }\n if (typeof completion === \"number\" && filename) {\n await this.printJobService.markProgress(printerId, filename, completion);\n }\n // PrusaLink states: \"Printing\", \"Finished\", \"Stopped\", \"Error\", \"Operational\", \"Ready\"\n if (state === \"Finished\" && filename) {\n const job = await this.printJobService.markFinished(printerId, filename);\n // Update printer thumbnail from completed job\n if (job) {\n await this.printerThumbnailCache.handleJobCompleted(printerId, job.id);\n }\n } else if ([\"Stopped\", \"Error\"].includes(state) && filename) {\n await this.printJobService.markFailed(printerId, filename, state);\n }\n }\n }\n\n private async onBambuSocketMessage(e: BambuEventDto) {\n const printerId = e.printerId;\n if (!printerId) {\n this.logger.warn(\"Received Bambu event without printerId\", e);\n return;\n }\n\n if (e.event === messages.current) {\n await this.setEvent(printerId, messages.current, e.payload);\n const payload = e.payload as any;\n const print = payload?.print;\n const percent = print?.mc_percent;\n const filename = print?.gcode_file || print?.subtask_name;\n const stage = print?.mc_print_stage;\n const state = print?.gcode_state;\n\n if (state === \"PRINTING\" && filename) {\n const printerName = await this.getPrinterName(printerId);\n const job = await this.printJobService.markStarted(printerId, filename, printerName);\n\n // Update printer thumbnail from this job\n if (job) {\n await this.printerThumbnailCache.handleJobStarted(printerId, job.id);\n }\n\n // Trigger file download and analysis if needed\n if (job && !job.fileStorageId && job.analysisState === \"NOT_ANALYZED\") {\n this.logger.log(`Job ${job.id} has no local file - triggering download and analysis`);\n await this.printJobService.triggerFileAnalysis(job.id);\n }\n\n // Update metadata from Bambu MQTT data if job was just created and has no metadata (as fallback)\n if (job && !job.metadata?.gcodePrintTimeSeconds) {\n const remainingMinutes = print?.mc_remaining_time;\n const totalLayers = print?.total_layer_num;\n // mc_remaining_time is in minutes, convert to seconds\n const estimatedSeconds = remainingMinutes ? remainingMinutes * 60 : null;\n\n // Try to get filament diameter from AMS tray data\n const tray = print?.ams?.tray_now ? print?.vt_tray : print?.ams?.ams?.[0]?.tray?.[0];\n const filamentDiameter = tray?.tray_diameter ? parseFloat(tray.tray_diameter) : null;\n\n await this.printJobService.updateJobMetadata(printerId, filename, {\n gcodePrintTimeSeconds: estimatedSeconds,\n nozzleDiameterMm: null, // Not available in MQTT\n filamentDiameterMm: filamentDiameter,\n filamentDensityGramsCm3: null,\n filamentUsedMm: null,\n filamentUsedCm3: null,\n filamentUsedGrams: null,\n totalFilamentUsedGrams: null,\n });\n }\n }\n if (typeof percent === \"number\" && filename) {\n await this.printJobService.markProgress(printerId, filename, percent);\n }\n if (state === \"FINISHED\" && filename) {\n const job = await this.printJobService.markFinished(printerId, filename);\n // Update printer thumbnail from completed job\n if (job) {\n await this.printerThumbnailCache.handleJobCompleted(printerId, job.id);\n }\n } else if (state === \"IDLE\") {\n // Bambu printers go to IDLE when print is cancelled/stopped (they don't send \"CANCELLED\" state)\n // Check if there's an active print job - if so, it was cancelled\n const activeJob = await this.printJobService.getActivePrintJob(printerId);\n if (activeJob && activeJob.status === \"PRINTING\") {\n this.logger.log(`Print job ${activeJob.id} transitioned to IDLE - marking as cancelled`);\n await this.printJobService.handlePrintCancelled(printerId, \"Print stopped\");\n }\n } else if (state === \"ERROR\" && filename) {\n await this.printJobService.markFailed(printerId, filename, \"Error\");\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"printer-events.cache.js","names":[],"sources":["../../src/state/printer-events.cache.ts"],"sourcesContent":["import { KeyDiffCache } from \"@/utils/cache/key-diff.cache\";\nimport { printerEvents, PrintersDeletedEvent } from \"@/constants/event.constants\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { WsMessage, messages, type OctoPrintEventDto } from \"@/services/octoprint/dto/octoprint-event.dto\";\nimport { MR_WsMessage, type MoonrakerEventDto } from \"@/services/moonraker/constants/moonraker-event.dto\";\nimport type { PrinterObjectsQueryDto } from \"@/services/moonraker/dto/objects/printer-objects-query.dto\";\nimport { SubscriptionType } from \"@/services/moonraker/moonraker-websocket.adapter\";\nimport { octoPrintEvent } from \"@/services/octoprint/octoprint-websocket.adapter\";\nimport { moonrakerEvent } from \"@/services/moonraker/constants/moonraker.constants\";\nimport { prusaLinkEvent } from \"@/services/prusa-link/constants/prusalink.constants\";\nimport type { PrusaLinkEventDto } from \"@/services/prusa-link/constants/prusalink-event.dto\";\nimport { bambuEvent, type BambuEventDto } from \"@/services/bambu/bambu-mqtt.adapter\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { PrintJobService } from \"@/services/orm/print-job.service\";\nimport { PrinterCache } from \"@/state/printer.cache\";\nimport { PrinterThumbnailCache } from \"@/state/printer-thumbnail.cache\";\nimport type { CurrentMessageDto } from \"@/services/octoprint/dto/websocket/current-message.dto\";\n\nexport type WsMessageWithoutEventAndPlugin = Exclude<WsMessage, \"event\" | \"plugin\">;\nexport type PrinterEventsCacheDto = Record<WsMessageWithoutEventAndPlugin, any>;\n\nexport class PrinterEventsCache extends KeyDiffCache<PrinterEventsCacheDto> {\n private readonly logger: LoggerService;\n\n constructor(\n private readonly eventEmitter2: EventEmitter2,\n loggerFactory: ILoggerFactory,\n private readonly printJobService: PrintJobService,\n private readonly printerCache: PrinterCache,\n private readonly printerThumbnailCache: PrinterThumbnailCache,\n ) {\n super();\n\n this.logger = loggerFactory(PrinterEventsCache.name);\n this.subscribeToEvents();\n }\n\n async deletePrinterSocketEvents(id: number) {\n await this.deleteKeyValue(id, true);\n }\n\n async getPrinterSocketEvents(id: number) {\n return this.keyValueStore.get(id);\n }\n\n async getOrCreateEvents(printerId: number) {\n let ref = await this.getValue(printerId);\n if (!ref) {\n ref = {\n connected: null,\n reauthRequired: null,\n // slicingProgress: null,\n notify_status_update: null,\n current: null,\n history: null,\n // timelapse: null,\n // event: {},\n // plugin: {},\n API_STATE_UPDATED: null,\n WS_CLOSED: null,\n WS_ERROR: null,\n WS_OPENED: null,\n WS_STATE_UPDATED: null,\n };\n await this.setKeyValue(printerId, ref);\n }\n return ref;\n }\n\n async setEvent(printerId: number, label: WsMessageWithoutEventAndPlugin, payload: any) {\n const ref = await this.getOrCreateEvents(printerId);\n ref[label] = {\n payload,\n receivedAt: Date.now(),\n };\n await this.setKeyValue(printerId, ref);\n }\n\n async setEventPartial(\n printerId: number,\n label: WsMessageWithoutEventAndPlugin,\n payload: any,\n keysToUpdate: string[],\n ) {\n const ref = await this.getOrCreateEvents(printerId);\n if (!ref[label]) {\n ref[label] = {\n payload: {},\n receivedAt: Date.now(),\n };\n }\n\n for (const key of keysToUpdate) {\n ref[label].payload[key] = payload[key];\n }\n\n ref[label].receivedAt = Date.now();\n await this.setKeyValue(printerId, ref);\n }\n\n private async handlePrintersDeleted(event: PrintersDeletedEvent) {\n await this.deleteKeysBatch(event.printerIds);\n }\n\n private subscribeToEvents() {\n this.eventEmitter2.on(octoPrintEvent(\"*\"), (e) => this.onOctoPrintSocketMessage(e));\n this.eventEmitter2.on(moonrakerEvent(\"*\"), (e) => this.onMoonrakerSocketMessage(e));\n this.eventEmitter2.on(prusaLinkEvent(\"*\"), (e) => this.onPrusaLinkPollMessage(e));\n this.eventEmitter2.on(bambuEvent(\"*\"), (e) => this.onBambuSocketMessage(e));\n this.eventEmitter2.on(printerEvents.printersDeleted, this.handlePrintersDeleted.bind(this));\n }\n\n private async getPrinterName(printerId: number): Promise<string | undefined> {\n try {\n const printer = await this.printerCache.getValue(printerId);\n return printer?.name;\n } catch (error) {\n this.logger.debug(`Could not get printer name for ${printerId}: ${error}`);\n return undefined;\n }\n }\n\n private async onOctoPrintSocketMessage(e: OctoPrintEventDto) {\n const printerId = e.printerId;\n if (e.event === messages.current && e.payload) {\n const payload = e.payload as CurrentMessageDto;\n\n const keysToUpdate = [\"state\", \"job\", \"progress\", \"currentZ\", \"offsets\", \"resends\"];\n\n if (Array.isArray(payload.temps) && payload.temps.length > 0) {\n keysToUpdate.push(\"temps\");\n }\n\n await this.setEventPartial(printerId, messages.current, payload, keysToUpdate);\n\n const flags = payload?.state?.flags;\n const filePath = payload?.job?.file?.path;\n const completion = payload?.progress?.completion;\n\n if (flags?.printing && filePath) {\n const printerName = await this.getPrinterName(printerId);\n const job = await this.printJobService.markStarted(printerId, filePath, printerName);\n\n if (job) {\n await this.printerThumbnailCache.handleJobStarted(printerId, job.id);\n }\n\n if (job && !job.fileStorageId && job.analysisState === \"NOT_ANALYZED\") {\n this.logger.log(`Job ${job.id} has no local file - triggering download and analysis`);\n await this.printJobService.triggerFileAnalysis(job.id);\n }\n\n // Update metadata from OctoPrint job data if available (as fallback)\n if (job && !job.metadata?.gcodePrintTimeSeconds) {\n const estimatedTime = payload?.job?.estimatedPrintTime;\n const filament = payload?.job?.filament?.tool0;\n\n await this.printJobService.updateJobMetadata(printerId, filePath, {\n gcodePrintTimeSeconds: estimatedTime ? Math.round(estimatedTime) : null,\n nozzleDiameterMm: null,\n filamentDiameterMm: null,\n filamentDensityGramsCm3: null,\n filamentUsedMm: filament?.length ? Math.round(filament.length) : null,\n filamentUsedCm3: filament?.volume ? Math.round(filament.volume * 100) / 100 : null,\n filamentUsedGrams: null,\n totalFilamentUsedGrams: null,\n });\n }\n }\n if (typeof completion === \"number\" && filePath) {\n await this.printJobService.markProgress(printerId, filePath, completion);\n }\n if ((flags?.finishing || flags?.error || flags?.cancelling) && filePath) {\n if (flags?.finishing) {\n const job = await this.printJobService.markFinished(printerId, filePath);\n if (job) {\n await this.printerThumbnailCache.handleJobCompleted(printerId, job.id);\n }\n } else if (flags?.cancelling) {\n await this.printJobService.markFailed(printerId, filePath, \"Cancelled\");\n } else {\n await this.printJobService.markFailed(printerId, filePath, \"Error\");\n }\n }\n }\n }\n\n private async onMoonrakerSocketMessage(\n e: MoonrakerEventDto<MR_WsMessage, PrinterObjectsQueryDto<SubscriptionType | null>>,\n ) {\n const printerId = e.printerId;\n const eventType = e.event;\n // https://github.com/mainsail-crew/mainsail/blob/fa61d4ef92 97426a404dd845a1a4d5e4525c43dc/src/components/panels/StatusPanel.vue#L199\n if ([messages.notify_status_update, messages.current].includes(eventType as any)) {\n await this.setEvent(printerId, eventType as \"notify_status_update\" | \"current\", e.payload);\n const payload = e.payload as any;\n const status = payload?.status ?? payload?.result ?? {};\n const filename = status?.print_stats?.filename;\n const progress = status?.display_status?.progress ?? status?.print_stats?.progress;\n const state = status?.print_stats?.state;\n if (state === \"printing\" && filename) {\n const printerName = await this.getPrinterName(printerId);\n const job = await this.printJobService.markStarted(printerId, filename, printerName);\n\n if (job) {\n await this.printerThumbnailCache.handleJobStarted(printerId, job.id);\n }\n\n if (job && !job.fileStorageId && job.analysisState === \"NOT_ANALYZED\") {\n this.logger.log(`Job ${job.id} has no local file - triggering download and analysis`);\n await this.printJobService.triggerFileAnalysis(job.id);\n }\n }\n if (typeof progress === \"number\" && filename) {\n await this.printJobService.markProgress(printerId, filename, Math.round(progress * 100));\n }\n if ([\"complete\", \"cancelled\", \"error\"].includes(state) && filename) {\n if (state === \"complete\") {\n const job = await this.printJobService.markFinished(printerId, filename);\n // Update printer thumbnail from completed job\n if (job) {\n await this.printerThumbnailCache.handleJobCompleted(printerId, job.id);\n }\n } else {\n await this.printJobService.markFailed(printerId, filename, state);\n }\n }\n }\n }\n\n private async onPrusaLinkPollMessage(e: PrusaLinkEventDto) {\n const printerId = e.printerId;\n if (e.event === messages.current) {\n await this.setEvent(printerId, messages.current, e.payload);\n const payload = e.payload as any;\n const state = payload?.state;\n const filename = payload?.job?.file?.path ?? payload?.job?.file?.display;\n const completion = payload?.progress?.completion;\n if (state === \"Printing\" && filename) {\n const printerName = await this.getPrinterName(printerId);\n const job = await this.printJobService.markStarted(printerId, filename, printerName);\n\n if (job) {\n await this.printerThumbnailCache.handleJobStarted(printerId, job.id);\n }\n\n // Trigger file download and analysis if needed\n if (job && !job.fileStorageId && job.analysisState === \"NOT_ANALYZED\") {\n this.logger.log(`Job ${job.id} has no local file - triggering download and analysis`);\n await this.printJobService.triggerFileAnalysis(job.id);\n }\n }\n if (typeof completion === \"number\" && filename) {\n await this.printJobService.markProgress(printerId, filename, completion);\n }\n // PrusaLink states: \"Printing\", \"Finished\", \"Stopped\", \"Error\", \"Operational\", \"Ready\"\n if (state === \"Finished\" && filename) {\n const job = await this.printJobService.markFinished(printerId, filename);\n // Update printer thumbnail from completed job\n if (job) {\n await this.printerThumbnailCache.handleJobCompleted(printerId, job.id);\n }\n } else if ([\"Stopped\", \"Error\"].includes(state) && filename) {\n await this.printJobService.markFailed(printerId, filename, state);\n }\n }\n }\n\n private async onBambuSocketMessage(e: BambuEventDto) {\n const printerId = e.printerId;\n if (!printerId) {\n this.logger.warn(\"Received Bambu event without printerId\", e);\n return;\n }\n\n if (e.event === messages.current) {\n await this.setEvent(printerId, messages.current, e.payload);\n const payload = e.payload as any;\n const print = payload?.print;\n const percent = print?.mc_percent;\n const filename = print?.gcode_file || print?.subtask_name;\n const stage = print?.mc_print_stage;\n const state = print?.gcode_state;\n\n if (state === \"PRINTING\" && filename) {\n const printerName = await this.getPrinterName(printerId);\n const job = await this.printJobService.markStarted(printerId, filename, printerName);\n\n // Update printer thumbnail from this job\n if (job) {\n await this.printerThumbnailCache.handleJobStarted(printerId, job.id);\n }\n\n // Trigger file download and analysis if needed\n if (job && !job.fileStorageId && job.analysisState === \"NOT_ANALYZED\") {\n this.logger.log(`Job ${job.id} has no local file - triggering download and analysis`);\n await this.printJobService.triggerFileAnalysis(job.id);\n }\n\n // Update metadata from Bambu MQTT data if job was just created and has no metadata (as fallback)\n if (job && !job.metadata?.gcodePrintTimeSeconds) {\n const remainingMinutes = print?.mc_remaining_time;\n const totalLayers = print?.total_layer_num;\n // mc_remaining_time is in minutes, convert to seconds\n const estimatedSeconds = remainingMinutes ? remainingMinutes * 60 : null;\n\n // Try to get filament diameter from AMS tray data\n const tray = print?.ams?.tray_now ? print?.vt_tray : print?.ams?.ams?.[0]?.tray?.[0];\n const filamentDiameter = tray?.tray_diameter ? parseFloat(tray.tray_diameter) : null;\n\n await this.printJobService.updateJobMetadata(printerId, filename, {\n gcodePrintTimeSeconds: estimatedSeconds,\n nozzleDiameterMm: null, // Not available in MQTT\n filamentDiameterMm: filamentDiameter,\n filamentDensityGramsCm3: null,\n filamentUsedMm: null,\n filamentUsedCm3: null,\n filamentUsedGrams: null,\n totalFilamentUsedGrams: null,\n });\n }\n }\n if (typeof percent === \"number\" && filename) {\n await this.printJobService.markProgress(printerId, filename, percent);\n }\n if (state === \"FINISHED\" && filename) {\n const job = await this.printJobService.markFinished(printerId, filename);\n // Update printer thumbnail from completed job\n if (job) {\n await this.printerThumbnailCache.handleJobCompleted(printerId, job.id);\n }\n } else if (state === \"IDLE\") {\n // Bambu printers go to IDLE when print is cancelled/stopped (they don't send \"CANCELLED\" state)\n // Check if there's an active print job - if so, it was cancelled\n const activeJob = await this.printJobService.getActivePrintJob(printerId);\n if (activeJob && activeJob.status === \"PRINTING\") {\n this.logger.log(`Print job ${activeJob.id} transitioned to IDLE - marking as cancelled`);\n await this.printJobService.handlePrintCancelled(printerId, \"Print stopped\");\n }\n } else if (state === \"ERROR\" && filename) {\n await this.printJobService.markFailed(printerId, filename, \"Error\");\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"printer-socket.store.js","names":[],"sources":["../../src/state/printer-socket.store.ts"],"sourcesContent":["import { captureException } from \"@sentry/node\";\nimport { errorSummary } from \"@/utils/error.utils\";\nimport {\n BatchPrinterCreatedEvent,\n PrinterCreatedEvent,\n printerEvents,\n PrintersDeletedEvent,\n PrinterUpdatedEvent,\n} from \"@/constants/event.constants\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { SocketFactory } from \"@/services/socket.factory\";\nimport { PrinterCache } from \"@/state/printer.cache\";\nimport { OctoprintWebsocketAdapter } from \"@/services/octoprint/octoprint-websocket.adapter\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { OctoprintType } from \"@/services/printer-api.interface\";\nimport type { IWebsocketAdapter } from \"@/services/websocket-adapter.interface\";\nimport type { PrinterDto } from \"@/services/interfaces/printer.dto\";\nimport { SocketState } from \"@/shared/dtos/socket-state.type\";\nimport { ApiState } from \"@/shared/dtos/api-state.type\";\n\nexport interface PrinterSocketState {\n printerId: number;\n printerType: number;\n socket: SocketState;\n api: ApiState;\n}\n\nexport class PrinterSocketStore {\n printerSocketAdaptersById = new Map<number, IWebsocketAdapter>();\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly socketFactory: SocketFactory,\n private readonly eventEmitter2: EventEmitter2,\n private readonly printerCache: PrinterCache,\n ) {\n this.logger = loggerFactory(PrinterSocketStore.name);\n\n this.subscribeToEvents();\n }\n\n getSocketStatesById() {\n const socketStatesById: { [k: number]: PrinterSocketState } = {};\n this.printerSocketAdaptersById.forEach((s) => {\n if (!s.printerId) {\n return;\n }\n\n socketStatesById[s.printerId] = {\n printerId: s.printerId,\n printerType: s.printerType,\n socket: s.socketState,\n api: s.apiState,\n };\n });\n return socketStatesById;\n }\n\n async loadPrinterSockets() {\n await this.printerCache.loadCache();\n\n const printerDtoList = await this.printerCache.listCachedPrinters(false);\n this.printerSocketAdaptersById.clear();\n for (const printerDto of printerDtoList) {\n try {\n this.createOrUpdateSocket(printerDto);\n } catch (e) {\n captureException(e);\n this.logger.error(\"PrinterSocketStore failed to construct new socket.\", errorSummary(e));\n }\n }\n\n this.logger.log(`Loaded ${this.printerSocketAdaptersById.size} printer sockets`);\n }\n\n listPrinterSockets() {\n return Array.from(this.printerSocketAdaptersById.values());\n }\n\n reconnectPrinterAdapter(id: number) {\n const socket = this.getPrinterSocket(id);\n if (!socket) return;\n\n socket.close();\n\n // The reconnect task will pick up this state and reconnect\n socket.resetSocketState();\n }\n\n getPrinterSocket(id: number): IWebsocketAdapter | undefined {\n return this.printerSocketAdaptersById.get(id);\n }\n\n /**\n * Sets up the new WebSocket connections for all printers\n */\n async reconnectPrinterSockets(): Promise<void> {\n let reauthRequested = 0;\n let socketSetupRequested = 0;\n const socketStates: { [k: string]: number } = {};\n const apiStates: { [k: string]: number } = {};\n const promisesReauth = [];\n for (const socket of this.printerSocketAdaptersById.values()) {\n try {\n if (socket.printerType === OctoprintType && (socket as OctoprintWebsocketAdapter).needsReauth()) {\n reauthRequested++;\n // TODO OP close socket\n const promise = socket.reauthSession().catch();\n // TODO MR reconnect\n promisesReauth.push(promise);\n }\n } catch (e) {\n captureException(e);\n }\n }\n\n await Promise.all(promisesReauth);\n\n const promisesOpenSocket: any[] = [];\n for (const socket of this.printerSocketAdaptersById.values()) {\n try {\n if (socket.needsSetup() || socket.needsReopen()) {\n socketSetupRequested++;\n const promise = socket\n .setupSocketSession()\n .then(() => {\n socket.open();\n })\n .catch();\n promisesOpenSocket.push(promise);\n }\n } catch (e) {\n captureException(e);\n }\n\n const keySocket = socket.socketState;\n const valSocket = socketStates[keySocket];\n socketStates[keySocket] = valSocket ? valSocket + 1 : 1;\n const keyApi = socket.apiState;\n const valApi = apiStates[keyApi];\n apiStates[keyApi] = valApi ? valApi + 1 : 1;\n }\n\n await Promise.all(promisesOpenSocket);\n }\n\n createOrUpdateSocket(printer: PrinterDto) {\n const { enabled, id } = printer;\n let foundAdapter = this.printerSocketAdaptersById.get(id);\n\n // Delete the socket if the printer is disabled\n if (!enabled) {\n this.logger.log(`Printer is disabled. Deleting socket`);\n this.deleteSocket(id);\n return;\n }\n\n // Create a new socket if it doesn't exist, or if printer type changed\n if (foundAdapter) {\n // Check if printer type changed - if so, we need a new adapter\n if (foundAdapter.printerType === printer.printerType) {\n foundAdapter.close();\n this.logger.log(`Closing printer socket for update`);\n } else {\n this.logger.log(\n `Printer type changed from ${foundAdapter.printerType} to ${printer.printerType}. Creating new socket adapter`,\n );\n foundAdapter.close();\n foundAdapter = this.socketFactory.createInstance(printer.printerType);\n this.printerSocketAdaptersById.set(id, foundAdapter);\n }\n } else {\n foundAdapter = this.socketFactory.createInstance(printer.printerType);\n this.printerSocketAdaptersById.set(id, foundAdapter);\n }\n\n // Reset the socket credentials before (re-)connecting\n foundAdapter.registerCredentials({\n printerId: printer.id,\n loginDto: {\n apiKey: printer.apiKey,\n username: printer.username,\n password: printer.password,\n printerURL: printer.printerURL,\n printerType: printer.printerType,\n },\n });\n foundAdapter.resetSocketState();\n }\n\n private handleBatchPrinterCreated(event: BatchPrinterCreatedEvent) {\n for (const printer of event.printers) {\n this.handlePrinterCreated({ printer });\n }\n }\n\n private handlePrinterCreated(event: PrinterCreatedEvent) {\n this.createOrUpdateSocket(event.printer);\n }\n\n private handlePrinterUpdated(event: PrinterUpdatedEvent) {\n this.logger.log(`Printer updated. Updating socket`);\n this.createOrUpdateSocket(event.printer);\n }\n\n private handlePrintersDeleted(event: PrintersDeletedEvent) {\n event.printerIds.forEach((id) => {\n this.deleteSocket(id);\n });\n }\n\n private subscribeToEvents() {\n this.eventEmitter2.on(printerEvents.printerCreated, this.handlePrinterCreated.bind(this));\n this.eventEmitter2.on(printerEvents.printersDeleted, this.handlePrintersDeleted.bind(this));\n this.eventEmitter2.on(printerEvents.printerUpdated, this.handlePrinterUpdated.bind(this));\n this.eventEmitter2.on(printerEvents.batchPrinterCreated, this.handleBatchPrinterCreated.bind(this));\n }\n\n private deleteSocket(printerId: number) {\n const socket = this.printerSocketAdaptersById.get(printerId);\n\n // Ensure that the printer does not re-register itself after being purged\n socket?.disallowEmittingEvents();\n\n socket?.close();\n\n // TODO mark diff cache\n this.printerSocketAdaptersById.delete(printerId);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"printer-socket.store.js","names":[],"sources":["../../src/state/printer-socket.store.ts"],"sourcesContent":["import { captureException } from \"@sentry/node\";\nimport { errorSummary } from \"@/utils/error.utils\";\nimport {\n BatchPrinterCreatedEvent,\n PrinterCreatedEvent,\n printerEvents,\n PrintersDeletedEvent,\n PrinterUpdatedEvent,\n} from \"@/constants/event.constants\";\nimport EventEmitter2 from \"eventemitter2\";\nimport { SocketFactory } from \"@/services/socket.factory\";\nimport { PrinterCache } from \"@/state/printer.cache\";\nimport { OctoprintWebsocketAdapter } from \"@/services/octoprint/octoprint-websocket.adapter\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { OctoprintType } from \"@/services/printer-api.interface\";\nimport type { IWebsocketAdapter } from \"@/services/websocket-adapter.interface\";\nimport type { PrinterDto } from \"@/services/interfaces/printer.dto\";\nimport { SocketState } from \"@/shared/dtos/socket-state.type\";\nimport { ApiState } from \"@/shared/dtos/api-state.type\";\n\nexport interface PrinterSocketState {\n printerId: number;\n printerType: number;\n socket: SocketState;\n api: ApiState;\n}\n\nexport class PrinterSocketStore {\n printerSocketAdaptersById = new Map<number, IWebsocketAdapter>();\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly socketFactory: SocketFactory,\n private readonly eventEmitter2: EventEmitter2,\n private readonly printerCache: PrinterCache,\n ) {\n this.logger = loggerFactory(PrinterSocketStore.name);\n\n this.subscribeToEvents();\n }\n\n getSocketStatesById() {\n const socketStatesById: { [k: number]: PrinterSocketState } = {};\n this.printerSocketAdaptersById.forEach((s) => {\n if (!s.printerId) {\n return;\n }\n\n socketStatesById[s.printerId] = {\n printerId: s.printerId,\n printerType: s.printerType,\n socket: s.socketState,\n api: s.apiState,\n };\n });\n return socketStatesById;\n }\n\n async loadPrinterSockets() {\n await this.printerCache.loadCache();\n\n const printerDtoList = await this.printerCache.listCachedPrinters(false);\n this.printerSocketAdaptersById.clear();\n for (const printerDto of printerDtoList) {\n try {\n this.createOrUpdateSocket(printerDto);\n } catch (e) {\n captureException(e);\n this.logger.error(\"PrinterSocketStore failed to construct new socket.\", errorSummary(e));\n }\n }\n\n this.logger.log(`Loaded ${this.printerSocketAdaptersById.size} printer sockets`);\n }\n\n listPrinterSockets() {\n return Array.from(this.printerSocketAdaptersById.values());\n }\n\n reconnectPrinterAdapter(id: number) {\n const socket = this.getPrinterSocket(id);\n if (!socket) return;\n\n socket.close();\n\n // The reconnect task will pick up this state and reconnect\n socket.resetSocketState();\n }\n\n getPrinterSocket(id: number): IWebsocketAdapter | undefined {\n return this.printerSocketAdaptersById.get(id);\n }\n\n /**\n * Sets up the new WebSocket connections for all printers\n */\n async reconnectPrinterSockets(): Promise<void> {\n let reauthRequested = 0;\n let socketSetupRequested = 0;\n const socketStates: { [k: string]: number } = {};\n const apiStates: { [k: string]: number } = {};\n const promisesReauth = [];\n for (const socket of this.printerSocketAdaptersById.values()) {\n try {\n if (socket.printerType === OctoprintType && (socket as OctoprintWebsocketAdapter).needsReauth()) {\n reauthRequested++;\n // TODO OP close socket\n const promise = socket.reauthSession().catch();\n // TODO MR reconnect\n promisesReauth.push(promise);\n }\n } catch (e) {\n captureException(e);\n }\n }\n\n await Promise.all(promisesReauth);\n\n const promisesOpenSocket: any[] = [];\n for (const socket of this.printerSocketAdaptersById.values()) {\n try {\n if (socket.needsSetup() || socket.needsReopen()) {\n socketSetupRequested++;\n const promise = socket\n .setupSocketSession()\n .then(() => {\n socket.open();\n })\n .catch();\n promisesOpenSocket.push(promise);\n }\n } catch (e) {\n captureException(e);\n }\n\n const keySocket = socket.socketState;\n const valSocket = socketStates[keySocket];\n socketStates[keySocket] = valSocket ? valSocket + 1 : 1;\n const keyApi = socket.apiState;\n const valApi = apiStates[keyApi];\n apiStates[keyApi] = valApi ? valApi + 1 : 1;\n }\n\n await Promise.all(promisesOpenSocket);\n }\n\n createOrUpdateSocket(printer: PrinterDto) {\n const { enabled, id } = printer;\n let foundAdapter = this.printerSocketAdaptersById.get(id);\n\n // Delete the socket if the printer is disabled\n if (!enabled) {\n this.logger.log(`Printer is disabled. Deleting socket`);\n this.deleteSocket(id);\n return;\n }\n\n // Create a new socket if it doesn't exist, or if printer type changed\n if (foundAdapter) {\n // Check if printer type changed - if so, we need a new adapter\n if (foundAdapter.printerType === printer.printerType) {\n foundAdapter.close();\n this.logger.log(`Closing printer socket for update`);\n } else {\n this.logger.log(\n `Printer type changed from ${foundAdapter.printerType} to ${printer.printerType}. Creating new socket adapter`,\n );\n foundAdapter.close();\n foundAdapter = this.socketFactory.createInstance(printer.printerType);\n this.printerSocketAdaptersById.set(id, foundAdapter);\n }\n } else {\n foundAdapter = this.socketFactory.createInstance(printer.printerType);\n this.printerSocketAdaptersById.set(id, foundAdapter);\n }\n\n // Reset the socket credentials before (re-)connecting\n foundAdapter.registerCredentials({\n printerId: printer.id,\n loginDto: {\n apiKey: printer.apiKey,\n username: printer.username,\n password: printer.password,\n printerURL: printer.printerURL,\n printerType: printer.printerType,\n },\n });\n foundAdapter.resetSocketState();\n }\n\n private handleBatchPrinterCreated(event: BatchPrinterCreatedEvent) {\n for (const printer of event.printers) {\n this.handlePrinterCreated({ printer });\n }\n }\n\n private handlePrinterCreated(event: PrinterCreatedEvent) {\n this.createOrUpdateSocket(event.printer);\n }\n\n private handlePrinterUpdated(event: PrinterUpdatedEvent) {\n this.logger.log(`Printer updated. Updating socket`);\n this.createOrUpdateSocket(event.printer);\n }\n\n private handlePrintersDeleted(event: PrintersDeletedEvent) {\n event.printerIds.forEach((id) => {\n this.deleteSocket(id);\n });\n }\n\n private subscribeToEvents() {\n this.eventEmitter2.on(printerEvents.printerCreated, this.handlePrinterCreated.bind(this));\n this.eventEmitter2.on(printerEvents.printersDeleted, this.handlePrintersDeleted.bind(this));\n this.eventEmitter2.on(printerEvents.printerUpdated, this.handlePrinterUpdated.bind(this));\n this.eventEmitter2.on(printerEvents.batchPrinterCreated, this.handleBatchPrinterCreated.bind(this));\n }\n\n private deleteSocket(printerId: number) {\n const socket = this.printerSocketAdaptersById.get(printerId);\n\n // Ensure that the printer does not re-register itself after being purged\n socket?.disallowEmittingEvents();\n\n socket?.close();\n\n // TODO mark diff cache\n this.printerSocketAdaptersById.delete(printerId);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"printer-thumbnail.cache.js","names":[],"sources":["../../src/state/printer-thumbnail.cache.ts"],"sourcesContent":["import { KeyDiffCache } from \"@/utils/cache/key-diff.cache\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { PrintJobService } from \"@/services/orm/print-job.service\";\nimport { FileStorageService } from \"@/services/file-storage.service\";\n\nexport interface CachedPrinterThumbnail {\n printerId: number;\n thumbnailBase64: string;\n jobId: number;\n fileName: string;\n updatedAt: Date;\n}\n\n/**\n * Cache for printer thumbnails using analyzed print job files\n * Automatically uses the most recent completed or active print job thumbnail\n */\nexport class PrinterThumbnailCache extends KeyDiffCache<CachedPrinterThumbnail> {\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly printJobService: PrintJobService,\n private readonly fileStorageService: FileStorageService,\n ) {\n super();\n this.logger = loggerFactory(PrinterThumbnailCache.name);\n }\n\n async loadCache() {\n this.logger.log(\"Loading printer thumbnail cache from print jobs...\");\n try {\n // Get all printers with recent jobs that have thumbnails\n const recentJobs = await this.printJobService.printJobRepository\n .createQueryBuilder(\"job\")\n .select(\"job.printerId\")\n .addSelect(\"MAX(job.endedAt)\", \"maxEndedAt\")\n .where(\"job.fileStorageId IS NOT NULL\")\n .andWhere(\"job.analysisState = :state\", { state: \"ANALYZED\" })\n .groupBy(\"job.printerId\")\n .getRawMany();\n\n let loadedCount = 0;\n for (const result of recentJobs) {\n const printerId = result.job_printerId;\n if (printerId) {\n try {\n await this.loadPrinterThumbnail(printerId);\n loadedCount++;\n } catch (error) {\n this.logger.warn(`Failed to load thumbnail for printer ${printerId}: ${error}`);\n }\n }\n }\n\n this.logger.log(`Loaded ${loadedCount} printer thumbnails from print jobs`);\n } catch (error) {\n this.logger.error(\"Failed to load thumbnail cache\", error);\n }\n }\n\n /**\n * Load thumbnail for a specific printer from its most recent print job\n */\n async loadPrinterThumbnail(printerId: number): Promise<void> {\n try {\n // Find most recent job with thumbnails for this printer\n const job = await this.printJobService.printJobRepository.findOne({\n where: {\n printerId,\n analysisState: \"ANALYZED\",\n },\n order: {\n endedAt: \"DESC\",\n startedAt: \"DESC\",\n createdAt: \"DESC\",\n },\n });\n\n if (!job || !job.fileStorageId) {\n this.logger.debug(`No suitable job found for printer ${printerId}`);\n return;\n }\n\n // Load metadata to check for thumbnails\n const metadata = await this.fileStorageService.loadMetadata(job.fileStorageId);\n const thumbnails = metadata?._thumbnails || [];\n\n if (thumbnails.length === 0) {\n this.logger.debug(`No thumbnails found for job ${job.id}`);\n return;\n }\n\n // Get the first (typically largest) thumbnail\n const thumbnailBuffer = await this.fileStorageService.getThumbnail(job.fileStorageId, 0);\n\n if (!thumbnailBuffer) {\n this.logger.warn(`Thumbnail file not found for job ${job.id}`);\n return;\n }\n\n const thumbnailBase64 = thumbnailBuffer.toString(\"base64\");\n\n await this.setPrinterThumbnail(printerId, thumbnailBase64, job.id, job.fileName);\n\n this.logger.debug(`Loaded thumbnail for printer ${printerId} from job ${job.id}`);\n } catch (error) {\n this.logger.error(`Failed to load thumbnail for printer ${printerId}: ${error}`);\n throw error;\n }\n }\n\n /**\n * Set/update printer thumbnail in cache\n */\n async setPrinterThumbnail(\n printerId: number,\n thumbnailBase64: string,\n jobId: number,\n fileName: string,\n ): Promise<void> {\n await this.setKeyValue(printerId, {\n printerId,\n thumbnailBase64,\n jobId,\n fileName,\n updatedAt: new Date(),\n });\n }\n\n /**\n * Remove printer thumbnail from cache\n */\n async unsetPrinterThumbnail(printerId: number): Promise<void> {\n await this.deleteKeyValue(printerId);\n }\n\n /**\n * Reset entire cache\n */\n async resetCache(): Promise<void> {\n this.logger.log(\"Resetting printer thumbnail cache\");\n this.keyValueStore.clear();\n this.resetDiffs();\n }\n\n /**\n * Update thumbnail after print job completes\n * Called by event handlers when a job finishes\n */\n async handleJobCompleted(printerId: number, jobId: number): Promise<void> {\n try {\n this.logger.log(`Updating thumbnail for printer ${printerId} after job ${jobId} completed`);\n await this.loadPrinterThumbnail(printerId);\n } catch (error) {\n this.logger.error(`Failed to update thumbnail after job completion: ${error}`);\n }\n }\n\n /**\n * Update thumbnail after print job starts (for active print preview)\n * Called by event handlers when a job starts\n */\n async handleJobStarted(printerId: number, jobId: number): Promise<void> {\n try {\n const job = await this.printJobService.printJobRepository.findOne({\n where: { id: jobId },\n });\n\n if (!job || !job.fileStorageId) {\n return;\n }\n\n // Load metadata to check for thumbnails\n const metadata = await this.fileStorageService.loadMetadata(job.fileStorageId);\n const thumbnails = metadata?._thumbnails || [];\n\n if (thumbnails.length === 0) {\n return;\n }\n\n // Get the first thumbnail\n const thumbnailBuffer = await this.fileStorageService.getThumbnail(job.fileStorageId, 0);\n\n if (!thumbnailBuffer) {\n return;\n }\n\n const thumbnailBase64 = thumbnailBuffer.toString(\"base64\");\n await this.setPrinterThumbnail(printerId, thumbnailBase64, job.id, job.fileName);\n\n this.logger.debug(`Updated thumbnail for printer ${printerId} from started job ${job.id}`);\n } catch (error) {\n this.logger.error(`Failed to update thumbnail after job start: ${error}`);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"printer-thumbnail.cache.js","names":[],"sources":["../../src/state/printer-thumbnail.cache.ts"],"sourcesContent":["import { KeyDiffCache } from \"@/utils/cache/key-diff.cache\";\nimport type { ILoggerFactory } from \"@/handlers/logger-factory\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { PrintJobService } from \"@/services/orm/print-job.service\";\nimport { FileStorageService } from \"@/services/file-storage.service\";\n\nexport interface CachedPrinterThumbnail {\n printerId: number;\n thumbnailBase64: string;\n jobId: number;\n fileName: string;\n updatedAt: Date;\n}\n\n/**\n * Cache for printer thumbnails using analyzed print job files\n * Automatically uses the most recent completed or active print job thumbnail\n */\nexport class PrinterThumbnailCache extends KeyDiffCache<CachedPrinterThumbnail> {\n private readonly logger: LoggerService;\n\n constructor(\n loggerFactory: ILoggerFactory,\n private readonly printJobService: PrintJobService,\n private readonly fileStorageService: FileStorageService,\n ) {\n super();\n this.logger = loggerFactory(PrinterThumbnailCache.name);\n }\n\n async loadCache() {\n this.logger.log(\"Loading printer thumbnail cache from print jobs...\");\n try {\n // Get all printers with recent jobs that have thumbnails\n const recentJobs = await this.printJobService.printJobRepository\n .createQueryBuilder(\"job\")\n .select(\"job.printerId\")\n .addSelect(\"MAX(job.endedAt)\", \"maxEndedAt\")\n .where(\"job.fileStorageId IS NOT NULL\")\n .andWhere(\"job.analysisState = :state\", { state: \"ANALYZED\" })\n .groupBy(\"job.printerId\")\n .getRawMany();\n\n let loadedCount = 0;\n for (const result of recentJobs) {\n const printerId = result.job_printerId;\n if (printerId) {\n try {\n await this.loadPrinterThumbnail(printerId);\n loadedCount++;\n } catch (error) {\n this.logger.warn(`Failed to load thumbnail for printer ${printerId}: ${error}`);\n }\n }\n }\n\n this.logger.log(`Loaded ${loadedCount} printer thumbnails from print jobs`);\n } catch (error) {\n this.logger.error(\"Failed to load thumbnail cache\", error);\n }\n }\n\n /**\n * Load thumbnail for a specific printer from its most recent print job\n */\n async loadPrinterThumbnail(printerId: number): Promise<void> {\n try {\n // Find most recent job with thumbnails for this printer\n const job = await this.printJobService.printJobRepository.findOne({\n where: {\n printerId,\n analysisState: \"ANALYZED\",\n },\n order: {\n endedAt: \"DESC\",\n startedAt: \"DESC\",\n createdAt: \"DESC\",\n },\n });\n\n if (!job || !job.fileStorageId) {\n this.logger.debug(`No suitable job found for printer ${printerId}`);\n return;\n }\n\n // Load metadata to check for thumbnails\n const metadata = await this.fileStorageService.loadMetadata(job.fileStorageId);\n const thumbnails = metadata?._thumbnails || [];\n\n if (thumbnails.length === 0) {\n this.logger.debug(`No thumbnails found for job ${job.id}`);\n return;\n }\n\n // Get the first (typically largest) thumbnail\n const thumbnailBuffer = await this.fileStorageService.getThumbnail(job.fileStorageId, 0);\n\n if (!thumbnailBuffer) {\n this.logger.warn(`Thumbnail file not found for job ${job.id}`);\n return;\n }\n\n const thumbnailBase64 = thumbnailBuffer.toString(\"base64\");\n\n await this.setPrinterThumbnail(printerId, thumbnailBase64, job.id, job.fileName);\n\n this.logger.debug(`Loaded thumbnail for printer ${printerId} from job ${job.id}`);\n } catch (error) {\n this.logger.error(`Failed to load thumbnail for printer ${printerId}: ${error}`);\n throw error;\n }\n }\n\n /**\n * Set/update printer thumbnail in cache\n */\n async setPrinterThumbnail(\n printerId: number,\n thumbnailBase64: string,\n jobId: number,\n fileName: string,\n ): Promise<void> {\n await this.setKeyValue(printerId, {\n printerId,\n thumbnailBase64,\n jobId,\n fileName,\n updatedAt: new Date(),\n });\n }\n\n /**\n * Remove printer thumbnail from cache\n */\n async unsetPrinterThumbnail(printerId: number): Promise<void> {\n await this.deleteKeyValue(printerId);\n }\n\n /**\n * Reset entire cache\n */\n async resetCache(): Promise<void> {\n this.logger.log(\"Resetting printer thumbnail cache\");\n this.keyValueStore.clear();\n this.resetDiffs();\n }\n\n /**\n * Update thumbnail after print job completes\n * Called by event handlers when a job finishes\n */\n async handleJobCompleted(printerId: number, jobId: number): Promise<void> {\n try {\n this.logger.log(`Updating thumbnail for printer ${printerId} after job ${jobId} completed`);\n await this.loadPrinterThumbnail(printerId);\n } catch (error) {\n this.logger.error(`Failed to update thumbnail after job completion: ${error}`);\n }\n }\n\n /**\n * Update thumbnail after print job starts (for active print preview)\n * Called by event handlers when a job starts\n */\n async handleJobStarted(printerId: number, jobId: number): Promise<void> {\n try {\n const job = await this.printJobService.printJobRepository.findOne({\n where: { id: jobId },\n });\n\n if (!job || !job.fileStorageId) {\n return;\n }\n\n // Load metadata to check for thumbnails\n const metadata = await this.fileStorageService.loadMetadata(job.fileStorageId);\n const thumbnails = metadata?._thumbnails || [];\n\n if (thumbnails.length === 0) {\n return;\n }\n\n // Get the first thumbnail\n const thumbnailBuffer = await this.fileStorageService.getThumbnail(job.fileStorageId, 0);\n\n if (!thumbnailBuffer) {\n return;\n }\n\n const thumbnailBase64 = thumbnailBuffer.toString(\"base64\");\n await this.setPrinterThumbnail(printerId, thumbnailBase64, job.id, job.fileName);\n\n this.logger.debug(`Updated thumbnail for printer ${printerId} from started job ${job.id}`);\n } catch (error) {\n this.logger.error(`Failed to update thumbnail after job start: ${error}`);\n }\n }\n}\n"],"mappings":"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"}