tiny-essentials 1.28.19 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (555) hide show
  1. package/README.md +1 -1
  2. package/changelog/1/28/19.md +24 -0
  3. package/changelog/1/29/0.md +58 -0
  4. package/dist/v1/ColorSafeStringify.min.js +1 -1
  5. package/dist/v1/TinyAdvancedRaffle.min.js +1 -1
  6. package/dist/v1/TinyAfterScrollWatcher.min.js +1 -1
  7. package/dist/v1/TinyAnalogClock.min.js +1 -1
  8. package/dist/v1/TinyArrayComparator.min.js +1 -1
  9. package/dist/v1/TinyArrayPaginator.min.js +1 -1
  10. package/dist/v1/TinyBasicsEs.min.js +1 -1
  11. package/dist/v1/TinyBrowserMonitor.min.js +1 -0
  12. package/dist/v1/TinyClassManager.min.js +1 -1
  13. package/dist/v1/TinyClipboard.min.js +1 -1
  14. package/dist/v1/TinyCloner.min.js +1 -0
  15. package/dist/v1/TinyColorConverter.min.js +1 -1
  16. package/dist/v1/TinyColorValidator.min.js +1 -1
  17. package/dist/v1/TinyCookieConsent.min.js +1 -1
  18. package/dist/v1/TinyDayNightCycle.min.js +1 -1
  19. package/dist/v1/TinyDebugger.min.js +1 -1
  20. package/dist/v1/TinyDomReadyManager.min.js +1 -1
  21. package/dist/v1/TinyDragDropDetector.min.js +1 -1
  22. package/dist/v1/TinyDragger.min.js +1 -1
  23. package/dist/v1/TinyElementObserver.min.js +1 -1
  24. package/dist/v1/TinyEssentials.min.js +1 -1
  25. package/dist/v1/TinyGamepad.min.js +1 -1
  26. package/dist/v1/TinyHtml.min.js +1 -1
  27. package/dist/v1/TinyHtmlTagRegexBuilder.min.js +1 -0
  28. package/dist/v1/TinyHttpResponseRegistry.min.js +1 -0
  29. package/dist/v1/TinyI18.min.js +1 -1
  30. package/dist/v1/TinyIframeEvents.min.js +1 -1
  31. package/dist/v1/TinyInventory.min.js +1 -1
  32. package/dist/v1/TinyInventoryTrader.min.js +1 -1
  33. package/dist/v1/TinyLevelUp.min.js +1 -1
  34. package/dist/v1/TinyLoadingScreen.min.js +1 -1
  35. package/dist/v1/TinyLocalStorage.min.js +1 -1
  36. package/dist/v1/TinyMaInSys.min.js +1 -1
  37. package/dist/v1/TinyMapCache.min.js +1 -0
  38. package/dist/v1/TinyMediaPlayer.min.js +1 -1
  39. package/dist/v1/TinyNeedBar.min.js +1 -1
  40. package/dist/v1/TinyNewWinEvents.min.js +1 -1
  41. package/dist/v1/TinyNotificationAdapter.min.js +1 -0
  42. package/dist/v1/TinyNotifications.min.js +1 -1
  43. package/dist/v1/TinyNotifyCenter.min.js +1 -1
  44. package/dist/v1/TinyOPFSFileSystem.min.js +1 -0
  45. package/dist/v1/TinyPasswordValidator.min.js +1 -0
  46. package/dist/v1/TinyPluginPackage.min.js +1 -0
  47. package/dist/v1/TinyPromiseQueue.min.js +1 -1
  48. package/dist/v1/TinyPushManager.min.js +1 -0
  49. package/dist/v1/TinyPushPayload.min.js +1 -0
  50. package/dist/v1/TinyPushRouter.min.js +1 -0
  51. package/dist/v1/TinyRadioFm.min.js +1 -1
  52. package/dist/v1/TinyRateLimiter.min.js +1 -1
  53. package/dist/v1/TinyRegexEs.min.js +1 -0
  54. package/dist/v1/TinyRouter.min.js +1 -0
  55. package/dist/v1/TinyServiceWorker.min.js +1 -0
  56. package/dist/v1/TinyServiceWorkerEngine.min.js +1 -0
  57. package/dist/v1/TinySetMapPackage.min.js +1 -0
  58. package/dist/v1/TinySimpleDice.min.js +1 -1
  59. package/dist/v1/TinySmartScroller.min.js +1 -1
  60. package/dist/v1/TinyStorageBucketFileSystem.min.js +1 -0
  61. package/dist/v1/TinyTextDiffer.min.js +1 -1
  62. package/dist/v1/TinyTextRangeEditor.min.js +1 -1
  63. package/dist/v1/TinyTextarea.min.js +1 -1
  64. package/dist/v1/TinyThrottledApi.min.js +1 -0
  65. package/dist/v1/TinyTimeout.min.js +1 -1
  66. package/dist/v1/TinyToastNotify.min.js +1 -1
  67. package/dist/v1/TinyUploadClicker.min.js +1 -1
  68. package/dist/v1/TinyUriParser.min.js +1 -0
  69. package/dist/v1/TinyVersion.min.js +1 -0
  70. package/dist/v1/TinyWebWorker.min.js +1 -0
  71. package/dist/v1/TinyWebWorkerEngine.min.js +1 -0
  72. package/dist/v1/UltraRandomMsgGen.min.js +1 -1
  73. package/dist/v1/basics/array.cjs +2 -2
  74. package/dist/v1/basics/array.d.mts +2 -0
  75. package/dist/v1/basics/array.d.mts.map +1 -1
  76. package/dist/v1/basics/array.mjs +2 -1
  77. package/dist/v1/basics/browserDetector.cjs +37 -12
  78. package/dist/v1/basics/browserDetector.d.mts +8 -0
  79. package/dist/v1/basics/browserDetector.d.mts.map +1 -1
  80. package/dist/v1/basics/browserDetector.mjs +32 -13
  81. package/dist/v1/basics/crypto.cjs +23 -0
  82. package/dist/v1/basics/crypto.d.mts +10 -0
  83. package/dist/v1/basics/crypto.d.mts.map +1 -0
  84. package/dist/v1/basics/crypto.mjs +18 -0
  85. package/dist/v1/basics/html.cjs +0 -3
  86. package/dist/v1/basics/html.d.mts.map +1 -1
  87. package/dist/v1/basics/html.mjs +0 -2
  88. package/dist/v1/basics/index.cjs +8 -1
  89. package/dist/v1/basics/index.d.mts +6 -5
  90. package/dist/v1/basics/index.d.mts.map +1 -1
  91. package/dist/v1/basics/index.mjs +6 -5
  92. package/dist/v1/basics/objChecker.cjs +88 -0
  93. package/dist/v1/basics/objChecker.d.mts +26 -0
  94. package/dist/v1/basics/objChecker.d.mts.map +1 -1
  95. package/dist/v1/basics/objChecker.mjs +74 -0
  96. package/dist/v1/basics/objFilter.cjs +234 -93
  97. package/dist/v1/basics/objFilter.d.mts +96 -22
  98. package/dist/v1/basics/objFilter.d.mts.map +1 -1
  99. package/dist/v1/basics/objFilter.mjs +216 -92
  100. package/dist/v1/basics/promiseUtils.cjs +48 -12
  101. package/dist/v1/basics/promiseUtils.d.mts +45 -12
  102. package/dist/v1/basics/promiseUtils.d.mts.map +1 -1
  103. package/dist/v1/basics/promiseUtils.mjs +47 -12
  104. package/dist/v1/basics/text.cjs +0 -12
  105. package/dist/v1/basics/text.d.mts +0 -10
  106. package/dist/v1/basics/text.d.mts.map +1 -1
  107. package/dist/v1/basics/text.mjs +0 -10
  108. package/dist/v1/index.cjs +88 -3
  109. package/dist/v1/index.d.mts +31 -6
  110. package/dist/v1/index.d.mts.map +1 -1
  111. package/dist/v1/index.mjs +32 -7
  112. package/dist/v1/jsdoc/EventEmitter.cjs +4 -0
  113. package/dist/v1/jsdoc/EventEmitter.d.mts +33 -0
  114. package/dist/v1/jsdoc/EventEmitter.d.mts.map +1 -0
  115. package/dist/v1/jsdoc/EventEmitter.mjs +18 -0
  116. package/dist/v1/libs/array/TinyArrayComparator.cjs +240 -25
  117. package/dist/v1/libs/array/TinyArrayComparator.d.mts +130 -22
  118. package/dist/v1/libs/array/TinyArrayComparator.d.mts.map +1 -1
  119. package/dist/v1/libs/array/TinyArrayComparator.mjs +210 -24
  120. package/dist/v1/libs/fs/constants.cjs +74 -0
  121. package/dist/v1/libs/fs/constants.d.mts +22 -0
  122. package/dist/v1/libs/fs/constants.d.mts.map +1 -0
  123. package/dist/v1/libs/fs/constants.mjs +53 -0
  124. package/dist/v1/libs/fs/encoding.cjs +118 -0
  125. package/dist/v1/libs/fs/encoding.d.mts +50 -0
  126. package/dist/v1/libs/fs/encoding.d.mts.map +1 -0
  127. package/dist/v1/libs/fs/encoding.mjs +102 -0
  128. package/dist/v1/libs/fs/error.cjs +188 -0
  129. package/dist/v1/libs/fs/error.d.mts +105 -0
  130. package/dist/v1/libs/fs/error.d.mts.map +1 -0
  131. package/dist/v1/libs/fs/error.mjs +158 -0
  132. package/dist/v1/libs/fs/handle.cjs +121 -0
  133. package/dist/v1/libs/fs/handle.d.mts +56 -0
  134. package/dist/v1/libs/fs/handle.d.mts.map +1 -0
  135. package/dist/v1/libs/fs/handle.mjs +100 -0
  136. package/dist/v1/libs/fs/index.cjs +700 -0
  137. package/dist/v1/libs/fs/index.d.mts +377 -0
  138. package/dist/v1/libs/fs/index.d.mts.map +1 -0
  139. package/dist/v1/libs/fs/index.mjs +630 -0
  140. package/dist/v1/libs/fs/path.cjs +234 -0
  141. package/dist/v1/libs/fs/path.d.mts +176 -0
  142. package/dist/v1/libs/fs/path.d.mts.map +1 -0
  143. package/dist/v1/libs/fs/path.mjs +197 -0
  144. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.cjs +202 -0
  145. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.d.mts +121 -0
  146. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.d.mts.map +1 -0
  147. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.mjs +172 -0
  148. package/dist/v1/libs/fs/plugins/OPFS/index.cjs +334 -0
  149. package/dist/v1/libs/fs/plugins/OPFS/index.d.mts +98 -0
  150. package/dist/v1/libs/fs/plugins/OPFS/index.d.mts.map +1 -0
  151. package/dist/v1/libs/fs/plugins/OPFS/index.mjs +300 -0
  152. package/dist/v1/libs/fs/stats.cjs +245 -0
  153. package/dist/v1/libs/fs/stats.d.mts +226 -0
  154. package/dist/v1/libs/fs/stats.d.mts.map +1 -0
  155. package/dist/v1/libs/fs/stats.mjs +197 -0
  156. package/dist/v1/libs/html/events/TinyIframeEvents.cjs +10 -1
  157. package/dist/v1/libs/html/events/TinyIframeEvents.d.mts +9 -2
  158. package/dist/v1/libs/html/events/TinyIframeEvents.d.mts.map +1 -1
  159. package/dist/v1/libs/html/events/TinyIframeEvents.mjs +8 -1
  160. package/dist/v1/libs/html/events/TinyNewWinEvents.cjs +14 -3
  161. package/dist/v1/libs/html/events/TinyNewWinEvents.d.mts +21 -5
  162. package/dist/v1/libs/html/events/TinyNewWinEvents.d.mts.map +1 -1
  163. package/dist/v1/libs/html/events/TinyNewWinEvents.mjs +12 -3
  164. package/dist/v1/libs/html/scroll/TinySmartScroller.cjs +13 -1
  165. package/dist/v1/libs/html/scroll/TinySmartScroller.d.mts +29 -2
  166. package/dist/v1/libs/html/scroll/TinySmartScroller.d.mts.map +1 -1
  167. package/dist/v1/libs/html/scroll/TinySmartScroller.mjs +11 -1
  168. package/dist/v1/libs/math/TinyAdvancedRaffle.cjs +13 -1
  169. package/dist/v1/libs/math/TinyAdvancedRaffle.d.mts +32 -2
  170. package/dist/v1/libs/math/TinyAdvancedRaffle.d.mts.map +1 -1
  171. package/dist/v1/libs/math/TinyAdvancedRaffle.mjs +11 -1
  172. package/dist/v1/libs/math/TinyTimeout.cjs +0 -14
  173. package/dist/v1/libs/math/TinyTimeout.d.mts +0 -11
  174. package/dist/v1/libs/math/TinyTimeout.d.mts.map +1 -1
  175. package/dist/v1/libs/math/TinyTimeout.mjs +0 -13
  176. package/dist/v1/libs/media/TinyMediaPlayer/index.cjs +25 -1
  177. package/dist/v1/libs/media/TinyMediaPlayer/index.d.mts +35 -2
  178. package/dist/v1/libs/media/TinyMediaPlayer/index.d.mts.map +1 -1
  179. package/dist/v1/libs/media/TinyMediaPlayer/index.mjs +23 -1
  180. package/dist/v1/libs/media/TinyMediaPlayer.cjs +2 -2
  181. package/dist/v1/libs/media/TinyMediaPlayer.d.mts +2 -2
  182. package/dist/v1/libs/media/TinyMediaPlayer.d.mts.map +1 -1
  183. package/dist/v1/libs/media/TinyMediaPlayer.mjs +2 -2
  184. package/dist/v1/libs/media/TinyRadioFm.cjs +19 -1
  185. package/dist/v1/libs/media/TinyRadioFm.d.mts +78 -2
  186. package/dist/v1/libs/media/TinyRadioFm.d.mts.map +1 -1
  187. package/dist/v1/libs/media/TinyRadioFm.mjs +17 -1
  188. package/dist/v1/libs/plugin/TinyPlugin.cjs +1630 -0
  189. package/dist/v1/libs/plugin/TinyPlugin.d.mts +753 -0
  190. package/dist/v1/libs/plugin/TinyPlugin.d.mts.map +1 -0
  191. package/dist/v1/libs/plugin/TinyPlugin.mjs +1376 -0
  192. package/dist/v1/libs/plugin/TinyVersion.cjs +204 -0
  193. package/dist/v1/libs/plugin/TinyVersion.d.mts +70 -0
  194. package/dist/v1/libs/plugin/TinyVersion.d.mts.map +1 -0
  195. package/dist/v1/libs/plugin/TinyVersion.mjs +179 -0
  196. package/dist/v1/libs/router/TinyRouter.cjs +661 -0
  197. package/dist/v1/libs/router/TinyRouter.d.mts +320 -0
  198. package/dist/v1/libs/router/TinyRouter.d.mts.map +1 -0
  199. package/dist/v1/libs/router/TinyRouter.mjs +572 -0
  200. package/dist/v1/libs/storage/TinyLocalStorage.cjs +20 -1
  201. package/dist/v1/libs/storage/TinyLocalStorage.d.mts +64 -2
  202. package/dist/v1/libs/storage/TinyLocalStorage.d.mts.map +1 -1
  203. package/dist/v1/libs/storage/TinyLocalStorage.mjs +18 -1
  204. package/dist/v1/libs/storage/TinyMapCache.cjs +373 -0
  205. package/dist/v1/libs/storage/TinyMapCache.d.mts +274 -0
  206. package/dist/v1/libs/storage/TinyMapCache.d.mts.map +1 -0
  207. package/dist/v1/libs/storage/TinyMapCache.mjs +333 -0
  208. package/dist/v1/libs/storage/TinySetMapDatabase.cjs +1167 -0
  209. package/dist/v1/libs/storage/TinySetMapDatabase.d.mts +603 -0
  210. package/dist/v1/libs/storage/TinySetMapDatabase.d.mts.map +1 -0
  211. package/dist/v1/libs/storage/TinySetMapDatabase.mjs +1059 -0
  212. package/dist/v1/libs/sw/browser/TinyNotificationAdapter.cjs +126 -0
  213. package/dist/v1/libs/sw/browser/TinyNotificationAdapter.d.mts +132 -0
  214. package/dist/v1/libs/sw/browser/TinyNotificationAdapter.d.mts.map +1 -0
  215. package/dist/v1/libs/sw/browser/TinyNotificationAdapter.mjs +109 -0
  216. package/dist/v1/libs/sw/browser/TinyPushManager.cjs +310 -0
  217. package/dist/v1/libs/sw/browser/TinyPushManager.d.mts +168 -0
  218. package/dist/v1/libs/sw/browser/TinyPushManager.d.mts.map +1 -0
  219. package/dist/v1/libs/sw/browser/TinyPushManager.mjs +264 -0
  220. package/dist/v1/libs/sw/browser/TinyServiceWorker.cjs +1060 -0
  221. package/dist/v1/libs/sw/browser/TinyServiceWorker.d.mts +348 -0
  222. package/dist/v1/libs/sw/browser/TinyServiceWorker.d.mts.map +1 -0
  223. package/dist/v1/libs/sw/browser/TinyServiceWorker.mjs +933 -0
  224. package/dist/v1/libs/sw/browser/plugins/TabManager.cjs +315 -0
  225. package/dist/v1/libs/sw/browser/plugins/TabManager.d.mts +178 -0
  226. package/dist/v1/libs/sw/browser/plugins/TabManager.d.mts.map +1 -0
  227. package/dist/v1/libs/sw/browser/plugins/TabManager.mjs +274 -0
  228. package/dist/v1/libs/sw/browser/utils.cjs +28 -0
  229. package/dist/v1/libs/sw/browser/utils.d.mts +8 -0
  230. package/dist/v1/libs/sw/browser/utils.d.mts.map +1 -0
  231. package/dist/v1/libs/sw/browser/utils.mjs +21 -0
  232. package/dist/v1/libs/sw/service/TinyPushRouter.cjs +222 -0
  233. package/dist/v1/libs/sw/service/TinyPushRouter.d.mts +138 -0
  234. package/dist/v1/libs/sw/service/TinyPushRouter.d.mts.map +1 -0
  235. package/dist/v1/libs/sw/service/TinyPushRouter.mjs +201 -0
  236. package/dist/v1/libs/sw/service/TinyServiceWorkerEngine.cjs +3016 -0
  237. package/dist/v1/libs/sw/service/TinyServiceWorkerEngine.d.mts +993 -0
  238. package/dist/v1/libs/sw/service/TinyServiceWorkerEngine.d.mts.map +1 -0
  239. package/dist/v1/libs/sw/service/TinyServiceWorkerEngine.mjs +2558 -0
  240. package/dist/v1/libs/sw/service/plugins/GlobBypassPlugin.cjs +88 -0
  241. package/dist/v1/libs/sw/service/plugins/GlobBypassPlugin.d.mts +32 -0
  242. package/dist/v1/libs/sw/service/plugins/GlobBypassPlugin.d.mts.map +1 -0
  243. package/dist/v1/libs/sw/service/plugins/GlobBypassPlugin.mjs +69 -0
  244. package/dist/v1/libs/sw/service/plugins/GlobCachePlugin.cjs +112 -0
  245. package/dist/v1/libs/sw/service/plugins/GlobCachePlugin.d.mts +31 -0
  246. package/dist/v1/libs/sw/service/plugins/GlobCachePlugin.d.mts.map +1 -0
  247. package/dist/v1/libs/sw/service/plugins/GlobCachePlugin.mjs +91 -0
  248. package/dist/v1/libs/sw/service/plugins/Ping.cjs +32 -0
  249. package/dist/v1/libs/sw/service/plugins/Ping.d.mts +9 -0
  250. package/dist/v1/libs/sw/service/plugins/Ping.d.mts.map +1 -0
  251. package/dist/v1/libs/sw/service/plugins/Ping.mjs +25 -0
  252. package/dist/v1/libs/sw/service/plugins/TabManager.cjs +457 -0
  253. package/dist/v1/libs/sw/service/plugins/TabManager.d.mts +91 -0
  254. package/dist/v1/libs/sw/service/plugins/TabManager.d.mts.map +1 -0
  255. package/dist/v1/libs/sw/service/plugins/TabManager.mjs +372 -0
  256. package/dist/v1/libs/sw/service/plugins/ViteFileDetector.cjs +180 -0
  257. package/dist/v1/libs/sw/service/plugins/ViteFileDetector.d.mts +28 -0
  258. package/dist/v1/libs/sw/service/plugins/ViteFileDetector.d.mts.map +1 -0
  259. package/dist/v1/libs/sw/service/plugins/ViteFileDetector.mjs +148 -0
  260. package/dist/v1/libs/sw/service/shared/TinyPushPayload.cjs +199 -0
  261. package/dist/v1/libs/sw/service/shared/TinyPushPayload.d.mts +80 -0
  262. package/dist/v1/libs/sw/service/shared/TinyPushPayload.d.mts.map +1 -0
  263. package/dist/v1/libs/sw/service/shared/TinyPushPayload.mjs +174 -0
  264. package/dist/v1/libs/sw/utils.cjs +57 -0
  265. package/dist/v1/libs/sw/utils.d.mts +173 -0
  266. package/dist/v1/libs/sw/utils.d.mts.map +1 -0
  267. package/dist/v1/libs/sw/utils.mjs +50 -0
  268. package/dist/v1/libs/text/TinyI18/Browser-NON-DOM.cjs +57 -0
  269. package/dist/v1/libs/text/TinyI18/Browser-NON-DOM.d.mts +39 -0
  270. package/dist/v1/libs/text/TinyI18/Browser-NON-DOM.d.mts.map +1 -0
  271. package/dist/v1/libs/text/TinyI18/Browser-NON-DOM.mjs +48 -0
  272. package/dist/v1/libs/text/TinyI18/Browser.cjs +64 -0
  273. package/dist/v1/libs/text/TinyI18/Browser.d.mts +47 -0
  274. package/dist/v1/libs/text/TinyI18/Browser.d.mts.map +1 -0
  275. package/dist/v1/libs/text/TinyI18/Browser.mjs +57 -0
  276. package/dist/v1/libs/text/TinyI18/Node.cjs +49 -0
  277. package/dist/v1/libs/text/TinyI18/Node.d.mts +38 -0
  278. package/dist/v1/libs/text/TinyI18/Node.d.mts.map +1 -0
  279. package/dist/v1/libs/text/TinyI18/Node.mjs +43 -0
  280. package/dist/v1/libs/text/TinyI18/browser-utils.cjs +17 -0
  281. package/dist/v1/libs/text/TinyI18/browser-utils.d.mts +8 -0
  282. package/dist/v1/libs/text/TinyI18/browser-utils.d.mts.map +1 -0
  283. package/dist/v1/libs/text/TinyI18/browser-utils.mjs +13 -0
  284. package/dist/v1/libs/text/{TinyI18.cjs → TinyI18/index.cjs} +205 -104
  285. package/dist/v1/libs/text/{TinyI18.d.mts → TinyI18/index.d.mts} +141 -92
  286. package/dist/v1/libs/text/TinyI18/index.d.mts.map +1 -0
  287. package/dist/v1/libs/text/{TinyI18.mjs → TinyI18/index.mjs} +195 -101
  288. package/dist/v1/libs/tools/TinyBrowserMonitor.cjs +1088 -0
  289. package/dist/v1/libs/tools/TinyBrowserMonitor.d.mts +621 -0
  290. package/dist/v1/libs/tools/TinyBrowserMonitor.d.mts.map +1 -0
  291. package/dist/v1/libs/tools/TinyBrowserMonitor.mjs +940 -0
  292. package/dist/v1/libs/tools/TinyDebugger.cjs +180 -19
  293. package/dist/v1/libs/tools/TinyDebugger.d.mts +220 -24
  294. package/dist/v1/libs/tools/TinyDebugger.d.mts.map +1 -1
  295. package/dist/v1/libs/tools/TinyDebugger.mjs +161 -19
  296. package/dist/v1/libs/tools/TinyHtmlTagRegexBuilder.cjs +256 -0
  297. package/dist/v1/libs/tools/TinyHtmlTagRegexBuilder.d.mts +125 -0
  298. package/dist/v1/libs/tools/TinyHtmlTagRegexBuilder.d.mts.map +1 -0
  299. package/dist/v1/libs/tools/TinyHtmlTagRegexBuilder.mjs +223 -0
  300. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser-NON-DOM.cjs +23 -0
  301. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser-NON-DOM.d.mts +17 -0
  302. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser-NON-DOM.d.mts.map +1 -0
  303. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser-NON-DOM.mjs +19 -0
  304. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser.cjs +23 -0
  305. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser.d.mts +17 -0
  306. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser.d.mts.map +1 -0
  307. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser.mjs +19 -0
  308. package/dist/v1/libs/tools/TinyHttpResponseRegistry/EmptyRequestCodes.cjs +390 -0
  309. package/dist/v1/libs/tools/TinyHttpResponseRegistry/EmptyRequestCodes.d.mts +9 -0
  310. package/dist/v1/libs/tools/TinyHttpResponseRegistry/EmptyRequestCodes.d.mts.map +1 -0
  311. package/dist/v1/libs/tools/TinyHttpResponseRegistry/EmptyRequestCodes.mjs +387 -0
  312. package/dist/v1/libs/tools/TinyHttpResponseRegistry/JsDoc.cjs +2 -0
  313. package/dist/v1/libs/tools/TinyHttpResponseRegistry/JsDoc.d.mts +27 -0
  314. package/dist/v1/libs/tools/TinyHttpResponseRegistry/JsDoc.d.mts.map +1 -0
  315. package/dist/v1/libs/tools/TinyHttpResponseRegistry/JsDoc.mjs +12 -0
  316. package/dist/v1/libs/tools/TinyHttpResponseRegistry/MozillaRequestCodes/LICENSE +53 -0
  317. package/dist/v1/libs/tools/TinyHttpResponseRegistry/MozillaRequestCodes/index.cjs +405 -0
  318. package/dist/v1/libs/tools/TinyHttpResponseRegistry/MozillaRequestCodes/index.d.mts +34 -0
  319. package/dist/v1/libs/tools/TinyHttpResponseRegistry/MozillaRequestCodes/index.d.mts.map +1 -0
  320. package/dist/v1/libs/tools/TinyHttpResponseRegistry/MozillaRequestCodes/index.mjs +342 -0
  321. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Node.cjs +23 -0
  322. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Node.d.mts +17 -0
  323. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Node.d.mts.map +1 -0
  324. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Node.mjs +19 -0
  325. package/dist/v1/libs/tools/TinyHttpResponseRegistry/RequestCodes.cjs +327 -0
  326. package/dist/v1/libs/tools/TinyHttpResponseRegistry/RequestCodes.d.mts +9 -0
  327. package/dist/v1/libs/tools/TinyHttpResponseRegistry/RequestCodes.d.mts.map +1 -0
  328. package/dist/v1/libs/tools/TinyHttpResponseRegistry/RequestCodes.mjs +324 -0
  329. package/dist/v1/libs/tools/TinyHttpResponseRegistry/index.cjs +268 -0
  330. package/dist/v1/libs/tools/TinyHttpResponseRegistry/index.d.mts +107 -0
  331. package/dist/v1/libs/tools/TinyHttpResponseRegistry/index.d.mts.map +1 -0
  332. package/dist/v1/libs/tools/TinyHttpResponseRegistry/index.mjs +238 -0
  333. package/dist/v1/libs/tools/TinyPasswordValidator.cjs +491 -0
  334. package/dist/v1/libs/tools/TinyPasswordValidator.d.mts +246 -0
  335. package/dist/v1/libs/tools/TinyPasswordValidator.d.mts.map +1 -0
  336. package/dist/v1/libs/tools/TinyPasswordValidator.mjs +434 -0
  337. package/dist/v1/libs/tools/TinyPkgExportValidator.cjs +48 -0
  338. package/dist/v1/libs/tools/TinyPkgExportValidator.d.mts +14 -0
  339. package/dist/v1/libs/tools/TinyPkgExportValidator.d.mts.map +1 -1
  340. package/dist/v1/libs/tools/TinyPkgExportValidator.mjs +44 -0
  341. package/dist/v1/libs/tools/TinySiteMap.cjs +1029 -0
  342. package/dist/v1/libs/tools/TinySiteMap.d.mts +429 -0
  343. package/dist/v1/libs/tools/TinySiteMap.d.mts.map +1 -0
  344. package/dist/v1/libs/tools/TinySiteMap.mjs +910 -0
  345. package/dist/v1/libs/tools/TinySiteMapStream.cjs +121 -0
  346. package/dist/v1/libs/tools/TinySiteMapStream.d.mts +51 -0
  347. package/dist/v1/libs/tools/TinySiteMapStream.d.mts.map +1 -0
  348. package/dist/v1/libs/tools/TinySiteMapStream.mjs +85 -0
  349. package/dist/v1/libs/tools/TinyURLSecurityVerifier.cjs +444 -0
  350. package/dist/v1/libs/tools/TinyURLSecurityVerifier.d.mts +217 -0
  351. package/dist/v1/libs/tools/TinyURLSecurityVerifier.d.mts.map +1 -0
  352. package/dist/v1/libs/tools/TinyURLSecurityVerifier.mjs +394 -0
  353. package/dist/v1/libs/tools/TinyUriParser/BlueSky.cjs +613 -0
  354. package/dist/v1/libs/tools/TinyUriParser/BlueSky.d.mts +152 -0
  355. package/dist/v1/libs/tools/TinyUriParser/BlueSky.d.mts.map +1 -0
  356. package/dist/v1/libs/tools/TinyUriParser/BlueSky.mjs +527 -0
  357. package/dist/v1/libs/tools/TinyUriParser/Discord.cjs +1119 -0
  358. package/dist/v1/libs/tools/TinyUriParser/Discord.d.mts +306 -0
  359. package/dist/v1/libs/tools/TinyUriParser/Discord.d.mts.map +1 -0
  360. package/dist/v1/libs/tools/TinyUriParser/Discord.mjs +968 -0
  361. package/dist/v1/libs/tools/TinyUriParser/MatrixProtocol.cjs +469 -0
  362. package/dist/v1/libs/tools/TinyUriParser/MatrixProtocol.d.mts +80 -0
  363. package/dist/v1/libs/tools/TinyUriParser/MatrixProtocol.d.mts.map +1 -0
  364. package/dist/v1/libs/tools/TinyUriParser/MatrixProtocol.mjs +379 -0
  365. package/dist/v1/libs/tools/TinyUriParser.cjs +217 -0
  366. package/dist/v1/libs/tools/TinyUriParser.d.mts +109 -0
  367. package/dist/v1/libs/tools/TinyUriParser.d.mts.map +1 -0
  368. package/dist/v1/libs/tools/TinyUriParser.mjs +183 -0
  369. package/dist/v1/libs/utils/TinyCloner.cjs +466 -0
  370. package/dist/v1/libs/utils/TinyCloner.d.mts +221 -0
  371. package/dist/v1/libs/utils/TinyCloner.d.mts.map +1 -0
  372. package/dist/v1/libs/utils/TinyCloner.mjs +416 -0
  373. package/dist/v1/libs/utils/TinyPromiseQueue.cjs +1 -1
  374. package/dist/v1/libs/utils/TinyPromiseQueue.d.mts +2 -2
  375. package/dist/v1/libs/utils/TinyPromiseQueue.d.mts.map +1 -1
  376. package/dist/v1/libs/utils/TinyPromiseQueue.mjs +1 -1
  377. package/dist/v1/libs/utils/TinyThrottledApi.cjs +333 -0
  378. package/dist/v1/libs/utils/TinyThrottledApi.d.mts +163 -0
  379. package/dist/v1/libs/utils/TinyThrottledApi.d.mts.map +1 -0
  380. package/dist/v1/libs/utils/TinyThrottledApi.mjs +294 -0
  381. package/dist/v1/libs/worker/browser/TinyWebWorker.cjs +342 -0
  382. package/dist/v1/libs/worker/browser/TinyWebWorker.d.mts +97 -0
  383. package/dist/v1/libs/worker/browser/TinyWebWorker.d.mts.map +1 -0
  384. package/dist/v1/libs/worker/browser/TinyWebWorker.mjs +305 -0
  385. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.cjs +346 -0
  386. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.d.mts +127 -0
  387. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.d.mts.map +1 -0
  388. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.mjs +304 -0
  389. package/dist/v1/regexp/Email.cjs +185 -0
  390. package/dist/v1/regexp/Email.d.mts +73 -0
  391. package/dist/v1/regexp/Email.d.mts.map +1 -0
  392. package/dist/v1/regexp/Email.mjs +153 -0
  393. package/dist/v1/regexp/Glob.cjs +293 -0
  394. package/dist/v1/regexp/Glob.d.mts +45 -0
  395. package/dist/v1/regexp/Glob.d.mts.map +1 -0
  396. package/dist/v1/regexp/Glob.mjs +260 -0
  397. package/dist/v1/regexp/Ipv4.cjs +57 -0
  398. package/dist/v1/regexp/Ipv4.d.mts +29 -0
  399. package/dist/v1/regexp/Ipv4.d.mts.map +1 -0
  400. package/dist/v1/regexp/Ipv4.mjs +45 -0
  401. package/dist/v1/regexp/Ipv6.cjs +67 -0
  402. package/dist/v1/regexp/Ipv6.d.mts +29 -0
  403. package/dist/v1/regexp/Ipv6.d.mts.map +1 -0
  404. package/dist/v1/regexp/Ipv6.mjs +54 -0
  405. package/dist/v1/regexp/SegmentExtractor.cjs +145 -0
  406. package/dist/v1/regexp/SegmentExtractor.d.mts +91 -0
  407. package/dist/v1/regexp/SegmentExtractor.d.mts.map +1 -0
  408. package/dist/v1/regexp/SegmentExtractor.mjs +116 -0
  409. package/dist/v1/regexp/UrlDetector.cjs +125 -0
  410. package/dist/v1/regexp/UrlDetector.d.mts +67 -0
  411. package/dist/v1/regexp/UrlDetector.d.mts.map +1 -0
  412. package/dist/v1/regexp/UrlDetector.mjs +102 -0
  413. package/dist/v1/regexp/Username/index.cjs +233 -0
  414. package/dist/v1/regexp/Username/index.d.mts +77 -0
  415. package/dist/v1/regexp/Username/index.d.mts.map +1 -0
  416. package/dist/v1/regexp/Username/index.mjs +199 -0
  417. package/dist/v1/regexp/Username/jsDoc.cjs +2 -0
  418. package/dist/v1/regexp/Username/jsDoc.d.mts +57 -0
  419. package/dist/v1/regexp/Username/jsDoc.d.mts.map +1 -0
  420. package/dist/v1/regexp/Username/jsDoc.mjs +18 -0
  421. package/dist/v1/regexp/Username/templates/BlueSky.cjs +56 -0
  422. package/dist/v1/regexp/Username/templates/BlueSky.d.mts +35 -0
  423. package/dist/v1/regexp/Username/templates/BlueSky.d.mts.map +1 -0
  424. package/dist/v1/regexp/Username/templates/BlueSky.mjs +52 -0
  425. package/dist/v1/regexp/Username/templates/Discord.cjs +505 -0
  426. package/dist/v1/regexp/Username/templates/Discord.d.mts +209 -0
  427. package/dist/v1/regexp/Username/templates/Discord.d.mts.map +1 -0
  428. package/dist/v1/regexp/Username/templates/Discord.mjs +458 -0
  429. package/dist/v1/regexp/Username/templates/MatrixProtocol.cjs +143 -0
  430. package/dist/v1/regexp/Username/templates/MatrixProtocol.d.mts +70 -0
  431. package/dist/v1/regexp/Username/templates/MatrixProtocol.d.mts.map +1 -0
  432. package/dist/v1/regexp/Username/templates/MatrixProtocol.mjs +128 -0
  433. package/dist/v1/regexp/Username/templates/deprecated/Discord.cjs +24 -0
  434. package/dist/v1/regexp/Username/templates/deprecated/Discord.d.mts +17 -0
  435. package/dist/v1/regexp/Username/templates/deprecated/Discord.d.mts.map +1 -0
  436. package/dist/v1/regexp/Username/templates/deprecated/Discord.mjs +20 -0
  437. package/dist/v1/regexp/Username/templates/index.cjs +11 -0
  438. package/dist/v1/regexp/Username/templates/index.d.mts +5 -0
  439. package/dist/v1/regexp/Username/templates/index.d.mts.map +1 -0
  440. package/dist/v1/regexp/Username/templates/index.mjs +4 -0
  441. package/dist/v1/regexp/index.cjs +41 -0
  442. package/dist/v1/regexp/index.d.mts +9 -0
  443. package/dist/v1/regexp/index.d.mts.map +1 -0
  444. package/dist/v1/regexp/index.mjs +8 -0
  445. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Browser.cjs +64 -0
  446. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Browser.d.mts +11 -0
  447. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Browser.d.mts.map +1 -0
  448. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Browser.mjs +51 -0
  449. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/FileInput.cjs +66 -0
  450. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/FileInput.d.mts +11 -0
  451. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/FileInput.d.mts.map +1 -0
  452. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/FileInput.mjs +51 -0
  453. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Node.cjs +39 -0
  454. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Node.d.mts +11 -0
  455. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Node.d.mts.map +1 -0
  456. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Node.mjs +28 -0
  457. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Template.cjs +494 -0
  458. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Template.d.mts +142 -0
  459. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Template.d.mts.map +1 -0
  460. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Template.mjs +445 -0
  461. package/dist/v1/webTemplates/bootstrap/5.3/html/BootstrapDialogs.cjs +30 -4
  462. package/dist/v1/webTemplates/bootstrap/5.3/html/BootstrapDialogs.d.mts +5 -2
  463. package/dist/v1/webTemplates/bootstrap/5.3/html/BootstrapDialogs.d.mts.map +1 -1
  464. package/dist/v1/webTemplates/bootstrap/5.3/html/BootstrapDialogs.mjs +29 -5
  465. package/dist/v1/webTemplates/media/MediaValidator/v1/Browser/index.cjs +135 -0
  466. package/dist/v1/webTemplates/media/MediaValidator/v1/Browser/index.d.mts +83 -0
  467. package/dist/v1/webTemplates/media/MediaValidator/v1/Browser/index.d.mts.map +1 -0
  468. package/dist/v1/webTemplates/media/MediaValidator/v1/Browser/index.mjs +120 -0
  469. package/dist/v1/webTemplates/media/MediaValidator/v1/Node/index.cjs +127 -0
  470. package/dist/v1/webTemplates/media/MediaValidator/v1/Node/index.d.mts +95 -0
  471. package/dist/v1/webTemplates/media/MediaValidator/v1/Node/index.d.mts.map +1 -0
  472. package/dist/v1/webTemplates/media/MediaValidator/v1/Node/index.mjs +109 -0
  473. package/dist/v1/webTemplates/multiformats/14.0/IPFSHasher/index.cjs +83 -0
  474. package/dist/v1/webTemplates/multiformats/14.0/IPFSHasher/index.d.mts +46 -0
  475. package/dist/v1/webTemplates/multiformats/14.0/IPFSHasher/index.d.mts.map +1 -0
  476. package/dist/v1/webTemplates/multiformats/14.0/IPFSHasher/index.mjs +71 -0
  477. package/dist/v1/webTemplates/vite/7.3/plugins/TinyVitePwaPlugin.cjs +493 -0
  478. package/dist/v1/webTemplates/vite/7.3/plugins/TinyVitePwaPlugin.d.mts +67 -0
  479. package/dist/v1/webTemplates/vite/7.3/plugins/TinyVitePwaPlugin.d.mts.map +1 -0
  480. package/dist/v1/webTemplates/vite/7.3/plugins/TinyVitePwaPlugin.mjs +399 -0
  481. package/dist/v1/webTemplates/vite/7.3/plugins/githubUtils.cjs +35 -0
  482. package/dist/v1/webTemplates/vite/7.3/plugins/githubUtils.d.mts +6 -0
  483. package/dist/v1/webTemplates/vite/7.3/plugins/githubUtils.d.mts.map +1 -0
  484. package/dist/v1/webTemplates/vite/7.3/plugins/githubUtils.mjs +29 -0
  485. package/docs/info/RegExp.md +74 -0
  486. package/docs/v1/Exports.md +101 -0
  487. package/docs/v1/README.md +113 -15
  488. package/docs/v1/basics/asyncReplace.md +2 -0
  489. package/docs/v1/basics/browserDetector.md +28 -11
  490. package/docs/v1/basics/crypto.md +85 -0
  491. package/docs/v1/basics/deprecated/html.md +2 -0
  492. package/docs/v1/basics/deprecated/objFilter.md +2 -0
  493. package/docs/v1/basics/html.md +2 -0
  494. package/docs/v1/basics/objChecker.md +55 -0
  495. package/docs/v1/basics/objFilter.md +57 -86
  496. package/docs/v1/basics/promiseUtils.md +12 -3
  497. package/docs/v1/basics/text.md +0 -1
  498. package/docs/v1/libs/array/TinyArrayComparator.md +50 -39
  499. package/docs/v1/libs/fs/TinyFS.md +752 -0
  500. package/docs/v1/libs/fs/TinyOPFSFileSystem.md +495 -0
  501. package/docs/v1/libs/fs/TinyStorageBucketFileSystem.md +407 -0
  502. package/docs/v1/libs/plugin/TinyPlugin.md +441 -0
  503. package/docs/v1/libs/plugin/TinyVersion.md +145 -0
  504. package/docs/v1/libs/router/TinyRouter.md +216 -0
  505. package/docs/v1/libs/storage/TinyMapCache.md +219 -0
  506. package/docs/v1/libs/storage/TinySetMapDatabase.md +478 -0
  507. package/docs/v1/libs/sw/browser/TinyNotificationAdapter.md +280 -0
  508. package/docs/v1/libs/sw/browser/TinyPushManager.md +782 -0
  509. package/docs/v1/libs/sw/browser/TinyServiceWorker.md +365 -0
  510. package/docs/v1/libs/sw/browser/plugins/TabManager.md +133 -0
  511. package/docs/v1/libs/sw/service/TinyPushRouter.md +314 -0
  512. package/docs/v1/libs/sw/service/TinyServiceWorkerEngine.md +748 -0
  513. package/docs/v1/libs/sw/service/plugins/GlobBypassPlugin.md +229 -0
  514. package/docs/v1/libs/sw/service/plugins/GlobCachePlugin.md +91 -0
  515. package/docs/v1/libs/sw/service/plugins/Ping.md +36 -0
  516. package/docs/v1/libs/sw/service/plugins/TabManager.md +64 -0
  517. package/docs/v1/libs/sw/service/plugins/ViteFileDetector.md +92 -0
  518. package/docs/v1/libs/sw/service/shared/TinyPushPayload.md +316 -0
  519. package/docs/v1/libs/text/TinyI18.md +80 -56
  520. package/docs/v1/libs/tools/TinyBrowserMonitor.md +170 -0
  521. package/docs/v1/libs/tools/TinyDebugger.md +117 -1
  522. package/docs/v1/libs/tools/TinyHtmlTagRegexBuilder.md +141 -0
  523. package/docs/v1/libs/tools/TinyHttpResponseRegistry.md +129 -0
  524. package/docs/v1/libs/tools/TinyPasswordValidator.md +153 -0
  525. package/docs/v1/libs/tools/TinyPkgExportValidator.md +45 -1
  526. package/docs/v1/libs/tools/TinySiteMap.md +134 -0
  527. package/docs/v1/libs/tools/TinySiteMapStream.md +97 -0
  528. package/docs/v1/libs/tools/TinyURLSecurityVerifier.md +146 -0
  529. package/docs/v1/libs/tools/TinyUriParser/Discord.md +374 -0
  530. package/docs/v1/libs/tools/TinyUriParser/MatrixProtocol.md +54 -0
  531. package/docs/v1/libs/tools/TinyUriParser.md +128 -0
  532. package/docs/v1/libs/utils/TinyCloner.md +126 -0
  533. package/docs/v1/libs/utils/TinyPromiseQueue.md +1 -1
  534. package/docs/v1/libs/utils/TinyThrottledApi.md +219 -0
  535. package/docs/v1/libs/worker/browser/TinyWebWorker.md +331 -0
  536. package/docs/v1/libs/worker/engine/TinyWebWorkerEngine.md +137 -0
  537. package/docs/v1/regexp/Email.md +127 -0
  538. package/docs/v1/regexp/Glob.md +155 -0
  539. package/docs/v1/regexp/IPv4.md +103 -0
  540. package/docs/v1/regexp/IPv6.md +122 -0
  541. package/docs/v1/regexp/SegmentExtractor.md +120 -0
  542. package/docs/v1/regexp/UrlDetector.md +117 -0
  543. package/docs/v1/regexp/Username/Base.md +131 -0
  544. package/docs/v1/regexp/Username/Discord.md +267 -0
  545. package/docs/v1/regexp/Username/MatrixProtocol.md +131 -0
  546. package/docs/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters.md +569 -0
  547. package/docs/v1/webTemplates/bootstrap/5.3/html/BootstrapDialogs.md +19 -7
  548. package/docs/v1/webTemplates/media/MediaValidator/v1/Browser.md +123 -0
  549. package/docs/v1/webTemplates/media/MediaValidator/v1/Node.md +136 -0
  550. package/docs/v1/webTemplates/multiformats/14.0/IPFSHasher.md +310 -0
  551. package/docs/v1/webTemplates/vite/7.3/plugins/TinyVitePwaPlugin.md +131 -0
  552. package/docs/v1/webTemplates/vite/7.3/plugins/githubUtils.md +50 -0
  553. package/package.json +303 -13
  554. package/types/EventEmitter.d.ts +48 -0
  555. package/dist/v1/libs/text/TinyI18.d.mts.map +0 -1
@@ -0,0 +1,3016 @@
1
+ 'use strict';
2
+
3
+ var TinyPushRouter = require('./TinyPushRouter.cjs');
4
+ var TinyPushPayload = require('./shared/TinyPushPayload.cjs');
5
+ var SegmentExtractor = require('../../../regexp/SegmentExtractor.cjs');
6
+ var TinyCloner = require('../../utils/TinyCloner.cjs');
7
+ var BrowserNONDOM = require('../../tools/TinyHttpResponseRegistry/Browser-NON-DOM.cjs');
8
+ var TinyPlugin = require('../../plugin/TinyPlugin.cjs');
9
+ var utils = require('../utils.cjs');
10
+
11
+ const codeIs = BrowserNONDOM.codeIs;
12
+
13
+ /** @type {readonly InstallStrategy[]} The list of accepted install strategies. */
14
+ const INSTALL_STRATEGIES = Object.freeze(['immediate', 'wait', 'manual']);
15
+
16
+ /** @type {readonly ActivateStrategy[]} The list of accepted activate strategies. */
17
+ const ACTIVATE_STRATEGIES = Object.freeze(['immediate', 'wait', 'manual']);
18
+
19
+ /** @type {readonly LifecycleOrder[]} The list of accepted lifecycle orders. */
20
+ const LIFECYCLE_ORDERS = Object.freeze(['before', 'after']);
21
+
22
+ /**
23
+ * Single source of truth for the lifecycle options: every entry pairs the
24
+ * property name with the list of values it accepts.
25
+ *
26
+ * @type {readonly (readonly [string, readonly string[]])[]}
27
+ */
28
+ const LIFECYCLE_SPEC = Object.freeze([
29
+ ['installStrategy', INSTALL_STRATEGIES],
30
+ ['activateStrategy', ACTIVATE_STRATEGIES],
31
+ ['installOrder', LIFECYCLE_ORDERS],
32
+ ['activateOrder', LIFECYCLE_ORDERS],
33
+ ]);
34
+
35
+ /**
36
+ * Maps each lifecycle phase to the {@link LifecycleOptions} key that stores the
37
+ * order of that phase. Keeping the mapping explicit lets the type checker prove
38
+ * that every phase resolves to a valid key.
39
+ *
40
+ * @type {Readonly<Record<'install'|'activate', 'installOrder'|'activateOrder'>>}
41
+ */
42
+ const PHASE_ORDER_KEY = Object.freeze({
43
+ install: 'installOrder',
44
+ activate: 'activateOrder',
45
+ });
46
+
47
+ ///////////////////////////////////////////////////////////////////
48
+
49
+ /**
50
+ * Describes a single entry of the {@link GlobalMsgCode} table.
51
+ *
52
+ * Each entry maps an internal HTTP status code (or the `unknown` fallback) to the
53
+ * information required to build a response and to log the occurrence.
54
+ *
55
+ * @template {string|null} Path - The type accepted by {@link GlobalMsgCodeItem.path}.
56
+ * Use `string` for entries that serve a file and `null` for entries that must not
57
+ * serve any file.
58
+ * @typedef {Object} GlobalMsgCodeItem
59
+ * @property {Path} path - Absolute path of the file served for this entry.
60
+ * When the value is `null`, no file is fetched and the response is built from
61
+ * {@link GlobalMsgCodeItem.msg} only.
62
+ * @property {string} msg - Short, human readable message. It is used as the response
63
+ * body and as the `statusText` of the generated error response.
64
+ * @property {string} logMsg - Message written to the log. It is aimed at developers,
65
+ * so it can be more descriptive than {@link GlobalMsgCodeItem.msg}.
66
+ */
67
+
68
+ /**
69
+ * The complete message table used by the engine to answer a request.
70
+ *
71
+ * Every key except `spaPath` is an HTTP status code. The `unknown` key is the
72
+ * fallback used when a status code has no dedicated entry.
73
+ *
74
+ * @typedef {{ spaPath: string; unknown: GlobalMsgCodeItem<string>; 200: GlobalMsgCodeItem<null>; 404: GlobalMsgCodeItem<string>; 500: GlobalMsgCodeItem<string>; }} GlobalMsgCode
75
+ * @property {string} spaPath - Path served for every navigation request while
76
+ * `spaMode` is `true`.
77
+ * @property {GlobalMsgCodeItem<string>} unknown - Fallback entry, used when no other
78
+ * entry matches the status code.
79
+ */
80
+
81
+ ///////////////////////////////////////////////////////////////////
82
+
83
+ /**
84
+ * Enriched event object for the sync event.
85
+ * @typedef {Object} SyncEventObj
86
+ * @property {SyncEvent} event - The native sync event.
87
+ * @property {string} tag - The tag associated with the sync event.
88
+ */
89
+
90
+ /**
91
+ * Callback for the sync handler.
92
+ * @callback SyncCallback
93
+ * @param {SyncEventObj} msg - The sync event object.
94
+ * @returns {Promise<void> | void} A promise that settles once the handler finishes, or nothing when the handler is synchronous.
95
+ */
96
+
97
+ /**
98
+ * Enriched event object for the install and activate lifecycle events.
99
+ * @typedef {Object} LifecycleEventObj
100
+ * @property {ExtendableEvent} event - The native install or activate event.
101
+ */
102
+
103
+ /**
104
+ * Callback for install and activate lifecycle handlers.
105
+ * @callback LifecycleCallback
106
+ * @param {LifecycleEventObj} msg - The lifecycle event object.
107
+ * @returns {Promise<void> | void} A promise that settles once the handler finishes, or nothing when the handler is synchronous.
108
+ */
109
+
110
+ ///////////////////////////////////////////////////////////////////
111
+
112
+ /**
113
+ * A function that handles the actual postMessage call to the message api source.
114
+ * @callback MessageApiReply
115
+ * @param {string} type - The type identifier for the api reply message.
116
+ * @param {MessagePayload} [data] - The payload to be sent in the api reply.
117
+ * @param {number} [timeout=10000] - Wait time.
118
+ * @returns {Promise<any>} A promise that resolves with the response payload sent back by the message source.
119
+ */
120
+
121
+ /**
122
+ * Options object passed to every API handler registered through {@link TinyServiceWorkerEngine#onApi}.
123
+ * @typedef {Object} ApiHandlerOptions
124
+ * @property {MessagePayload} [data] - The payload received from the browser.
125
+ * @property {string} clientId - The ID of the client that sent the message.
126
+ * @property {string} correlationId - The request ID.
127
+ * @property {MessageReplyTo} replyTo - A function to send a reply to the message source.
128
+ * @property {MessageReplyToAll} replyToAll - A function to send a reply to all clients.
129
+ * @property {MessageReplyTemplate} replyTemplate - A template function to format reply messages.
130
+ * @property {MessageReply} reply - A convenience method to reply to the message source.
131
+ * @property {MessageApiReply} replyApi - A convenience method to reply to the message api source.
132
+ */
133
+
134
+ /**
135
+ * Handles an API call received from the browser and returns the payload that must be sent back.
136
+ * @callback ApiHandlerCallback
137
+ * @param {ApiHandlerOptions} options - Options for the API handler.
138
+ * @returns {Promise<MessagePayload|undefined> | (MessagePayload|undefined)} The response payload.
139
+ */
140
+
141
+ ///////////////////////////////////////////////////////////////////
142
+
143
+ /**
144
+ * Strategy used to resolve the `install` phase of the Service Worker.
145
+ *
146
+ * - `immediate`: runs the install listeners and calls `skipWaiting()` right away.
147
+ * - `wait`: never calls `skipWaiting()`. The new worker stays in the `waiting`
148
+ * state until every client is closed.
149
+ * - `manual`: does not call `skipWaiting()` automatically. The application must
150
+ * call {@link TinyServiceWorkerEngine#skipWaiting} when it decides to.
151
+ *
152
+ * @typedef {'immediate'|'wait'|'manual'} InstallStrategy
153
+ */
154
+
155
+ /**
156
+ * Strategy used to resolve the `activate` phase of the Service Worker.
157
+ *
158
+ * - `immediate`: runs the activate listeners and calls `clients.claim()` right away.
159
+ * - `wait`: never calls `clients.claim()`.
160
+ * - `manual`: does not call `clients.claim()` automatically.
161
+ *
162
+ * @typedef {'immediate'|'wait'|'manual'} ActivateStrategy
163
+ */
164
+
165
+ /**
166
+ * Determines whether the lifecycle strategy of a phase runs before or after
167
+ * the user-registered listeners of that same phase.
168
+ *
169
+ * - `before`: the strategy (`skipWaiting()` / `clients.claim()`) is resolved
170
+ * first, then the listeners run.
171
+ * - `after`: the listeners run first, then the strategy is resolved.
172
+ *
173
+ * @typedef {'before'|'after'} LifecycleOrder
174
+ */
175
+
176
+ /**
177
+ * Lifecycle configuration for the engine.
178
+ *
179
+ * @typedef {Object} LifecycleOptions
180
+ * @property {InstallStrategy} installStrategy - How the `install` phase is resolved.
181
+ * @property {ActivateStrategy} activateStrategy - How the `activate` phase is resolved.
182
+ * @property {LifecycleOrder} installOrder - Order of the strategy inside the `install` phase.
183
+ * @property {LifecycleOrder} activateOrder - Order of the strategy inside the `activate` phase.
184
+ */
185
+
186
+ ///////////////////////////////////////////////////////////////////
187
+
188
+ /**
189
+ /**
190
+ * A partial configuration object for Service Worker settings.
191
+ * @typedef {Object} PartialServiceWorkerSettings
192
+ * @property {boolean} [spaMode] - Whether the service worker is running in Single Page Application mode.
193
+ * @property {Partial<LifecycleOptions>} [lifecycle] - Partial lifecycle configuration.
194
+ * @property {PartialFetchOptions} fetch - Partial configuration for fetch event interception.
195
+ * @property {PartialPushOptions} push - Partial configuration for push event interception.
196
+ * @property {PartialSyncOptions} sync - Partial configuration for sync event interception.
197
+ * @property {Partial<MessagingOptions>} messaging - Partial configuration for message event handling.
198
+ */
199
+
200
+ /**
201
+ * A partial configuration object for background sync interception settings.
202
+ * @typedef {Object} PartialSyncOptions
203
+ * @property {boolean} [enabled] - Indicates if sync interception is enabled.
204
+ */
205
+
206
+ /**
207
+ * A partial configuration object for push interception settings.
208
+ * @typedef {Object} PartialPushOptions
209
+ * @property {boolean} [enabled] - Indicates if push interception is enabled.
210
+ * @property {string} [vapidPublicKey] - Base64 URL-safe VAPID public key.
211
+ * @property {string} [subscribeEndpoint] - Backend route that persists subscriptions.
212
+ * @property {string} [defaultIcon] - Fallback notification icon.
213
+ * @property {string} [defaultBadge] - Fallback notification badge.
214
+ * @property {string} [defaultUrl] - Fallback click URL.
215
+ */
216
+
217
+ /**
218
+ * A partial configuration object for fetch interception settings.
219
+ * @typedef {Object} PartialFetchOptions
220
+ * @property {boolean} [enabled] - Indicates if fetch interception is enabled.
221
+ * @property {Partial<RouterOptions>} router - Partial configuration for the routing logic.
222
+ */
223
+
224
+ ///////////////////////////////////////////////////////////////////
225
+
226
+ /**
227
+ * Configuration settings for intercepting and handling fetch events.
228
+ * @typedef {Object} FetchOptions
229
+ * @property {boolean} enabled - Indicates if fetch interception is enabled.
230
+ * @property {RouterOptions} router - Configuration for the routing logic.
231
+ */
232
+
233
+ /**
234
+ * Configuration settings for intercepting and handling push events.
235
+ * @typedef {Object} PushOptions
236
+ * @property {boolean} enabled - Indicates if push interception is enabled.
237
+ * @property {string} vapidPublicKey - Base64 URL-safe VAPID public key.
238
+ * @property {string} subscribeEndpoint - Backend route that persists subscriptions.
239
+ * @property {string} defaultIcon - Fallback notification icon.
240
+ * @property {string} defaultBadge - Fallback notification badge.
241
+ * @property {string} defaultUrl - Fallback click URL.
242
+ */
243
+
244
+ /**
245
+ * Configuration settings for handling message communication.
246
+ * @typedef {Object} MessagingOptions
247
+ * @property {boolean} enabled - Indicates if message communication is enabled.
248
+ */
249
+
250
+ /**
251
+ * Configuration settings for background sync event interception.
252
+ * @typedef {Object} SyncOptions
253
+ * @property {boolean} enabled - Indicates if sync interception is enabled.
254
+ */
255
+
256
+ /**
257
+ * Configuration settings for the routing logic used during fetch interception.
258
+ * @typedef {Object} RouterOptions
259
+ * @property {boolean} enabled - Indicates if the router is active.
260
+ * @property {Map<number, RouterCodeConfig>} codes - A map linking HTTP status codes to their respective configurations.
261
+ */
262
+
263
+ /**
264
+ * The complete configuration structure for the Service Worker engine.
265
+ * @typedef {Object} ServiceWorkerSettings
266
+ * @property {boolean} spaMode - Whether the service worker is running in Single Page Application mode.
267
+ * @property {LifecycleOptions} lifecycle - Lifecycle strategy configuration.
268
+ * @property {FetchOptions} fetch - Configuration for fetch event interception.
269
+ * @property {PushOptions} push - Configuration for push event interception.
270
+ * @property {SyncOptions} sync - Configuration for background sync event interception.
271
+ * @property {MessagingOptions} messaging - Configuration for message event handling.
272
+ */
273
+
274
+ ///////////////////////////////////////////////////////////////////
275
+
276
+ /**
277
+ * A function that transforms a given path string into another string.
278
+ * @callback PathGetter
279
+ * @param {string} path - The original path string to be transformed.
280
+ * @returns {string} The transformed path string.
281
+ */
282
+
283
+ /**
284
+ * Data associated with a default HTTP status code response.
285
+ * @typedef {Object} DefaultCodeData
286
+ * @property {PathGetter} pathGetter - A function to retrieve the path for a given input.
287
+ * @property {RouterCodeConfig} data - The configuration data for the default response.
288
+ */
289
+
290
+ /**
291
+ * Options object passed to the fetch handler function.
292
+ * @typedef {Object} FnOptions
293
+ * @property {FetchEvent} event - The fetch event being handled.
294
+ * @property {Request} request - The request object from the fetch event.
295
+ * @property {URL} url - The parsed URL of the request.
296
+ * @property {HttpResponseType} resType - The categorized response type.
297
+ * @property {string} [customMsg] - A custom error message.
298
+ * @property {string} [customPath] - A custom page path.
299
+ * @property {number} code - The HTTP status code.
300
+ */
301
+
302
+ /**
303
+ * Configuration for a specific HTTP status code response handler.
304
+ * @typedef {Object} RouterCodeConfig
305
+ * @property {(ops: FnOptions) => Promise<Response> | Response} fn - The function executed to handle the request.
306
+ * @property {string} msg - The message string to be sent in the response.
307
+ * @property {string} logMsg - The log message to be recorded.
308
+ */
309
+
310
+ /**
311
+ * Represents the raw values returned by a fetch checker.
312
+ * @typedef {Object} FetchCheckerValues
313
+ * @property {number} code - The HTTP status code associated with the fetch result.
314
+ * @property {boolean} needValidation - Whether the router must build a response for the current request.
315
+ * @property {boolean} continueCheck - Whether the next registered fetch listener is still allowed to run.
316
+ * @property {string} [customPath] - Overrides the path served by the router for this request.
317
+ * @property {string} [customMsg] - Overrides the message attached to the generated response.
318
+ */
319
+
320
+ /**
321
+ * The enriched result from a fetch checker, combining base values with additional data.
322
+ * @typedef {FetchCheckerValues & { [id:string]:any}} FetchCheckerResult
323
+ */
324
+
325
+ ///////////////////////////////////////////////////////////////////
326
+
327
+ /**
328
+ * The data payload contained within the message.
329
+ * @typedef {any} MessagePayload
330
+ */
331
+
332
+ /**
333
+ * Represents the structured data format for messages sent via postMessage.
334
+ * @typedef {Object} MessagingData
335
+ * @property {string} type - The identifier for the message type.
336
+ * @property {MessagePayload} [data] - The actual payload of the message.
337
+ */
338
+
339
+ /**
340
+ * A function used to format a reply message into a standard MessagingData object.
341
+ * @callback MessageReplyTemplate
342
+ * @param {string} type - The type identifier for the reply message.
343
+ * @param {MessagePayload} [data] - The payload to be sent in the reply.
344
+ * @returns {MessagingData} The formatted message object.
345
+ */
346
+
347
+ /**
348
+ * A function that handles the actual postMessage call to a specific Client.
349
+ * @callback MessageReplyTo
350
+ * @param {Client} client - The target client to receive the message.
351
+ * @param {string} type - The type identifier for the reply message.
352
+ * @param {MessagePayload} [data] - The payload to be sent in the reply.
353
+ */
354
+
355
+ /**
356
+ * Options accepted by the broadcast reply helpers, such as {@link TinyServiceWorkerEngine.replyToAll}.
357
+ * @typedef {Object} MessageReplyToAllOptions
358
+ * @property {string} type - The type identifier for the reply message.
359
+ * @property {MessagePayload} [data] - The payload to be sent in the reply.
360
+ * @property {ClientQueryOptions} [options] - Additional options for client matching.
361
+ */
362
+
363
+ /**
364
+ * The result of a message broadcast operation.
365
+ * @typedef {Promise<void | readonly (Client | WindowClient)[]>} MessageReplyToAllResponse
366
+ */
367
+
368
+ /**
369
+ * A function that handles the actual postMessage call to all Clients.
370
+ * @callback MessageReplyToAll
371
+ * @param {MessageReplyToAllOptions} options - The options for the reply.
372
+ * @returns {Promise<MessageReplyToAllResponse>} The result of the broadcast.
373
+ */
374
+
375
+ /**
376
+ * A function that handles the actual postMessage call to the message source.
377
+ * @callback MessageReply
378
+ * @param {string} type - The type identifier for the reply message.
379
+ * @param {MessagePayload} [data] - The payload to be sent in the reply.
380
+ */
381
+
382
+ /**
383
+ * An enriched message object containing the event, client information, and utility methods for responding.
384
+ * @typedef {Object} MessageObj
385
+ * @property {ExtendableMessageEvent} event - The original message event.
386
+ * @property {string} clientId - The ID of the client that sent the message.
387
+ * @property {MessagePayload} [data] - The payload sent within the message.
388
+ * @property {MessageReplyTo} replyTo - A function to send a reply to the message source.
389
+ * @property {MessageReplyToAll} replyToAll - A function to send a reply to all clients.
390
+ * @property {MessageReplyTemplate} replyTemplate - A template function to format reply messages.
391
+ * @property {MessageReply} reply - A convenience method to reply to the message source.
392
+ */
393
+
394
+ /**
395
+ * A callback function executed when a registered message type is received.
396
+ * @callback MessageCallback
397
+ * @param {MessageObj} msg - The enriched message data object.
398
+ */
399
+
400
+ ///////////////////////////////////////////////////////////////////
401
+
402
+ /**
403
+ * A record of key-value pairs representing URL parameters extracted from a path.
404
+ * @typedef {Record<string, string>} FetchObjParams
405
+ */
406
+
407
+ /**
408
+ * An enriched data object for fetch plugins.
409
+ * @typedef {Object} FetchObj
410
+ * @property {FetchEvent} event - The original fetch event.
411
+ * @property {Request} request - The intercepted request.
412
+ * @property {URL} url - The parsed URL of the request.
413
+ * @property {FetchObjParams} params - Parameters extracted from the URL (e.g., /:id).
414
+ * @property {MessageReplyTo} replyTo - A function to send a reply to the message source.
415
+ * @property {MessageReplyToAll} replyToAll - A function to send a reply to all clients.
416
+ * @property {MessageReplyTemplate} replyTemplate - A template function to format reply messages.
417
+ * @property {boolean} isSameOrigin - Indicates if the request is same-origin.
418
+ * @property {Error} [error] - An error object if the plugin execution failed.
419
+ */
420
+
421
+ /**
422
+ * Callback function executed when the fetchUrls or fetchRegExp route matches.
423
+ * If it returns a boolean `false`, the interception is considered handled.
424
+ * @callback FetchCallback
425
+ * @param {FetchObj} fetchObj - The enriched request object.
426
+ * @param {FetchCheckerResult} result - The result of the fetch check.
427
+ * @returns {Promise<void>|void} A promise or value indicating if the request was handled.
428
+ */
429
+
430
+ ///////////////////////////////////////////////////////////////////
431
+
432
+ /** @type {ServiceWorkerGlobalScope} The global scope of the Service Worker, aliased for readability. */
433
+ // @ts-ignore
434
+ const sw = self;
435
+
436
+ /**
437
+ * Formats an error and its associated event into a standardized error object.
438
+ * @param {any} err - The error or value to be wrapped.
439
+ * @param {ExtendableEvent} event - The event associated with the error.
440
+ * @returns {{event: ExtendableEvent, error: Error}} An object containing the event and the error.
441
+ */
442
+ const errorMaker = (err, event) => ({
443
+ event,
444
+ error: err instanceof Error ? err : new Error('Unknown Error'),
445
+ });
446
+
447
+ /**
448
+ * The category of an HTTP status code, used to route responses to the correct handler.
449
+ * @typedef {'info'|'success'|'redirect'|'client-error'|'server-error'|'unknown'} HttpResponseType
450
+ */
451
+
452
+ /**
453
+ * Maps an HTTP status code to its corresponding response type category.
454
+ * @param {number} code - The HTTP status code to categorize.
455
+ * @returns {HttpResponseType} The categorized response type.
456
+ */
457
+ const getResType = (code) => {
458
+ // Informational responses
459
+ if (codeIs.info(code)) return 'info';
460
+ // Successful responses
461
+ if (codeIs.success(code)) return 'success';
462
+ // Redirection messages
463
+ if (codeIs.redirect(code)) return 'redirect';
464
+ // Client error responses
465
+ if (codeIs.clientError(code)) return 'client-error';
466
+ // Server error responses
467
+ if (codeIs.serverError(code)) return 'server-error';
468
+ // Unknown error
469
+ return 'unknown';
470
+ };
471
+
472
+ ///////////////////////////////////////////////////////////////////
473
+
474
+ /**
475
+ * A function used to install a plugin into the engine.
476
+ * @template {TinyPluginLayer} Layer
477
+ * @template {string} IdString
478
+ * @template {string} VersionString
479
+ * @template {any[]} Options
480
+ * @typedef {import('../../plugin/TinyPlugin.mjs').TinyPluginInstaller<TinyServiceWorkerEngine, Layer, IdString, VersionString, Options>} SwPluginInstaller
481
+ */
482
+
483
+ /**
484
+ * Represents a plugin instance designed to be integrated into a TinyServiceWorkerEngine.
485
+ * @template {TinyPluginLayer} Layer
486
+ * @template {string} IdString
487
+ * @template {string} VersionString
488
+ * @template {any[]} Options
489
+ * @typedef {TinyPlugin<TinyServiceWorkerEngine, Layer, IdString, VersionString, Options>} TinyServiceWorkerPlugin
490
+ */
491
+
492
+ /**
493
+ * A mock class to simulate NotificationEvent for messaging purposes.
494
+ * @extends Event
495
+ */
496
+ class MockNotificationEvent extends Event {
497
+ /** @type {ExtendableMessageEvent} The wrapped message event. */
498
+ #event;
499
+ /**
500
+ * @type {string}
501
+ * Required by NotificationEvent interface
502
+ */
503
+ #action;
504
+ /** @type {Notification} The mocked notification instance. */
505
+ #notification;
506
+
507
+ get notification() {
508
+ return this.#notification;
509
+ }
510
+
511
+ get action() {
512
+ return this.#action;
513
+ }
514
+
515
+ /**
516
+ * @param {ExtendableMessageEvent} event - The event.
517
+ * @param {any} data - Notification details.
518
+ * @param {string} [action=''] - The action identifier that was clicked.
519
+ */
520
+ constructor(event, data, action = '') {
521
+ super('notificationclick');
522
+ this.#event = event;
523
+ this.#action = action;
524
+ this.#notification = {
525
+ title: data.title,
526
+ body: data.body,
527
+ ...data.options, // Ensures fields like 'date', 'tag' and 'icon' are passed on
528
+ close: () => {
529
+ // Mock implementation of the close method
530
+ },
531
+ };
532
+ }
533
+
534
+ /**
535
+ * Mocking waitUntil to satisfy the ExtendableEvent/NotificationEvent interface.
536
+ * @param {Promise<any>} promise - The promise to keep the event alive.
537
+ * @returns {void}
538
+ */
539
+ waitUntil(promise) {
540
+ return this.#event.waitUntil(promise);
541
+ }
542
+ }
543
+
544
+ /**
545
+ * Manages the lifecycle and execution of modules based on the provided configuration.
546
+ */
547
+ class TinyServiceWorkerEngine extends TinyPlugin.TinyPluginCore {
548
+ static #PUSH_TYPE = utils.PUSH_TYPE;
549
+ static get PUSH_TYPE() {
550
+ return TinyServiceWorkerEngine.#PUSH_TYPE;
551
+ }
552
+
553
+ /**
554
+ * Validates if an event type is a reserved name for the internal lifecycle.
555
+ * @param {string} type - The name of the event to validate.
556
+ * @throws {TypeError} If the event name is in the reserved list.
557
+ */
558
+ static #validateEventType(type) {
559
+ if (type.startsWith('sw:')) {
560
+ throw new TypeError(
561
+ `The event type "${type}" is reserved for internal PWA lifecycle management and cannot be used for Service Worker messaging.`,
562
+ );
563
+ }
564
+ }
565
+
566
+ /**
567
+ * A function used to format a reply message into a standard MessagingData object.
568
+ *
569
+ * @param {string} type - The type identifier for the reply message.
570
+ * @param {MessagePayload} [payload] - The payload to be sent in the reply.
571
+ * @param {boolean} [strict=false] - Whether to enforce the reserved event name rules.
572
+ * @returns {MessagingData} The formatted message object.
573
+ * @throws {TypeError} If type is not a string, payload is not a non-null object,
574
+ * or strict is not a boolean.
575
+ */
576
+ static #replyTemplate(type, payload, strict = false) {
577
+ if (typeof type !== 'string') {
578
+ throw new TypeError(
579
+ `[TinyServiceWorkerEngine] replyTemplate: type must be a string. Received: ${typeof type}`,
580
+ );
581
+ }
582
+ if (
583
+ typeof payload !== 'undefined' &&
584
+ (Array.isArray(payload) || typeof payload !== 'object' || payload === null)
585
+ ) {
586
+ throw new TypeError(
587
+ '[TinyServiceWorkerEngine] replyTemplate: payload must be a non-null object when provided.',
588
+ );
589
+ }
590
+ if (typeof strict !== 'boolean') {
591
+ throw new TypeError(
592
+ `[TinyServiceWorkerEngine] replyTemplate: strict must be a boolean. Received: ${typeof strict}`,
593
+ );
594
+ }
595
+ if (strict) TinyServiceWorkerEngine.#validateEventType(type);
596
+ return { type, data: payload };
597
+ }
598
+
599
+ /**
600
+ * A function used to format a reply message into a standard MessagingData object.
601
+ *
602
+ * @param {string} type - The type identifier for the reply message.
603
+ * @param {MessagePayload} [payload] - The payload to be sent in the reply.
604
+ * @returns {MessagingData} The formatted message object.
605
+ * @throws {TypeError} If type is not a string.
606
+ */
607
+ static replyTemplate(type, payload) {
608
+ return TinyServiceWorkerEngine.#replyTemplate(type, payload, true);
609
+ }
610
+
611
+ /**
612
+ * A function that handles the actual postMessage call to a specific Client.
613
+ *
614
+ * @param {Client} targetSource - The target client to receive the message.
615
+ * @param {string} type - The type identifier for the reply message.
616
+ * @param {MessagePayload} [payload] - The payload to be sent in the reply.
617
+ * @returns {void}
618
+ * @throws {TypeError} If targetSource is invalid or type is not a string.
619
+ */
620
+ static #replyTo(targetSource, type, payload, strict = false) {
621
+ if (!(targetSource instanceof Client)) {
622
+ throw new TypeError(
623
+ `[TinyServiceWorkerEngine] replyTo: targetSource must be a valid Client with a postMessage method.`,
624
+ );
625
+ }
626
+ if (typeof strict !== 'boolean') {
627
+ throw new TypeError(
628
+ `[TinyServiceWorkerEngine] replyTo: strict must be a boolean. Received: ${typeof strict}`,
629
+ );
630
+ }
631
+ targetSource.postMessage(TinyServiceWorkerEngine.#replyTemplate(type, payload, strict));
632
+ }
633
+
634
+ /**
635
+ * A function that handles the actual postMessage call to a specific Client.
636
+ *
637
+ * @param {Client} targetSource - The target client to receive the message.
638
+ * @param {string} type - The type identifier for the reply message.
639
+ * @param {MessagePayload} [payload] - The payload to be sent in the reply.
640
+ * @returns {void}
641
+ * @throws {TypeError} If targetSource is invalid or type is not a string.
642
+ */
643
+ static replyTo(targetSource, type, payload) {
644
+ return TinyServiceWorkerEngine.#replyTo(targetSource, type, payload, true);
645
+ }
646
+
647
+ /**
648
+ * A function that handles the actual postMessage call to all Clients.
649
+ *
650
+ * @param {MessageReplyToAllOptions} ops - The options for the reply.
651
+ * @param {boolean} [strict=false] - Whether to perform strict event type validation.
652
+ * @returns {Promise<MessageReplyToAllResponse>} The result of the broadcast.
653
+ * @throws {TypeError} If ops is not a valid object or strict is not a boolean.
654
+ */
655
+ static async #replyToAll(ops, strict = false) {
656
+ if (typeof ops !== 'object' || ops === null || Array.isArray(ops)) {
657
+ throw new TypeError('[TinyServiceWorkerEngine] replyToAll: ops must be a non-null object.');
658
+ }
659
+ if (typeof strict !== 'boolean') {
660
+ throw new TypeError(
661
+ `[TinyServiceWorkerEngine] replyToAll: strict must be a boolean. Received: ${typeof strict}`,
662
+ );
663
+ }
664
+
665
+ const { type, data, options = { type: 'window', includeUncontrolled: true } } = ops;
666
+
667
+ if (typeof type !== 'string' || type.trim() === '') {
668
+ throw new TypeError(
669
+ '[TinyServiceWorkerEngine] replyToAll: ops.type must be a non-empty string.',
670
+ );
671
+ }
672
+ if (
673
+ typeof data !== 'undefined' &&
674
+ (Array.isArray(data) || typeof data !== 'object' || data === null)
675
+ ) {
676
+ throw new TypeError(
677
+ '[TinyServiceWorkerEngine] replyToAll: ops.data must be a non-null object when provided.',
678
+ );
679
+ }
680
+ if (typeof options !== 'object' || options === null || Array.isArray(options)) {
681
+ throw new TypeError(
682
+ '[TinyServiceWorkerEngine] replyToAll: ops.options must be a non-null object when provided.',
683
+ );
684
+ }
685
+
686
+ return sw.clients.matchAll(options).then((clientList) =>
687
+ clientList.forEach((client) => {
688
+ TinyServiceWorkerEngine.#replyTo(client, type, data, strict);
689
+ }),
690
+ );
691
+ }
692
+
693
+ /**
694
+ * A function that handles the actual postMessage call to all Clients.
695
+ *
696
+ * @param {MessageReplyToAllOptions} ops - The options for the reply.
697
+ * @returns {Promise<MessageReplyToAllResponse>} The result of the broadcast.
698
+ */
699
+ static async replyToAll(ops) {
700
+ return TinyServiceWorkerEngine.#replyToAll(ops, true);
701
+ }
702
+
703
+ /**
704
+ * Checks if the provided Request object is a navigation request.
705
+ * @param {Request} req - The request to evaluate.
706
+ * @returns {boolean} True if the request mode is 'navigate', false otherwise.
707
+ */
708
+ static isNavigate(req) {
709
+ return req.mode === 'navigate';
710
+ }
711
+
712
+ /**
713
+ * Determines whether the origin of a given Request or URL matches the origin of the current Service Worker context.
714
+ * @param {Request|URL} data - The request or URL to check.
715
+ * @returns {boolean} True if the origins match, false otherwise.
716
+ */
717
+ static isSameOrigin(data) {
718
+ // 1. Parse the request URL into a URL object to access its specific components
719
+ const reqUrl = data instanceof Request ? new URL(data.url) : data;
720
+
721
+ // 2. Compare the origin of the request with the origin of the Service Worker itself
722
+ // self.location.origin provides the protocol, domain, and port of the Service Worker
723
+ return reqUrl.origin === self.location.origin;
724
+ }
725
+
726
+ /**
727
+ * Retrieves the configuration for a specific HTTP status code.
728
+ * @param {number} c - The HTTP status code.
729
+ * @returns {RouterCodeConfig} The configuration for the given code.
730
+ * @throws {TypeError} If c is not a number.
731
+ */
732
+ getCodeCfg(c) {
733
+ if (typeof c !== 'number') throw new TypeError('Code must be a number.');
734
+ /** @type {RouterCodeConfig|undefined} */
735
+ let routerCodeCfg;
736
+
737
+ // 1. Check user-defined codes in the Map
738
+ routerCodeCfg = this.#config.fetch.router.codes.get(c);
739
+
740
+ // 2. If not found, check for dynamic default configurations
741
+ // @ts-ignore
742
+ if (!routerCodeCfg) routerCodeCfg = this.#defaultCode[c].data;
743
+
744
+ // 3. If still not found, fallback to the 'unknown' configuration
745
+ if (!routerCodeCfg) {
746
+ routerCodeCfg = this.createFetchRes({
747
+ isError: true,
748
+ msg: this.#globalMsgCode.unknown.msg,
749
+ logMsg: this.#globalMsgCode.unknown.logMsg,
750
+ pathGetter: () => this.globalPathGetter(this.#globalMsgCode.unknown.path),
751
+ });
752
+ }
753
+
754
+ return { ...routerCodeCfg };
755
+ }
756
+
757
+ /**
758
+ * Sets the SPA mode configuration.
759
+ * @param {boolean} value - The value to set for spaMode.
760
+ * @throws {TypeError} If value is not a boolean.
761
+ */
762
+ set spaMode(value) {
763
+ if (typeof value !== 'boolean') {
764
+ throw new TypeError(`Invalid type for "spaMode": expected boolean, got ${typeof value}`);
765
+ }
766
+ this.#config.spaMode = value;
767
+ }
768
+
769
+ /**
770
+ * Gets the current SPA mode configuration.
771
+ * @returns {boolean} The current spaMode value.
772
+ */
773
+ get spaMode() {
774
+ return this.#config.spaMode;
775
+ }
776
+
777
+ /**
778
+ * Gets a deep clone of the current lifecycle configuration.
779
+ *
780
+ * @returns {LifecycleOptions} A deep cloned copy of the lifecycle configuration.
781
+ */
782
+ get lifecycle() {
783
+ return TinyCloner.clone(this.#config.lifecycle);
784
+ }
785
+
786
+ /**
787
+ * Merges the provided properties into the current lifecycle configuration.
788
+ *
789
+ * The operation is a **merge**, not a replacement: omitted keys keep their
790
+ * current value. This is required because the internal validation is strict,
791
+ * so a partial patch such as `{ strategyOrder: 'before' }` must not erase the
792
+ * install and activate strategies.
793
+ *
794
+ * @param {Partial<LifecycleOptions>} value - The lifecycle properties to merge.
795
+ * @throws {TypeError} If `value` is not a valid partial {@link LifecycleOptions}.
796
+ */
797
+ set lifecycle(value) {
798
+ TinyServiceWorkerEngine.#assertLifecycle(value);
799
+ this.#updateConfig({ lifecycle: value });
800
+ }
801
+
802
+ /**
803
+ * Returns the global path based on the current SPA mode configuration.
804
+ * @param {string} path - The original path.
805
+ * @returns {string} The path adjusted for SPA mode or the original path.
806
+ * @throws {TypeError} If path is not a non-empty string.
807
+ */
808
+ globalPathGetter(path) {
809
+ if (typeof path !== 'string' || path.trim() === '') {
810
+ throw new TypeError(
811
+ '[TinyServiceWorkerEngine] globalPathGetter: path must be a non-empty string.',
812
+ );
813
+ }
814
+ return !this.#config.spaMode ? path : this.#globalMsgCode.spaPath;
815
+ }
816
+
817
+ /** @type {Map<string, ApiHandlerCallback>} The registered API handlers, keyed by call type. */
818
+ #apiHandlers = new Map();
819
+
820
+ /** @type {Map<string, SyncCallback>} The registered sync listeners, keyed by sync tag. */
821
+ #syncListeners = new Map();
822
+
823
+ /** @type {Map<string, LifecycleCallback>} The registered install listeners, keyed by tag. */
824
+ #installListeners = new Map();
825
+
826
+ /** @type {Map<string, LifecycleCallback>} The registered activate listeners, keyed by tag. */
827
+ #activateListeners = new Map();
828
+
829
+ /** @type {Map<string, {resolve: (value: any) => void, reject: (reason: Error) => void, timer: NodeJS.Timeout}>} The in-flight API requests, keyed by correlation id. */
830
+ #pendingRequests = new Map();
831
+
832
+ /** @type {GlobalMsgCode} The active global message table used to build responses. */
833
+ #globalMsgCode = {
834
+ spaPath: '/index.html',
835
+ unknown: {
836
+ path: '/500.html',
837
+ msg: 'Unknown',
838
+ logMsg: 'Unknown route error',
839
+ },
840
+ 200: {
841
+ path: null,
842
+ msg: 'Success',
843
+ logMsg: 'Valid route',
844
+ },
845
+ 404: {
846
+ path: '/404.html',
847
+ msg: 'Not Found',
848
+ logMsg: 'Route not found',
849
+ },
850
+ 500: {
851
+ path: '/500.html',
852
+ msg: 'Internal Server Error',
853
+ logMsg: 'Route server error',
854
+ },
855
+ };
856
+
857
+ /** @type {{ 200: DefaultCodeData, 404: DefaultCodeData, 500: DefaultCodeData }} The built-in response handlers, keyed by HTTP status code. */
858
+ #defaultCode = {
859
+ 200: {
860
+ pathGetter: (path) => this.globalPathGetter(path),
861
+ data: {
862
+ fn: (ops) =>
863
+ this.fetchFn(
864
+ this.#globalMsgCode[200].msg,
865
+ this.#globalMsgCode[200].logMsg,
866
+ this.#defaultCode[200].pathGetter,
867
+ ops,
868
+ ),
869
+ msg: this.#globalMsgCode[200].msg,
870
+ logMsg: this.#globalMsgCode[200].logMsg,
871
+ },
872
+ },
873
+ 404: {
874
+ pathGetter: () => this.globalPathGetter(this.#globalMsgCode[404].path),
875
+ data: {
876
+ fn: (ops) =>
877
+ this.fetchErrorFn(
878
+ this.#globalMsgCode[404].msg,
879
+ this.#globalMsgCode[404].logMsg,
880
+ this.#defaultCode[404].pathGetter,
881
+ ops,
882
+ ),
883
+ msg: this.#globalMsgCode[404].msg,
884
+ logMsg: this.#globalMsgCode[404].logMsg,
885
+ },
886
+ },
887
+ 500: {
888
+ pathGetter: () => this.globalPathGetter(this.#globalMsgCode[500].path),
889
+ data: {
890
+ fn: (ops) =>
891
+ this.fetchErrorFn(
892
+ this.#globalMsgCode[500].msg,
893
+ this.#globalMsgCode[500].logMsg,
894
+ this.#defaultCode[500].pathGetter,
895
+ ops,
896
+ ),
897
+ msg: this.#globalMsgCode[500].msg,
898
+ logMsg: this.#globalMsgCode[500].logMsg,
899
+ },
900
+ },
901
+ };
902
+
903
+ /**
904
+ * Handles a successful fetch request.
905
+ * @param {string} msg - The message to be logged.
906
+ * @param {string} logMsg - The log message to be recorded.
907
+ * @param {string|PathGetter} pathGetter - The path to fetch.
908
+ * @param {FnOptions} options - The options for the fetch operation.
909
+ * @returns {Promise<Response>} A promise that resolves to the Response object.
910
+ */
911
+ async fetchFn(msg, logMsg, pathGetter, options) {
912
+ if (typeof msg !== 'string' || typeof logMsg !== 'string') {
913
+ throw new TypeError('[TinyServiceWorkerEngine] fetchFn: msg and logMsg must be strings.');
914
+ }
915
+ if (typeof pathGetter !== 'string' && typeof pathGetter !== 'function') {
916
+ throw new TypeError(
917
+ '[TinyServiceWorkerEngine] fetchFn: pathGetter must be a string or a function.',
918
+ );
919
+ }
920
+ if (typeof options !== 'object' || options === null || !(options.url instanceof URL)) {
921
+ throw new TypeError('[TinyServiceWorkerEngine] fetchFn: options.url must be a valid URL.');
922
+ }
923
+
924
+ const { url, code, request, customPath } = options;
925
+ const path =
926
+ typeof customPath === 'string'
927
+ ? customPath
928
+ : typeof pathGetter === 'string'
929
+ ? pathGetter
930
+ : pathGetter(url.toString());
931
+
932
+ this.log('info', `${code} - ${logMsg}: ${url.pathname}`);
933
+ try {
934
+ const res = await fetch(path === url.toString() ? request : path);
935
+ return res;
936
+ } catch (err) {
937
+ return this.getCodeCfg(500).fn(options);
938
+ }
939
+ }
940
+
941
+ /**
942
+ * Handles a failed fetch request by simulating an error page.
943
+ * @param {string} msg - The message to be logged.
944
+ * @param {string} logMsg - The log message to be recorded.
945
+ * @param {string|PathGetter} pathGetter - The path to fetch.
946
+ * @param {FnOptions} options - The options for the fetch operation.
947
+ * @returns {Promise<Response>} A promise that resolves to the error Response object.
948
+ */
949
+ async fetchErrorFn(msg, logMsg, pathGetter, options) {
950
+ if (typeof msg !== 'string' || typeof logMsg !== 'string') {
951
+ throw new TypeError(
952
+ '[TinyServiceWorkerEngine] fetchErrorFn: msg and logMsg must be strings.',
953
+ );
954
+ }
955
+ if (typeof pathGetter !== 'string' && typeof pathGetter !== 'function') {
956
+ throw new TypeError(
957
+ '[TinyServiceWorkerEngine] fetchErrorFn: pathGetter must be a string or a function.',
958
+ );
959
+ }
960
+ if (typeof options !== 'object' || options === null || !(options.url instanceof URL)) {
961
+ throw new TypeError(
962
+ '[TinyServiceWorkerEngine] fetchErrorFn: options.url must be a valid URL.',
963
+ );
964
+ }
965
+
966
+ const { url, code, event, request, resType, customMsg, customPath } = options;
967
+ const path =
968
+ typeof customPath === 'string'
969
+ ? customPath
970
+ : typeof pathGetter === 'string'
971
+ ? pathGetter
972
+ : pathGetter(url.toString());
973
+
974
+ this.log('warn', `${code} - ${logMsg}: ${url.pathname}`);
975
+ const errMsg = typeof customMsg === 'string' ? customMsg : msg;
976
+
977
+ this.emit('fetchError', {
978
+ event,
979
+ request,
980
+ error: new Error(`${code} ${errMsg}`),
981
+ url,
982
+ resType,
983
+ });
984
+
985
+ // Simulating Apache2 ErrorDocument behavior by serving path.html with a status
986
+ try {
987
+ const res = await fetch(path);
988
+ return new Response(res.body, {
989
+ status: code,
990
+ statusText: errMsg,
991
+ headers: res.headers,
992
+ });
993
+ } catch {
994
+ return new Response(this.#globalMsgCode[500].msg, { status: 500 });
995
+ }
996
+ }
997
+
998
+ /**
999
+ * Creates a configuration for a fetch response.
1000
+ * @param {Object} ops - The creation options.
1001
+ * @param {boolean} ops.isError - Indicates if it is an error response.
1002
+ * @param {string} ops.msg - The message to be sent.
1003
+ * @param {string} ops.logMsg - The log message.
1004
+ * @param {string|PathGetter} ops.pathGetter - The path to use for the response.
1005
+ * @returns {RouterCodeConfig} The newly created configuration.
1006
+ * @throws {TypeError} If any property is missing or has the wrong type.
1007
+ */
1008
+ createFetchRes({ isError, msg, logMsg, pathGetter }) {
1009
+ if (typeof isError !== 'boolean') {
1010
+ throw new TypeError('[TinyServiceWorkerEngine] createFetchRes: isError must be a boolean.');
1011
+ }
1012
+ if (typeof msg !== 'string') {
1013
+ throw new TypeError('[TinyServiceWorkerEngine] createFetchRes: msg must be a string.');
1014
+ }
1015
+ if (typeof logMsg !== 'string') {
1016
+ throw new TypeError('[TinyServiceWorkerEngine] createFetchRes: logMsg must be a string.');
1017
+ }
1018
+ if (typeof pathGetter !== 'string' && typeof pathGetter !== 'function') {
1019
+ throw new TypeError(
1020
+ '[TinyServiceWorkerEngine] createFetchRes: pathGetter must be a string or a function.',
1021
+ );
1022
+ }
1023
+
1024
+ return {
1025
+ fn: (ops) =>
1026
+ isError
1027
+ ? this.fetchErrorFn(msg, logMsg, pathGetter, ops)
1028
+ : this.fetchFn(msg, logMsg, pathGetter, ops),
1029
+ msg,
1030
+ logMsg,
1031
+ };
1032
+ }
1033
+
1034
+ /**
1035
+ * The push router. Kept outside `#config` on purpose: `get config()` deep
1036
+ * clones the configuration, and a class instance would not survive the clone.
1037
+ * @type {TinyPushRouter}
1038
+ */
1039
+ #pushRouter = new TinyPushRouter();
1040
+
1041
+ /**
1042
+ * Replaces the push router.
1043
+ * @param {TinyPushRouter} router - The new router.
1044
+ * @throws {TypeError} If `router` is not a TinyPushRouter.
1045
+ */
1046
+ set pushRouter(router) {
1047
+ if (!(router instanceof TinyPushRouter)) {
1048
+ throw new TypeError('[TinyServiceWorkerEngine] pushRouter must be a TinyPushRouter.');
1049
+ }
1050
+ this.#pushRouter = router;
1051
+ }
1052
+
1053
+ /** @returns {TinyPushRouter} The active push router. */
1054
+ get pushRouter() {
1055
+ return this.#pushRouter;
1056
+ }
1057
+
1058
+ /**
1059
+ * The internal configuration state of the engine.
1060
+ * @type {ServiceWorkerSettings}
1061
+ */
1062
+ #config = {
1063
+ spaMode: false,
1064
+ lifecycle: {
1065
+ installStrategy: 'immediate',
1066
+ activateStrategy: 'immediate',
1067
+ installOrder: 'after',
1068
+ activateOrder: 'after',
1069
+ },
1070
+ push: {
1071
+ enabled: true,
1072
+ vapidPublicKey: '',
1073
+ subscribeEndpoint: '',
1074
+ defaultIcon: '/icons/notification-192.png',
1075
+ defaultBadge: '/icons/badge-72.png',
1076
+ defaultUrl: '/',
1077
+ },
1078
+ sync: {
1079
+ enabled: true,
1080
+ },
1081
+ fetch: {
1082
+ enabled: true,
1083
+ router: {
1084
+ enabled: false,
1085
+ codes: new Map(),
1086
+ },
1087
+ },
1088
+ messaging: {
1089
+ enabled: true,
1090
+ },
1091
+ };
1092
+
1093
+ /**
1094
+ * Sets the engine configuration.
1095
+ * @param {ServiceWorkerSettings} config - The new configuration to apply.
1096
+ * @throws {TypeError} If the configuration does not meet the minimum requirements.
1097
+ */
1098
+ set config(config) {
1099
+ this.#updateConfig(config, true);
1100
+ }
1101
+
1102
+ /**
1103
+ * Gets the current engine configuration.
1104
+ * Returns a deep clone to prevent external mutation of the internal state.
1105
+ * @returns {ServiceWorkerSettings} A deep cloned copy of the configuration.
1106
+ */
1107
+ get config() {
1108
+ return TinyCloner.clone(this.#config);
1109
+ }
1110
+
1111
+ /**
1112
+ * A map containing registered message type listeners.
1113
+ * @type {Map<string, MessageCallback>}
1114
+ */
1115
+ #messages = new Map();
1116
+
1117
+ /**
1118
+ * A set of event names that must be ignored when received from the browser.
1119
+ * Useful to avoid collisions with third-party Service Workers that share the same scope.
1120
+ * @type {Set<string>}
1121
+ */
1122
+ #eventBlacklist = new Set();
1123
+
1124
+ /**
1125
+ * A map containing registered fetch RegExp listeners.
1126
+ * @type {Map<string, FetchCallback>}
1127
+ */
1128
+ #fetchRegExp = new Map();
1129
+
1130
+ /**
1131
+ * A map containing registered fetch URL listeners.
1132
+ * @type {Map<string, FetchCallback>}
1133
+ */
1134
+ #fetchUrls = new Map();
1135
+
1136
+ /**
1137
+ * A map containing the global fetch tracking listeners.
1138
+ * @type {Map<string, FetchCallback>}
1139
+ */
1140
+ #fetchGlobal = new Map();
1141
+
1142
+ /**
1143
+ * Flag indicating if the engine has been initialized.
1144
+ * @type {boolean}
1145
+ */
1146
+ #started = false;
1147
+
1148
+ /**
1149
+ * Indicates if the engine has been initialized.
1150
+ * @returns {boolean} True if started, false otherwise.
1151
+ */
1152
+ get started() {
1153
+ return this.#started;
1154
+ }
1155
+
1156
+ /**
1157
+ * Performs rigorous deep validation of the configuration object.
1158
+ * @param {ServiceWorkerSettings | Partial<PartialServiceWorkerSettings>} config - The configuration object to validate.
1159
+ * @param {boolean} [strict=false] - If true, ensures all properties defined in the typedef are present.
1160
+ * @throws {TypeError} If any property is of the wrong type or is missing when in strict mode.
1161
+ */
1162
+ #validateConfig(config, strict = false) {
1163
+ if (typeof config !== 'object' || config === null) {
1164
+ throw new TypeError('Configuration must be a non-null object.');
1165
+ }
1166
+
1167
+ /**
1168
+ * Helper to validate existence and type.
1169
+ * @template {Record<any, any>} T
1170
+ * @param {T} obj - The object containing the property.
1171
+ * @param {string} key - The property name.
1172
+ * @param {string} type - The expected typeof result.
1173
+ * @param {string} context - Context for the error message.
1174
+ */
1175
+ const validateField = (obj, key, type, context) => {
1176
+ const value = obj[key];
1177
+ if (strict && value === undefined) {
1178
+ throw new TypeError(`Missing required property: "${context}.${key}"`);
1179
+ }
1180
+ if (value !== undefined && typeof value !== type) {
1181
+ throw new TypeError(
1182
+ `Invalid type for "${context}.${key}": expected ${type}, got ${typeof value}`,
1183
+ );
1184
+ }
1185
+ };
1186
+
1187
+ validateField(config, 'spaMode', 'boolean', 'root');
1188
+
1189
+ // Deep validation for 'fetch' configuration
1190
+ if (config.fetch !== undefined) {
1191
+ if (typeof config.fetch !== 'object' || config.fetch === null) {
1192
+ throw new TypeError('Fetch configuration must be a non-null object.');
1193
+ }
1194
+ validateField(config.fetch, 'enabled', 'boolean', 'fetch');
1195
+
1196
+ if (config.fetch.router !== undefined) {
1197
+ if (typeof config.fetch.router !== 'object' || config.fetch.router === null) {
1198
+ throw new TypeError('Fetch router configuration must be a non-null object.');
1199
+ }
1200
+ validateField(config.fetch.router, 'enabled', 'boolean', 'fetch.router');
1201
+
1202
+ // Deep validation for 'router.codes' Map
1203
+ if (config.fetch.router.codes !== undefined) {
1204
+ if (!(config.fetch.router.codes instanceof Map)) {
1205
+ throw new TypeError(
1206
+ '[TinyServiceWorkerEngine] validateConfig: fetch.router.codes must be a Map.',
1207
+ );
1208
+ }
1209
+
1210
+ for (const [code, cfg] of config.fetch.router.codes.entries()) {
1211
+ // Validate Map Key
1212
+ if (typeof code !== 'number') {
1213
+ throw new TypeError(
1214
+ `[TinyServiceWorkerEngine] validateConfig: Map key must be a number. Received: ${typeof code}`,
1215
+ );
1216
+ }
1217
+
1218
+ // Validate Map Value (RouterCodeConfig)
1219
+ if (typeof cfg !== 'object' || cfg === null) {
1220
+ throw new TypeError(
1221
+ `[TinyServiceWorkerEngine] validateConfig: Value for code ${code} must be an object.`,
1222
+ );
1223
+ }
1224
+
1225
+ if (typeof cfg.fn !== 'function') {
1226
+ throw new TypeError(
1227
+ `[TinyServiceWorkerEngine] validateConfig: cfg.fn for code ${code} must be a function.`,
1228
+ );
1229
+ }
1230
+ if (typeof cfg.msg !== 'string') {
1231
+ throw new TypeError(
1232
+ `[TinyServiceWorkerEngine] validateConfig: cfg.msg for code ${code} must be a string.`,
1233
+ );
1234
+ }
1235
+ if (typeof cfg.logMsg !== 'string') {
1236
+ throw new TypeError(
1237
+ `[TinyServiceWorkerEngine] validateConfig: cfg.logMsg for code ${code} must be a string.`,
1238
+ );
1239
+ }
1240
+ }
1241
+ } else if (strict) {
1242
+ throw new TypeError('Missing required property: "fetch.router.codes"');
1243
+ }
1244
+ } else if (strict) {
1245
+ throw new TypeError('Missing required property: "fetch.router"');
1246
+ }
1247
+ } else if (strict) {
1248
+ throw new TypeError('Missing required property: "fetch"');
1249
+ }
1250
+
1251
+ if (config.lifecycle !== undefined) {
1252
+ TinyServiceWorkerEngine.#assertLifecycle(config.lifecycle);
1253
+
1254
+ if (strict) {
1255
+ for (const [key] of LIFECYCLE_SPEC) {
1256
+ if (/** @type {Record<string, unknown>} */ (config.lifecycle)[key] === undefined) {
1257
+ throw new TypeError(`Missing required property: "lifecycle.${key}"`);
1258
+ }
1259
+ }
1260
+ }
1261
+ } else if (strict) {
1262
+ throw new TypeError('Missing required property: "lifecycle"');
1263
+ }
1264
+
1265
+ if (config.sync !== undefined) {
1266
+ if (typeof config.sync !== 'object' || config.sync === null) {
1267
+ throw new TypeError('Sync configuration must be a non-null object.');
1268
+ }
1269
+ validateField(config.sync, 'enabled', 'boolean', 'sync');
1270
+ } else if (strict) {
1271
+ throw new TypeError('Missing required property: "sync"');
1272
+ }
1273
+
1274
+ // Deep validation for 'messaging' configuration
1275
+ if (config.messaging !== undefined) {
1276
+ if (typeof config.messaging !== 'object' || config.messaging === null) {
1277
+ throw new TypeError('Messaging configuration must be a non-null object.');
1278
+ }
1279
+ validateField(config.messaging, 'enabled', 'boolean', 'messaging');
1280
+ } else if (strict) {
1281
+ throw new TypeError('Missing required property: "messaging"');
1282
+ }
1283
+ }
1284
+
1285
+ /**
1286
+ * Updates the internal configuration.
1287
+ * @param {Partial<PartialServiceWorkerSettings>} config - The partial configuration to apply.
1288
+ * @param {boolean} [forceFullValidation=false] - Whether to force full validation.
1289
+ */
1290
+ #updateConfig(config, forceFullValidation = false) {
1291
+ this.#validateConfig(config, forceFullValidation);
1292
+
1293
+ // 1. Prepare Messaging configuration
1294
+ const newMessaging = config.messaging
1295
+ ? { ...this.#config.messaging, ...config.messaging }
1296
+ : this.#config.messaging;
1297
+
1298
+ // 2. Prepare Fetch Router configuration
1299
+ let newRouter = this.#config.fetch.router;
1300
+ if (config.fetch?.router) {
1301
+ // Merge the router properties
1302
+ newRouter = {
1303
+ ...this.#config.fetch.router,
1304
+ ...config.fetch.router,
1305
+ };
1306
+
1307
+ // If the router contains a Map of codes, create a deep clone of the Map and its values
1308
+ if (newRouter.codes instanceof Map) {
1309
+ newRouter.codes = new Map(
1310
+ Array.from(newRouter.codes, ([code, cfg]) => [
1311
+ code,
1312
+ { ...cfg }, // Clone of the RouterCodeConfig object
1313
+ ]),
1314
+ );
1315
+ }
1316
+ }
1317
+
1318
+ // 3. Prepare Fetch configuration
1319
+ const newFetch = config.fetch
1320
+ ? {
1321
+ ...this.#config.fetch,
1322
+ ...config.fetch,
1323
+ router: newRouter,
1324
+ }
1325
+ : this.#config.fetch;
1326
+
1327
+ // 4. Prepare Fetch configuration
1328
+ const newPush = config.push
1329
+ ? {
1330
+ ...this.#config.push,
1331
+ ...config.push,
1332
+ }
1333
+ : this.#config.push;
1334
+
1335
+ // 5. Prepare Lifecycle configuration
1336
+ const newLifecycle = config.lifecycle
1337
+ ? { ...this.#config.lifecycle, ...config.lifecycle }
1338
+ : this.#config.lifecycle;
1339
+
1340
+ // 6. Prepare Sync configuration
1341
+ const newSync = config.sync ? { ...this.#config.sync, ...config.sync } : this.#config.sync;
1342
+
1343
+ // 7. Apply to the instance's private state
1344
+ this.#config = {
1345
+ spaMode: config.spaMode ?? this.#config.spaMode,
1346
+ lifecycle: newLifecycle,
1347
+ fetch: newFetch,
1348
+ push: newPush,
1349
+ sync: newSync,
1350
+ messaging: newMessaging,
1351
+ };
1352
+
1353
+ // Final validation to ensure the merge did not break type rules
1354
+ this.#validateConfig(this.#config, true);
1355
+ }
1356
+
1357
+ /**
1358
+ * Initializes a new instance of the TinyServiceWorkerEngine with the provided configuration and logger settings.
1359
+ * @param {Partial<PartialServiceWorkerSettings>} config - The configuration to apply.
1360
+ * @param {Object} [lgConfig] - Configuration options for the instance.
1361
+ * @param {boolean} [lgConfig.debugMode=false] - Whether to enable internal debug logging.
1362
+ * @param {boolean} [lgConfig.useLogColors=false] - Whether to enable log color support.
1363
+ * @param {Partial<Console>} [lgConfig.logger=console] - A custom logger object.
1364
+ * @throws {TypeError} If the configuration does not meet the minimum requirements.
1365
+ */
1366
+ constructor(config = {}, lgConfig = {}) {
1367
+ super({
1368
+ logCfg: {
1369
+ id: '[_main_class_TinySW-Engine_reset_]',
1370
+ logger: lgConfig.logger ?? console,
1371
+ debugMode: lgConfig.debugMode ?? false,
1372
+ useLogColors: lgConfig.useLogColors ?? false,
1373
+ },
1374
+ });
1375
+ this.#updateConfig(config);
1376
+ }
1377
+
1378
+ /**
1379
+ * Validates that a listener identifier is a non-empty string.
1380
+ * @param {unknown} type - The value to validate.
1381
+ * @param {string} context - The method name used in the error message.
1382
+ * @returns {void}
1383
+ * @throws {TypeError} If type is not a non-empty string.
1384
+ */
1385
+ static #assertListenerType(type, context) {
1386
+ if (typeof type !== 'string' || type.trim() === '') {
1387
+ throw new TypeError(`[TinyServiceWorkerEngine] ${context}: type must be a non-empty string.`);
1388
+ }
1389
+ }
1390
+
1391
+ /**
1392
+ * Validates that a listener callback is a function.
1393
+ * @param {unknown} callback - The value to validate.
1394
+ * @param {string} context - The method name used in the error message.
1395
+ * @returns {void}
1396
+ * @throws {TypeError} If callback is not a function.
1397
+ */
1398
+ static #assertListenerCallback(callback, context) {
1399
+ if (typeof callback !== 'function') {
1400
+ throw new TypeError(`[TinyServiceWorkerEngine] ${context}: callback must be a function.`);
1401
+ }
1402
+ }
1403
+
1404
+ /**
1405
+ * Gets the number of registered fetch RegExp listeners.
1406
+ * @returns {number} The count of registered RegExp listeners.
1407
+ */
1408
+ get fetchRegExpSize() {
1409
+ return this.#fetchRegExp.size;
1410
+ }
1411
+
1412
+ /**
1413
+ * Adds a fetch RegExp listener.
1414
+ * @param {string} type - The identifier for the fetch type.
1415
+ * @param {FetchCallback} callback - The callback to execute.
1416
+ */
1417
+ addFetchRegExpListener(type, callback) {
1418
+ TinyServiceWorkerEngine.#assertListenerType(type, 'addFetchRegExpListener');
1419
+ TinyServiceWorkerEngine.#assertListenerCallback(callback, 'addFetchRegExpListener');
1420
+ this.#fetchRegExp.set(type, callback);
1421
+ }
1422
+
1423
+ /**
1424
+ * Removes a fetch RegExp listener.
1425
+ * @param {string} type - The identifier for the fetch type.
1426
+ * @returns {boolean} True if the listener was removed, false otherwise.
1427
+ */
1428
+ removeFetchRegExpListener(type) {
1429
+ TinyServiceWorkerEngine.#assertListenerType(type, 'removeFetchRegExpListener');
1430
+ return this.#fetchRegExp.delete(type);
1431
+ }
1432
+
1433
+ /**
1434
+ * Retrieves a fetch RegExp listener.
1435
+ * @param {string} type - The identifier for the fetch type.
1436
+ * @returns {FetchCallback|undefined} The listener, or undefined if not found.
1437
+ */
1438
+ getFetchRegExpListener(type) {
1439
+ TinyServiceWorkerEngine.#assertListenerType(type, 'getFetchRegExpListener');
1440
+ return this.#fetchRegExp.get(type);
1441
+ }
1442
+
1443
+ /**
1444
+ * Checks if a fetch RegExp listener exists.
1445
+ * @param {string} type - The identifier for the fetch type.
1446
+ * @returns {boolean} True if the listener exists, false otherwise.
1447
+ */
1448
+ hasFetchRegExp(type) {
1449
+ TinyServiceWorkerEngine.#assertListenerType(type, 'hasFetchRegExp');
1450
+ return this.#fetchRegExp.has(type);
1451
+ }
1452
+
1453
+ /**
1454
+ * Clears all registered fetch RegExp listeners.
1455
+ * @returns {void}
1456
+ */
1457
+ clearFetchRegExps() {
1458
+ return this.#fetchRegExp.clear();
1459
+ }
1460
+
1461
+ /**
1462
+ * Gets the number of registered fetch URL listeners.
1463
+ * @returns {number} The count of registered URL listeners.
1464
+ */
1465
+ get fetchUrlSize() {
1466
+ return this.#fetchUrls.size;
1467
+ }
1468
+
1469
+ /**
1470
+ * Adds a fetch URL listener.
1471
+ * @param {string} type - The identifier for the fetch type.
1472
+ * @param {FetchCallback} callback - The callback to execute.
1473
+ */
1474
+ addFetchUrlListener(type, callback) {
1475
+ TinyServiceWorkerEngine.#assertListenerType(type, 'addFetchUrlListener');
1476
+ TinyServiceWorkerEngine.#assertListenerCallback(callback, 'addFetchUrlListener');
1477
+ this.#fetchUrls.set(type, callback);
1478
+ }
1479
+
1480
+ /**
1481
+ * Removes a fetch URL listener.
1482
+ * @param {string} type - The identifier for the fetch type.
1483
+ * @returns {boolean} True if the listener was removed, false otherwise.
1484
+ */
1485
+ removeFetchUrlListener(type) {
1486
+ TinyServiceWorkerEngine.#assertListenerType(type, 'removeFetchUrlListener');
1487
+ return this.#fetchUrls.delete(type);
1488
+ }
1489
+
1490
+ /**
1491
+ * Retrieves a fetch URL listener.
1492
+ * @param {string} type - The identifier for the fetch type.
1493
+ * @returns {FetchCallback|undefined} The listener, or undefined if not found.
1494
+ */
1495
+ getFetchUrlListener(type) {
1496
+ TinyServiceWorkerEngine.#assertListenerType(type, 'getFetchUrlListener');
1497
+ return this.#fetchUrls.get(type);
1498
+ }
1499
+
1500
+ /**
1501
+ * Checks if a fetch URL listener exists.
1502
+ * @param {string} type - The identifier for the fetch type.
1503
+ * @returns {boolean} True if the listener exists, false otherwise.
1504
+ */
1505
+ hasFetchUrl(type) {
1506
+ TinyServiceWorkerEngine.#assertListenerType(type, 'hasFetchUrl');
1507
+ return this.#fetchUrls.has(type);
1508
+ }
1509
+
1510
+ /**
1511
+ * Clears all registered fetch URL listeners.
1512
+ * @returns {void}
1513
+ */
1514
+ clearFetchUrls() {
1515
+ return this.#fetchUrls.clear();
1516
+ }
1517
+
1518
+ /**
1519
+ * Gets the number of registered global fetch tracking listeners.
1520
+ * @returns {number} The count of registered tracking listeners.
1521
+ */
1522
+ get fetchGlobalSize() {
1523
+ return this.#fetchGlobal.size;
1524
+ }
1525
+
1526
+ /**
1527
+ * Adds a global fetch tracking listener.
1528
+ * @param {string} type - The identifier for the tracking type.
1529
+ * @param {FetchCallback} callback - The callback function to be executed.
1530
+ */
1531
+ addFetchGlobalListener(type, callback) {
1532
+ TinyServiceWorkerEngine.#assertListenerType(type, 'addFetchGlobalListener');
1533
+ TinyServiceWorkerEngine.#assertListenerCallback(callback, 'addFetchGlobalListener');
1534
+ this.#fetchGlobal.set(type, callback);
1535
+ }
1536
+
1537
+ /**
1538
+ * Removes a global fetch tracking listener.
1539
+ * @param {string} type - The identifier for the tracking type.
1540
+ * @returns {boolean} True if the listener was removed, false otherwise.
1541
+ */
1542
+ removeFetchGlobalListener(type) {
1543
+ TinyServiceWorkerEngine.#assertListenerType(type, 'removeFetchGlobalListener');
1544
+ return this.#fetchGlobal.delete(type);
1545
+ }
1546
+
1547
+ /**
1548
+ * Retrieves a global fetch tracking listener.
1549
+ * @param {string} type - The identifier for the tracking type.
1550
+ * @returns {FetchCallback|undefined} The listener, or undefined if not found.
1551
+ */
1552
+ getFetchGlobalListener(type) {
1553
+ TinyServiceWorkerEngine.#assertListenerType(type, 'getFetchGlobalListener');
1554
+ return this.#fetchGlobal.get(type);
1555
+ }
1556
+
1557
+ /**
1558
+ * Checks if a global fetch tracking listener exists.
1559
+ * @param {string} type - The identifier for the tracking type.
1560
+ * @returns {boolean} True if the listener exists, false otherwise.
1561
+ */
1562
+ hasFetchGlobal(type) {
1563
+ TinyServiceWorkerEngine.#assertListenerType(type, 'hasFetchGlobal');
1564
+ return this.#fetchGlobal.has(type);
1565
+ }
1566
+
1567
+ /**
1568
+ * Clears all global fetch tracking listeners.
1569
+ * @returns {void}
1570
+ */
1571
+ clearFetchGlobals() {
1572
+ return this.#fetchGlobal.clear();
1573
+ }
1574
+
1575
+ /**
1576
+ * Gets the number of registered message listeners.
1577
+ * @returns {number} The count of registered message listeners.
1578
+ */
1579
+ get messagesSize() {
1580
+ return this.#messages.size;
1581
+ }
1582
+
1583
+ /**
1584
+ * Adds a message listener.
1585
+ * @param {string} type - The identifier for the message type.
1586
+ * @param {MessageCallback} callback - The callback to execute.
1587
+ */
1588
+ addMessageListener(type, callback) {
1589
+ TinyServiceWorkerEngine.#assertListenerType(type, 'addMessageListener');
1590
+ TinyServiceWorkerEngine.#assertListenerCallback(callback, 'addMessageListener');
1591
+ this.#messages.set(type, callback);
1592
+ }
1593
+
1594
+ /**
1595
+ * Removes a message listener.
1596
+ * @param {string} type - The identifier for the message type.
1597
+ * @returns {boolean} True if the listener was removed, false otherwise.
1598
+ */
1599
+ removeMessageListener(type) {
1600
+ TinyServiceWorkerEngine.#assertListenerType(type, 'removeMessageListener');
1601
+ return this.#messages.delete(type);
1602
+ }
1603
+
1604
+ /**
1605
+ * Retrieves a message listener.
1606
+ * @param {string} type - The identifier for the message type.
1607
+ * @returns {MessageCallback|undefined} The listener, or undefined if not found.
1608
+ */
1609
+ getMessageListener(type) {
1610
+ TinyServiceWorkerEngine.#assertListenerType(type, 'getMessageListener');
1611
+ return this.#messages.get(type);
1612
+ }
1613
+
1614
+ /**
1615
+ * Checks if a message listener exists.
1616
+ * @param {string} type - The identifier for the message type.
1617
+ * @returns {boolean} True if the listener exists, false otherwise.
1618
+ */
1619
+ hasMessageListener(type) {
1620
+ TinyServiceWorkerEngine.#assertListenerType(type, 'hasMessageListener');
1621
+ return this.#messages.has(type);
1622
+ }
1623
+
1624
+ /**
1625
+ * Clears all registered message listeners.
1626
+ * @returns {void}
1627
+ */
1628
+ clearMessageListeners() {
1629
+ return this.#messages.clear();
1630
+ }
1631
+
1632
+ /**
1633
+ * Returns a copy of the blacklisted event names.
1634
+ * @returns {string[]} An array with every blacklisted event name.
1635
+ */
1636
+ get eventBlacklist() {
1637
+ return Array.from(this.#eventBlacklist);
1638
+ }
1639
+
1640
+ /**
1641
+ * Adds an event name to the blacklist. Blacklisted events are ignored when
1642
+ * they arrive from the browser.
1643
+ * @param {string} type - The event name to ignore.
1644
+ * @returns {void}
1645
+ * @throws {TypeError} If the type is not a non-empty string.
1646
+ */
1647
+ addToEventBlacklist(type) {
1648
+ TinyServiceWorkerEngine.#assertListenerType(type, 'addToEventBlacklist');
1649
+ this.#eventBlacklist.add(type);
1650
+ }
1651
+
1652
+ /**
1653
+ * Removes an event name from the blacklist.
1654
+ * @param {string} type - The event name to remove.
1655
+ * @returns {boolean} True if the event was removed, false otherwise.
1656
+ * @throws {TypeError} If the type is not a non-empty string.
1657
+ */
1658
+ removeFromEventBlacklist(type) {
1659
+ TinyServiceWorkerEngine.#assertListenerType(type, 'removeFromEventBlacklist');
1660
+ return this.#eventBlacklist.delete(type);
1661
+ }
1662
+
1663
+ /**
1664
+ * Checks whether an event name is blacklisted.
1665
+ * @param {string} type - The event name to check.
1666
+ * @returns {boolean} True if the event is blacklisted, false otherwise.
1667
+ * @throws {TypeError} If the type is not a non-empty string.
1668
+ */
1669
+ hasInEventBlacklist(type) {
1670
+ TinyServiceWorkerEngine.#assertListenerType(type, 'hasInEventBlacklist');
1671
+ return this.#eventBlacklist.has(type);
1672
+ }
1673
+
1674
+ /**
1675
+ * Removes every event name from the blacklist.
1676
+ * @returns {void}
1677
+ */
1678
+ clearEventBlacklist() {
1679
+ this.#eventBlacklist.clear();
1680
+ }
1681
+
1682
+ /**
1683
+ * Promotes the waiting Service Worker to the active state.
1684
+ *
1685
+ * This is the manual counterpart of the `'immediate'` install strategy. It is
1686
+ * safe to call at any time: when no worker is waiting, the browser resolves the
1687
+ * call without side effects.
1688
+ *
1689
+ * @param {ExtendableEvent} [event] - The lifecycle event that triggered the call, when any.
1690
+ * @returns {Promise<boolean>} `true` when `skipWaiting()` was requested.
1691
+ * @throws {TypeError} If `event` is provided and does not expose a `waitUntil` method.
1692
+ */
1693
+ async skipWaiting(event) {
1694
+ if (!(event instanceof ExtendableEvent)) {
1695
+ throw new TypeError(
1696
+ '[TinyServiceWorkerEngine] skipWaiting: event must be an ExtendableEvent when provided.',
1697
+ );
1698
+ }
1699
+
1700
+ this.emit('beforeSkipWaiting', { event });
1701
+ await sw.skipWaiting();
1702
+ this.emit('afterSkipWaiting', { event });
1703
+ this.log('info', 'skipWaiting: activation requested.');
1704
+ return true;
1705
+ }
1706
+
1707
+ /**
1708
+ * Makes the active Service Worker take control of every open client.
1709
+ *
1710
+ * This is the manual counterpart of the `'immediate'` activate strategy.
1711
+ *
1712
+ * @param {ClientQueryOptions} [query] - Filter applied to the clients that must be claimed.
1713
+ * @returns {Promise<readonly Client[]>} The clients that are now controlled.
1714
+ * @throws {TypeError} If `query` is provided and is not a non-null object.
1715
+ */
1716
+ async claimClients(query = { type: 'window', includeUncontrolled: true }) {
1717
+ if (typeof query !== 'object' || query === null || Array.isArray(query)) {
1718
+ throw new TypeError(
1719
+ '[TinyServiceWorkerEngine] claimClients: query must be a non-null object when provided.',
1720
+ );
1721
+ }
1722
+
1723
+ this.emit('beforeClaim', { query });
1724
+ await sw.clients.claim();
1725
+ const clients = await sw.clients.matchAll(query);
1726
+ this.emit('afterClaim', { query, clients });
1727
+ this.log('info', `claimClients: ${clients.length} client(s) claimed.`);
1728
+ return clients;
1729
+ }
1730
+
1731
+ /**
1732
+ * Forces every controlled client to navigate to the given URL.
1733
+ *
1734
+ * Useful when the new Service Worker changes the routing contract and the open
1735
+ * tabs must be reloaded to stay consistent with the new version.
1736
+ *
1737
+ * @param {string} [url] - Target URL. Defaults to the current URL of each client.
1738
+ * @returns {Promise<number>} The number of clients that were navigated.
1739
+ * @throws {TypeError} If `url` is provided and is not a non-empty string.
1740
+ */
1741
+ async reloadClients(url) {
1742
+ if (url !== undefined && (typeof url !== 'string' || url.trim() === '')) {
1743
+ throw new TypeError(
1744
+ '[TinyServiceWorkerEngine] reloadClients: url must be a non-empty string when provided.',
1745
+ );
1746
+ }
1747
+
1748
+ const clientList = await sw.clients.matchAll({ type: 'window', includeUncontrolled: false });
1749
+ let total = 0;
1750
+
1751
+ for (const client of clientList) {
1752
+ if (typeof client.navigate !== 'function') continue;
1753
+ await client.navigate(url ?? client.url);
1754
+ total += 1;
1755
+ }
1756
+
1757
+ this.log('info', `reloadClients: ${total} client(s) navigated.`);
1758
+ return total;
1759
+ }
1760
+
1761
+ /**
1762
+ * Describes every key accepted by {@link GlobalMsgCode}, except `spaPath`.
1763
+ *
1764
+ * @type {Readonly<Record<string, { nullablePath: boolean }>>}
1765
+ */
1766
+ static #GLOBAL_MSG_CODE_SPEC = Object.freeze({
1767
+ unknown: { nullablePath: false },
1768
+ 200: { nullablePath: true },
1769
+ 404: { nullablePath: false },
1770
+ 500: { nullablePath: false },
1771
+ });
1772
+
1773
+ /**
1774
+ * Validates a single entry of the {@link GlobalMsgCode} table.
1775
+ *
1776
+ * @param {unknown} item - The value to validate.
1777
+ * @param {string} context - Dotted path of the entry, used in the error messages.
1778
+ * @param {boolean} nullablePath - `true` when `path` is allowed to be `null`.
1779
+ * @returns {void}
1780
+ * @throws {TypeError} If `item` is not a valid {@link GlobalMsgCodeItem}.
1781
+ */
1782
+ static #validateGlobalMsgCodeItem(item, context, nullablePath) {
1783
+ if (typeof item !== 'object' || item === null || Array.isArray(item)) {
1784
+ throw new TypeError(
1785
+ `[TinyServiceWorkerEngine] globalMsgCode: "${context}" must be a non-null object.`,
1786
+ );
1787
+ }
1788
+
1789
+ const { path, msg, logMsg } = /** @type {GlobalMsgCodeItem<string|null>} */ (item);
1790
+
1791
+ if (path === null && !nullablePath) {
1792
+ throw new TypeError(
1793
+ `[TinyServiceWorkerEngine] globalMsgCode: "${context}.path" must be a non-empty string.`,
1794
+ );
1795
+ }
1796
+
1797
+ if (path !== null && (typeof path !== 'string' || path.trim() === '')) {
1798
+ throw new TypeError(
1799
+ `[TinyServiceWorkerEngine] globalMsgCode: "${context}.path" must be a non-empty string${
1800
+ nullablePath ? ' or null' : ''
1801
+ }.`,
1802
+ );
1803
+ }
1804
+
1805
+ if (typeof msg !== 'string' || msg.trim() === '') {
1806
+ throw new TypeError(
1807
+ `[TinyServiceWorkerEngine] globalMsgCode: "${context}.msg" must be a non-empty string.`,
1808
+ );
1809
+ }
1810
+
1811
+ if (typeof logMsg !== 'string' || logMsg.trim() === '') {
1812
+ throw new TypeError(
1813
+ `[TinyServiceWorkerEngine] globalMsgCode: "${context}.logMsg" must be a non-empty string.`,
1814
+ );
1815
+ }
1816
+ }
1817
+
1818
+ /**
1819
+ * Performs a deep validation of a {@link GlobalMsgCode} object.
1820
+ *
1821
+ * @param {unknown} config - The value to validate.
1822
+ * @param {boolean} [strict=false] - When `true`, every key of {@link GlobalMsgCode}
1823
+ * must be present. When `false`, only the provided keys are validated.
1824
+ * @returns {void}
1825
+ * @throws {TypeError} If `config` is not a non-null object, if it contains an
1826
+ * unknown key, or if one of its entries is not a valid {@link GlobalMsgCodeItem}.
1827
+ */
1828
+ static #validateGlobalMsgCode(config, strict = false) {
1829
+ if (typeof config !== 'object' || config === null || Array.isArray(config)) {
1830
+ throw new TypeError('[TinyServiceWorkerEngine] globalMsgCode must be a non-null object.');
1831
+ }
1832
+
1833
+ const allowedKeys = ['spaPath', ...Object.keys(TinyServiceWorkerEngine.#GLOBAL_MSG_CODE_SPEC)];
1834
+ const providedKeys = new Set(Object.keys(config));
1835
+
1836
+ if (strict) {
1837
+ const missingKeys = allowedKeys.filter((key) => !providedKeys.has(key));
1838
+ if (missingKeys.length > 0) {
1839
+ throw new TypeError(
1840
+ `[TinyServiceWorkerEngine] globalMsgCode: missing required key(s): ${missingKeys
1841
+ .map((key) => `"${key}"`)
1842
+ .join(', ')}.`,
1843
+ );
1844
+ }
1845
+ }
1846
+
1847
+ for (const key of providedKeys) {
1848
+ if (!allowedKeys.includes(key)) {
1849
+ throw new TypeError(
1850
+ `[TinyServiceWorkerEngine] globalMsgCode: unknown key "${key}". Expected one of: ${allowedKeys
1851
+ .map((allowed) => `"${allowed}"`)
1852
+ .join(', ')}.`,
1853
+ );
1854
+ }
1855
+
1856
+ if (key === 'spaPath') {
1857
+ const { spaPath } = /** @type {Record<string, unknown>} */ (config);
1858
+ if (typeof spaPath !== 'string' || spaPath.trim() === '') {
1859
+ throw new TypeError(
1860
+ '[TinyServiceWorkerEngine] globalMsgCode: "spaPath" must be a non-empty string.',
1861
+ );
1862
+ }
1863
+ continue;
1864
+ }
1865
+
1866
+ const spec = TinyServiceWorkerEngine.#GLOBAL_MSG_CODE_SPEC[key];
1867
+ TinyServiceWorkerEngine.#validateGlobalMsgCodeItem(
1868
+ /** @type {Record<string, unknown>} */ (config)[key],
1869
+ key,
1870
+ spec.nullablePath,
1871
+ );
1872
+ }
1873
+ }
1874
+
1875
+ /**
1876
+ * Validates a partial lifecycle configuration.
1877
+ *
1878
+ * Only the provided keys are checked, so the method accepts both a full
1879
+ * {@link LifecycleOptions} object and a partial patch.
1880
+ *
1881
+ * @param {unknown} lifecycle - The value to validate.
1882
+ * @returns {void}
1883
+ * @throws {TypeError} If `lifecycle` is not a non-null object, if it contains an
1884
+ * unknown key, or if one of its values is not an accepted strategy.
1885
+ */
1886
+ static #assertLifecycle(lifecycle) {
1887
+ if (typeof lifecycle !== 'object' || lifecycle === null || Array.isArray(lifecycle)) {
1888
+ throw new TypeError('[TinyServiceWorkerEngine] lifecycle must be a non-null object.');
1889
+ }
1890
+
1891
+ const source = /** @type {Record<string, unknown>} */ (lifecycle);
1892
+
1893
+ for (const key of Object.keys(source)) {
1894
+ if (!LIFECYCLE_SPEC.some(([name]) => name === key)) {
1895
+ throw new TypeError(
1896
+ `[TinyServiceWorkerEngine] lifecycle: unknown key "${key}". Expected one of: ${LIFECYCLE_SPEC.map(
1897
+ ([name]) => `"${name}"`,
1898
+ ).join(', ')}.`,
1899
+ );
1900
+ }
1901
+ }
1902
+
1903
+ for (const [key, allowed] of LIFECYCLE_SPEC) {
1904
+ const value = source[key];
1905
+ if (value !== undefined && !allowed.includes(/** @type {string} */ (value))) {
1906
+ throw new TypeError(
1907
+ `[TinyServiceWorkerEngine] lifecycle.${key} must be one of ${allowed.join(
1908
+ ', ',
1909
+ )}. Received: ${String(value)}`,
1910
+ );
1911
+ }
1912
+ }
1913
+ }
1914
+
1915
+ /**
1916
+ * Gets a deep clone of the current global message configuration.
1917
+ * @returns {GlobalMsgCode} A deep cloned copy of the global message configuration.
1918
+ */
1919
+ get globalMsgCode() {
1920
+ return TinyCloner.clone(this.#globalMsgCode);
1921
+ }
1922
+
1923
+ /**
1924
+ * Replaces the whole global message configuration.
1925
+ *
1926
+ * The provided object is validated and deep cloned, so later mutations of the
1927
+ * argument do not affect the engine.
1928
+ *
1929
+ * @param {GlobalMsgCode} newConfig - The complete configuration that replaces the current one.
1930
+ * @throws {TypeError} If `newConfig` is not a complete, valid {@link GlobalMsgCode} object.
1931
+ */
1932
+ set globalMsgCode(newConfig) {
1933
+ TinyServiceWorkerEngine.#validateGlobalMsgCode(newConfig, true);
1934
+ // Merge and then deep clone the result to ensure the internal state is isolated
1935
+ this.#globalMsgCode = TinyCloner.clone(newConfig);
1936
+ }
1937
+
1938
+ /**
1939
+ * Merges the provided properties into the current global message configuration.
1940
+ *
1941
+ * @param {Partial<GlobalMsgCode>} newConfig - The properties to merge.
1942
+ * @throws {TypeError} If `newConfig` is not a valid partial {@link GlobalMsgCode} object.
1943
+ */
1944
+ updateGlobalMsgCode(newConfig) {
1945
+ TinyServiceWorkerEngine.#validateGlobalMsgCode(newConfig, false);
1946
+ // Merge and then deep clone the result to ensure the internal state is isolated
1947
+ this.#globalMsgCode = TinyCloner.clone({ ...this.#globalMsgCode, ...newConfig });
1948
+ }
1949
+
1950
+ /**
1951
+ * Adds or updates a custom router configuration for a specific HTTP status code.
1952
+ * @param {number} code - The HTTP status code.
1953
+ * @param {RouterCodeConfig} config - The configuration object.
1954
+ * @throws {TypeError} If code is not a number or config is invalid.
1955
+ */
1956
+ addRouterCode(code, config) {
1957
+ if (typeof code !== 'number') throw new TypeError('Code must be a number.');
1958
+ if (typeof config !== 'object' || config === null)
1959
+ throw new TypeError('Config must be an object.');
1960
+ if (
1961
+ typeof config.fn !== 'function' ||
1962
+ typeof config.msg !== 'string' ||
1963
+ typeof config.logMsg !== 'string'
1964
+ ) {
1965
+ throw new TypeError(
1966
+ 'Invalid RouterCodeConfig: fn must be a function, and msg/logMsg must be strings.',
1967
+ );
1968
+ }
1969
+
1970
+ const newCodes = new Map(this.#config.fetch.router.codes);
1971
+ // Deep clone the incoming config before storing it in the Map
1972
+ newCodes.set(code, TinyCloner.clone(config));
1973
+
1974
+ this.#updateConfig({
1975
+ fetch: {
1976
+ router: {
1977
+ codes: newCodes,
1978
+ },
1979
+ },
1980
+ });
1981
+ }
1982
+
1983
+ /**
1984
+ * Removes a custom router configuration for a specific HTTP status code.
1985
+ * @param {number} code - The HTTP status code to remove.
1986
+ * @returns {boolean} True if a code was removed, false otherwise.
1987
+ * @throws {TypeError} If code is not a number.
1988
+ */
1989
+ removeRouterCode(code) {
1990
+ if (typeof code !== 'number') {
1991
+ throw new TypeError('[TinyServiceWorkerEngine] removeRouterCode: code must be a number.');
1992
+ }
1993
+ const newCodes = new Map(this.#config.fetch.router.codes);
1994
+ if (newCodes.delete(code)) {
1995
+ this.#updateConfig({ fetch: { router: { codes: newCodes } } });
1996
+ return true;
1997
+ }
1998
+ return false;
1999
+ }
2000
+
2001
+ /**
2002
+ * Retrieves a deep clone of the current configuration for a specific HTTP status code.
2003
+ * @param {number} code - The HTTP status code.
2004
+ * @returns {RouterCodeConfig|undefined} A deep cloned copy of the configuration or undefined.
2005
+ * @throws {TypeError} If code is not a number.
2006
+ */
2007
+ getRouterCode(code) {
2008
+ if (typeof code !== 'number') {
2009
+ throw new TypeError('[TinyServiceWorkerEngine] getRouterCode: code must be a number.');
2010
+ }
2011
+ const cfg = this.#config.fetch.router.codes.get(code);
2012
+ // Return a deep clone to prevent the consumer from mutating the engine's internal Map
2013
+ return cfg ? TinyCloner.clone(cfg) : undefined;
2014
+ }
2015
+
2016
+ /**
2017
+ * Performs checks against registered fetchUrls and fetchRegExp to determine if a request should be intercepted.
2018
+ *
2019
+ * @param {FetchEvent} event - The original fetch event.
2020
+ * @param {URL} url - The parsed URL of the request.
2021
+ * @returns {Promise<FetchCheckerResult>} A promise that resolves to true if the request should proceed normally, or false if it was handled by a plugin.
2022
+ */
2023
+ async #fetchChecker(event, url) {
2024
+ /** @type {Request} */
2025
+ const request = event.request;
2026
+ const isSameOrigin = TinyServiceWorkerEngine.isSameOrigin(request);
2027
+
2028
+ /** @type {FetchCheckerResult} */
2029
+ const result = { code: 404, needValidation: isSameOrigin, continueCheck: true };
2030
+
2031
+ /** @type {FetchObj} */
2032
+ const fetchObj = {
2033
+ isSameOrigin,
2034
+ event,
2035
+ request,
2036
+ url,
2037
+ params: {},
2038
+ replyTemplate: TinyServiceWorkerEngine.replyTemplate,
2039
+ replyTo: TinyServiceWorkerEngine.replyTo,
2040
+ replyToAll: TinyServiceWorkerEngine.replyToAll,
2041
+ };
2042
+
2043
+ // 0. Global Tracking Layer (Always executed, regardless of URL or Router)
2044
+ for (const callback of this.#fetchGlobal.values()) {
2045
+ if (!result.continueCheck) break;
2046
+ try {
2047
+ // Execute the callback. If it returns false, the request is considered "handled"
2048
+ // but since this is a tracking layer, the flow continues according to the router logic.
2049
+ await callback(fetchObj, result);
2050
+ } catch (error) {
2051
+ this.log('error', 'Error in global fetch tracking listener:', error);
2052
+ this.emit('fetchGlobalError', { error, fetchObj });
2053
+ }
2054
+ }
2055
+
2056
+ if (result.continueCheck) {
2057
+ // 3. Execute the plugin if a match is found
2058
+ /** @param {FetchCallback} callback */
2059
+ const executeCallback = async (callback) => {
2060
+ try {
2061
+ this.emit('beforeFetchPlugin', fetchObj);
2062
+ await callback(fetchObj, result);
2063
+ this.emit('afterFetchPlugin', fetchObj);
2064
+ } catch (error) {
2065
+ result.code = 500;
2066
+ fetchObj.error = error instanceof Error ? error : new Error('Unknown Error.');
2067
+ result.continueCheck = false;
2068
+ result.needValidation = true;
2069
+ this.log('error', `Error executing fetch plugin for "${url.pathname}":`, error);
2070
+ this.emit('fetchPluginError', fetchObj);
2071
+ }
2072
+ };
2073
+
2074
+ // 1. Check in fetchUrls (Exact match and Parameterized match)
2075
+ for (const [pattern, callback] of this.#fetchUrls.entries()) {
2076
+ if (!result.continueCheck) break;
2077
+ // Dynamic parameter matching (e.g., /user/:id)
2078
+ if (pattern.includes('/:')) {
2079
+ // Converts the key pattern into the extraction Regex
2080
+ const seg = SegmentExtractor.segmentExtractorV1(pattern);
2081
+ const { match, params } = seg.exec(url.pathname);
2082
+ fetchObj.params = { ...fetchObj.params, ...params };
2083
+ if (match) {
2084
+ await executeCallback(callback);
2085
+ continue; // Route type detected
2086
+ }
2087
+ }
2088
+
2089
+ // Exact match
2090
+ if (url.pathname === pattern) {
2091
+ await executeCallback(callback);
2092
+ }
2093
+ }
2094
+
2095
+ // 2. Check in fetchRegExp
2096
+ for (const [regExpStr, callback] of this.#fetchRegExp.entries()) {
2097
+ if (!result.continueCheck) break;
2098
+ const regex = new RegExp(regExpStr);
2099
+ if (regex.test(url.pathname)) {
2100
+ await executeCallback(callback);
2101
+ }
2102
+ }
2103
+ }
2104
+
2105
+ return result;
2106
+ }
2107
+
2108
+ /**
2109
+ * Adds a handler for a specific sync event.
2110
+ * @param {string} tag - The tag of the sync event.
2111
+ * @param {SyncCallback} callback - The function to be executed.
2112
+ * @throws {TypeError} If tag is not a string or callback is not a function.
2113
+ */
2114
+ addSyncListener(tag, callback) {
2115
+ if (typeof tag !== 'string' || tag.trim() === '') {
2116
+ throw new TypeError('Tag must be a non-empty string.');
2117
+ }
2118
+ if (typeof callback !== 'function') {
2119
+ throw new TypeError('Callback must be a function.');
2120
+ }
2121
+ this.#syncListeners.set(tag, callback);
2122
+ }
2123
+
2124
+ /**
2125
+ * Removes a sync handler for a specific tag.
2126
+ * @param {string} tag - The tag of the sync event.
2127
+ * @returns {boolean} True if the handler was removed.
2128
+ * @throws {TypeError} If tag is not a non-empty string.
2129
+ */
2130
+ removeSyncListener(tag) {
2131
+ if (typeof tag !== 'string' || tag.trim() === '') {
2132
+ throw new TypeError(
2133
+ '[TinyServiceWorkerEngine] removeSyncListener: tag must be a non-empty string.',
2134
+ );
2135
+ }
2136
+ return this.#syncListeners.delete(tag);
2137
+ }
2138
+
2139
+ /**
2140
+ * Returns the number of registered sync listeners.
2141
+ * @returns {number} The count of registered sync listeners.
2142
+ */
2143
+ get syncListenerSize() {
2144
+ return this.#syncListeners.size;
2145
+ }
2146
+
2147
+ /**
2148
+ * Clears all sync listeners.
2149
+ * @returns {void}
2150
+ */
2151
+ clearSyncListeners() {
2152
+ return this.#syncListeners.clear();
2153
+ }
2154
+
2155
+ /**
2156
+ * Gets the number of registered install listeners.
2157
+ * @returns {number} The count of registered install listeners.
2158
+ */
2159
+ get installListenerSize() {
2160
+ return this.#installListeners.size;
2161
+ }
2162
+
2163
+ /**
2164
+ * Adds an install listener.
2165
+ * @param {string} tag - The identifier for the install listener.
2166
+ * @param {LifecycleCallback} callback - The callback to execute.
2167
+ * @returns {void}
2168
+ * @throws {TypeError} If tag is not a non-empty string or callback is not a function.
2169
+ */
2170
+ addInstallListener(tag, callback) {
2171
+ TinyServiceWorkerEngine.#assertListenerType(tag, 'addInstallListener');
2172
+ TinyServiceWorkerEngine.#assertListenerCallback(callback, 'addInstallListener');
2173
+ this.#installListeners.set(tag, callback);
2174
+ }
2175
+
2176
+ /**
2177
+ * Removes an install listener.
2178
+ * @param {string} tag - The identifier for the install listener.
2179
+ * @returns {boolean} True if the listener was removed, false otherwise.
2180
+ * @throws {TypeError} If tag is not a non-empty string.
2181
+ */
2182
+ removeInstallListener(tag) {
2183
+ TinyServiceWorkerEngine.#assertListenerType(tag, 'removeInstallListener');
2184
+ return this.#installListeners.delete(tag);
2185
+ }
2186
+
2187
+ /**
2188
+ * Retrieves an install listener.
2189
+ * @param {string} tag - The identifier for the install listener.
2190
+ * @returns {LifecycleCallback|undefined} The listener, or undefined if not found.
2191
+ * @throws {TypeError} If tag is not a non-empty string.
2192
+ */
2193
+ getInstallListener(tag) {
2194
+ TinyServiceWorkerEngine.#assertListenerType(tag, 'getInstallListener');
2195
+ return this.#installListeners.get(tag);
2196
+ }
2197
+
2198
+ /**
2199
+ * Checks if an install listener exists.
2200
+ * @param {string} tag - The identifier for the install listener.
2201
+ * @returns {boolean} True if the listener exists, false otherwise.
2202
+ * @throws {TypeError} If tag is not a non-empty string.
2203
+ */
2204
+ hasInstallListener(tag) {
2205
+ TinyServiceWorkerEngine.#assertListenerType(tag, 'hasInstallListener');
2206
+ return this.#installListeners.has(tag);
2207
+ }
2208
+
2209
+ /**
2210
+ * Clears all registered install listeners.
2211
+ * @returns {void}
2212
+ */
2213
+ clearInstallListeners() {
2214
+ return this.#installListeners.clear();
2215
+ }
2216
+
2217
+ /**
2218
+ * Gets the number of registered activate listeners.
2219
+ * @returns {number} The count of registered activate listeners.
2220
+ */
2221
+ get activateListenerSize() {
2222
+ return this.#activateListeners.size;
2223
+ }
2224
+
2225
+ /**
2226
+ * Adds an activate listener.
2227
+ * @param {string} tag - The identifier for the activate listener.
2228
+ * @param {LifecycleCallback} callback - The callback to execute.
2229
+ * @returns {void}
2230
+ * @throws {TypeError} If tag is not a non-empty string or callback is not a function.
2231
+ */
2232
+ addActivateListener(tag, callback) {
2233
+ TinyServiceWorkerEngine.#assertListenerType(tag, 'addActivateListener');
2234
+ TinyServiceWorkerEngine.#assertListenerCallback(callback, 'addActivateListener');
2235
+ this.#activateListeners.set(tag, callback);
2236
+ }
2237
+
2238
+ /**
2239
+ * Removes an activate listener.
2240
+ * @param {string} tag - The identifier for the activate listener.
2241
+ * @returns {boolean} True if the listener was removed, false otherwise.
2242
+ * @throws {TypeError} If tag is not a non-empty string.
2243
+ */
2244
+ removeActivateListener(tag) {
2245
+ TinyServiceWorkerEngine.#assertListenerType(tag, 'removeActivateListener');
2246
+ return this.#activateListeners.delete(tag);
2247
+ }
2248
+
2249
+ /**
2250
+ * Retrieves an activate listener.
2251
+ * @param {string} tag - The identifier for the activate listener.
2252
+ * @returns {LifecycleCallback|undefined} The listener, or undefined if not found.
2253
+ * @throws {TypeError} If tag is not a non-empty string.
2254
+ */
2255
+ getActivateListener(tag) {
2256
+ TinyServiceWorkerEngine.#assertListenerType(tag, 'getActivateListener');
2257
+ return this.#activateListeners.get(tag);
2258
+ }
2259
+
2260
+ /**
2261
+ * Checks if an activate listener exists.
2262
+ * @param {string} tag - The identifier for the activate listener.
2263
+ * @returns {boolean} True if the listener exists, false otherwise.
2264
+ * @throws {TypeError} If tag is not a non-empty string.
2265
+ */
2266
+ hasActivateListener(tag) {
2267
+ TinyServiceWorkerEngine.#assertListenerType(tag, 'hasActivateListener');
2268
+ return this.#activateListeners.has(tag);
2269
+ }
2270
+
2271
+ /**
2272
+ * Clears all registered activate listeners.
2273
+ * @returns {void}
2274
+ */
2275
+ clearActivateListeners() {
2276
+ return this.#activateListeners.clear();
2277
+ }
2278
+
2279
+ /**
2280
+ * Removes a registered API handler.
2281
+ * @param {string} type - The identifier for the call.
2282
+ * @returns {boolean} True if the handler was removed, false otherwise.
2283
+ * @throws {TypeError} If type is not a non-empty string.
2284
+ */
2285
+ offApi(type) {
2286
+ TinyServiceWorkerEngine.#assertListenerType(type, 'offApi');
2287
+ return this.#apiHandlers.delete(type);
2288
+ }
2289
+
2290
+ /**
2291
+ * Registers a handler for API calls coming from the browser.
2292
+ * @param {string} type - The identifier for the call.
2293
+ * @param {ApiHandlerCallback} callback - Function that processes the request and returns a payload.
2294
+ * @returns {void}
2295
+ * @throws {TypeError} If type is not a non-empty string or callback is not a function.
2296
+ */
2297
+ onApi(type, callback) {
2298
+ TinyServiceWorkerEngine.#assertListenerType(type, 'onApi');
2299
+ TinyServiceWorkerEngine.#assertListenerCallback(callback, 'onApi');
2300
+ this.#apiHandlers.set(type, callback);
2301
+ }
2302
+
2303
+ /**
2304
+ * Sends a request to a specific client in the browser and waits for a response.
2305
+ * @param {Client} client - The client (tab/worker) to which to send.
2306
+ * @param {string} type - The identifier for the call.
2307
+ * @param {MessagePayload} [data] - The request payload.
2308
+ * @param {number} [timeout=10000] - Maximum waiting time in milliseconds.
2309
+ * @returns {Promise<any>} A promise that resolves with the result object or undefined from the Service Worker.
2310
+ * @throws {TypeError} If any argument has an invalid type.
2311
+ */
2312
+ async emitApi(client, type, data, timeout = 10000) {
2313
+ if (!(client instanceof Client)) {
2314
+ throw new TypeError('[TinyServiceWorkerEngine] emitApi: client must be a valid Client.');
2315
+ }
2316
+ if (typeof type !== 'string' || type.trim() === '') {
2317
+ throw new TypeError('[TinyServiceWorkerEngine] emitApi: type must be a non-empty string.');
2318
+ }
2319
+ if (
2320
+ typeof data !== 'undefined' &&
2321
+ (Array.isArray(data) || typeof data !== 'object' || data === null)
2322
+ ) {
2323
+ throw new TypeError(
2324
+ '[TinyServiceWorkerEngine] emitApi: data must be a non-null object when provided.',
2325
+ );
2326
+ }
2327
+ if (typeof timeout !== 'number' || !Number.isFinite(timeout) || timeout < 0) {
2328
+ throw new TypeError(
2329
+ '[TinyServiceWorkerEngine] emitApi: timeout must be a non-negative finite number.',
2330
+ );
2331
+ }
2332
+
2333
+ const correlationId = crypto.randomUUID();
2334
+
2335
+ return new Promise((resolve, reject) => {
2336
+ const timer = setTimeout(() => {
2337
+ if (this.#pendingRequests.has(correlationId)) {
2338
+ this.#pendingRequests.delete(correlationId);
2339
+ reject(new Error(`API timeout: ${type}`));
2340
+ }
2341
+ }, timeout);
2342
+
2343
+ this.#pendingRequests.set(correlationId, { resolve, reject, timer });
2344
+
2345
+ client.postMessage({
2346
+ type,
2347
+ data,
2348
+ correlationId,
2349
+ isApi: true,
2350
+ });
2351
+ });
2352
+ }
2353
+
2354
+ /**
2355
+ * Handles a `push` event.
2356
+ *
2357
+ * The method never rejects: a failure is reported through the `pushError`
2358
+ * event so the browser does not show its own generic notification.
2359
+ *
2360
+ * @param {PushEvent} event - The native push event.
2361
+ * @returns {Promise<void>}
2362
+ */
2363
+ async #handlePush(event) {
2364
+ const pushCfg = this.#config.push;
2365
+ const message = await TinyPushPayload.fromEvent(event);
2366
+
2367
+ if (TinyPushPayload.isExpired(message)) {
2368
+ this.log('warn', `Discarded expired push: ${message.id ?? message.type}.`);
2369
+ return;
2370
+ }
2371
+
2372
+ /** @type {import('./TinyPushRouter.mjs').TinyPushContext} */
2373
+ const context = { event, message, engine: this, handled: false, notificationShown: false };
2374
+
2375
+ this.emit('push', context);
2376
+
2377
+ /** @type {null|Error} */
2378
+ let errorNotification = null;
2379
+ try {
2380
+ const handled = await this.#pushRouter.dispatch(message, context);
2381
+
2382
+ if (!handled && message.notification) {
2383
+ const { title, options } = TinyPushPayload.toNotification(message, pushCfg);
2384
+ await this.showNotification(title, options.body ?? '', options);
2385
+ context.notificationShown = true;
2386
+ }
2387
+ } catch (error) {
2388
+ const body = errorMaker(error, event);
2389
+ errorNotification = body.error;
2390
+ this.emit('pushError', body);
2391
+ this.log('error', 'Failed to handle push message.', error);
2392
+ } finally {
2393
+ await TinyServiceWorkerEngine.#replyToAll(
2394
+ {
2395
+ type: 'sw:PushReceived',
2396
+ data: {
2397
+ ...message,
2398
+ notificationShown: context.notificationShown === true,
2399
+ error: errorNotification ? errorNotification.message : null,
2400
+ },
2401
+ },
2402
+ false,
2403
+ );
2404
+ }
2405
+ }
2406
+
2407
+ /**
2408
+ * Handles a `notificationclick` event, including action buttons.
2409
+ *
2410
+ * @param {NotificationEvent} event - The native notification click event.
2411
+ * @returns {Promise<void>}
2412
+ */
2413
+ async #handleNotificationClick(event) {
2414
+ const action = event.action || null;
2415
+ const data = /** @type {Record<string, any>} */ (event.notification.data ?? {});
2416
+ const target = action ? data.actions?.[action] : data.url;
2417
+
2418
+ this.emit('notificationclick', { event, action, data });
2419
+
2420
+ if (!target) {
2421
+ event.notification.close();
2422
+ return;
2423
+ }
2424
+
2425
+ const url = new URL(target, sw.location.origin).href;
2426
+ const clientList = await sw.clients.matchAll({ type: 'window', includeUncontrolled: true });
2427
+
2428
+ for (const client of clientList) {
2429
+ if (client.url === url && 'focus' in client) {
2430
+ await client.focus();
2431
+ event.notification.close();
2432
+ return;
2433
+ }
2434
+ }
2435
+
2436
+ await sw.clients.openWindow(url);
2437
+ event.notification.close();
2438
+ }
2439
+
2440
+ /**
2441
+ * Handles a `pushsubscriptionchange` event.
2442
+ *
2443
+ * The browser fires this event when the push service rotates the endpoint.
2444
+ * Without this handler the user stops receiving pushes silently.
2445
+ *
2446
+ * @param {Event} event - The native event.
2447
+ * @returns {Promise<void>}
2448
+ */
2449
+ async #handleSubscriptionChange(event) {
2450
+ this.emit('pushsubscriptionchange', { event });
2451
+ await TinyServiceWorkerEngine.#replyToAll({ type: 'sw:PushSubscriptionChange' }, false);
2452
+ }
2453
+
2454
+ /**
2455
+ * Displays a native notification to the user.
2456
+ *
2457
+ * @param {string} title - The notification title.
2458
+ * @param {string} body - The message body.
2459
+ * @param {NotificationOptions} [options] - Additional options from the Notification API.
2460
+ * @returns {Promise<void>}
2461
+ */
2462
+ async showNotification(title, body, options = {}) {
2463
+ const pushCfg = this.#config.push;
2464
+ if (!pushCfg.enabled) {
2465
+ throw new Error(
2466
+ '[TinyServiceWorkerEngine] showNotification: Push notifications are disabled in the configuration.',
2467
+ );
2468
+ }
2469
+
2470
+ if (typeof title !== 'string' || title.trim() === '') {
2471
+ throw new TypeError('Notification title must be a non-empty string.');
2472
+ }
2473
+ if (typeof body !== 'string') {
2474
+ throw new TypeError('Notification body must be a string.');
2475
+ }
2476
+
2477
+ return sw.registration.showNotification(title, {
2478
+ body,
2479
+ ...options,
2480
+ });
2481
+ }
2482
+
2483
+ /**
2484
+ * Runs every registered lifecycle listener and resolves the phase strategy.
2485
+ *
2486
+ * The order is read from `lifecycle.installOrder` or `lifecycle.activateOrder`,
2487
+ * depending on `phase`. Listeners never reject: a throwing listener is logged
2488
+ * and emitted, so it can not block the Service Worker lifecycle.
2489
+ *
2490
+ * @param {Map<string, LifecycleCallback>} listeners - The listeners to run.
2491
+ * @param {ExtendableEvent} event - The native lifecycle event.
2492
+ * @param {'install'|'activate'} phase - The lifecycle phase, used for logging and events.
2493
+ * @param {(event: ExtendableEvent) => Promise<void>} resolveStrategy - Resolves the phase strategy.
2494
+ * @returns {Promise<void>} Resolves once every listener has settled.
2495
+ * @throws {TypeError} If `phase` is unknown or `resolveStrategy` is not a function.
2496
+ */
2497
+ async #runLifecycleListeners(listeners, event, phase, resolveStrategy) {
2498
+ if (typeof resolveStrategy !== 'function') {
2499
+ throw new TypeError(
2500
+ `[TinyServiceWorkerEngine] runLifecycleListeners: resolveStrategy must be a function. Received: ${typeof resolveStrategy}`,
2501
+ );
2502
+ }
2503
+
2504
+ const orderKey = PHASE_ORDER_KEY[phase];
2505
+ if (orderKey === undefined) {
2506
+ throw new TypeError(
2507
+ `[TinyServiceWorkerEngine] runLifecycleListeners: unknown phase "${String(phase)}".`,
2508
+ );
2509
+ }
2510
+
2511
+ const strategyFirst = this.#config.lifecycle[orderKey] === 'before';
2512
+
2513
+ if (strategyFirst) await resolveStrategy(event);
2514
+
2515
+ for (const [tag, callback] of listeners.entries()) {
2516
+ try {
2517
+ await callback({ event });
2518
+ } catch (error) {
2519
+ this.log('error', `Error in ${phase} handler for tag "${tag}":`, error);
2520
+ this.emit(`${phase}ListenerError`, { event, tag, error });
2521
+ }
2522
+ }
2523
+
2524
+ if (!strategyFirst) await resolveStrategy(event);
2525
+ }
2526
+
2527
+ /**
2528
+ * Resolves the `install` phase according to the configured strategy.
2529
+ *
2530
+ * @param {ExtendableEvent} event - The native install event.
2531
+ * @returns {Promise<void>}
2532
+ * @throws {TypeError} If the configured install strategy is not recognized.
2533
+ */
2534
+ async #resolveInstall(event) {
2535
+ const { installStrategy } = this.#config.lifecycle;
2536
+
2537
+ switch (installStrategy) {
2538
+ case 'immediate':
2539
+ await this.skipWaiting(event);
2540
+ return;
2541
+ case 'wait':
2542
+ this.log('info', 'install: strategy "wait" active, the worker stays in the waiting state.');
2543
+ return;
2544
+ case 'manual':
2545
+ this.log('info', 'install: strategy "manual" active, call skipWaiting() to activate.');
2546
+ this.emit('waitingForActivation', { event });
2547
+ return;
2548
+ default:
2549
+ throw new TypeError(
2550
+ `[TinyServiceWorkerEngine] resolveInstall: unknown install strategy "${String(installStrategy)}".`,
2551
+ );
2552
+ }
2553
+ }
2554
+
2555
+ /**
2556
+ * Resolves the `activate` phase according to the configured strategy.
2557
+ *
2558
+ * @param {ExtendableEvent} event - The native activate event.
2559
+ * @returns {Promise<void>}
2560
+ * @throws {TypeError} If the configured activate strategy is not recognized.
2561
+ */
2562
+ async #resolveActivate(event) {
2563
+ const { activateStrategy } = this.#config.lifecycle;
2564
+
2565
+ switch (activateStrategy) {
2566
+ case 'immediate':
2567
+ await this.claimClients();
2568
+ return;
2569
+ case 'wait':
2570
+ this.log('info', 'activate: strategy "wait" active, clients will not be claimed.');
2571
+ return;
2572
+ case 'manual':
2573
+ this.log('info', 'activate: strategy "manual" active, call claimClients() to claim.');
2574
+ this.emit('waitingForClaim', { event });
2575
+ return;
2576
+ default:
2577
+ throw new TypeError(
2578
+ `[TinyServiceWorkerEngine] resolveActivate: unknown activate strategy "${String(activateStrategy)}".`,
2579
+ );
2580
+ }
2581
+ }
2582
+
2583
+ /**
2584
+ * Initializes the Service Worker event listeners.
2585
+ * @returns {void}
2586
+ * @throws {Error} If the engine has already been started.
2587
+ */
2588
+ init() {
2589
+ if (this.#started) throw new Error('TinyServiceWorkerEngine is already initialized.');
2590
+
2591
+ // Resolves the install phase according to the configured strategy
2592
+ sw.addEventListener('install', (event) => {
2593
+ event.waitUntil(
2594
+ this.#runLifecycleListeners(this.#installListeners, event, 'install', (e) =>
2595
+ this.#resolveInstall(e),
2596
+ ).catch((err) => this.emit('installError', errorMaker(err, event))),
2597
+ );
2598
+ });
2599
+
2600
+ // Resolves the activate phase according to the configured strategy
2601
+ sw.addEventListener('activate', (event) => {
2602
+ this.emit('beforeActivated', { event });
2603
+ event.waitUntil(
2604
+ this.#runLifecycleListeners(this.#activateListeners, event, 'activate', (e) =>
2605
+ this.#resolveActivate(e),
2606
+ )
2607
+ .then(() => {
2608
+ this.emit('afterActivated', { event });
2609
+ this.log('info', 'Activated.');
2610
+ })
2611
+ .catch((err) => this.emit('activateError', errorMaker(err, event))),
2612
+ );
2613
+ });
2614
+
2615
+ // Listener for Push events
2616
+ const pushCfg = this.#config.push;
2617
+ if (pushCfg.enabled) {
2618
+ sw.addEventListener('push', (event) => {
2619
+ event.waitUntil(
2620
+ this.#handlePush(event).catch((error) => {
2621
+ this.emit('pushError', errorMaker(error, event));
2622
+ }),
2623
+ );
2624
+ });
2625
+
2626
+ sw.addEventListener('notificationclick', (event) => {
2627
+ event.waitUntil(this.#handleNotificationClick(event));
2628
+ });
2629
+
2630
+ sw.addEventListener('notificationclose', (event) => {
2631
+ this.emit('notificationclose', { event });
2632
+ });
2633
+
2634
+ sw.addEventListener('pushsubscriptionchange', (event) => {
2635
+ event.waitUntil(this.#handleSubscriptionChange(event));
2636
+ });
2637
+ }
2638
+
2639
+ // Listener for Sync events
2640
+ const syncCfg = this.#config.sync;
2641
+ if (syncCfg.enabled) {
2642
+ sw.addEventListener('sync', (event) => {
2643
+ const tag = event.tag;
2644
+ const callback = this.#syncListeners.get(tag);
2645
+
2646
+ this.emit('beforeSync', { event, tag });
2647
+
2648
+ if (callback) {
2649
+ event.waitUntil(
2650
+ (async () => {
2651
+ try {
2652
+ await callback({ event, tag });
2653
+ this.emit('afterSync', { event, tag });
2654
+ } catch (error) {
2655
+ this.log('error', `Error in sync handler for tag "${tag}":`, error);
2656
+ this.emit('syncError', { event, tag, error });
2657
+ }
2658
+ })(),
2659
+ );
2660
+ } else {
2661
+ this.emit('afterSync', { event, tag });
2662
+ }
2663
+ });
2664
+ }
2665
+
2666
+ // Detect fetch events on the website.
2667
+ const fetchCfg = this.#config.fetch;
2668
+ if (fetchCfg.enabled) {
2669
+ sw.addEventListener('fetch', (event) => {
2670
+ /** @type {Request} */
2671
+ const request = event.request;
2672
+ const url = new URL(request.url);
2673
+ this.emit('fetchRequested', { event, request, url });
2674
+
2675
+ // Handles navigation requests based on the router configuration.
2676
+ const routerCfg = fetchCfg.router;
2677
+ event.respondWith(
2678
+ (async () => {
2679
+ const fetchResult = await this.#fetchChecker(event, url);
2680
+
2681
+ /**
2682
+ * @param {number} code
2683
+ * @param {string} [customMsg]
2684
+ * @param {string} [customPath]
2685
+ */
2686
+ const buildReply = (code, customMsg, customPath) => {
2687
+ if (routerCfg.enabled) {
2688
+ const resType = getResType(code);
2689
+ const codeCfg = this.getCodeCfg(code);
2690
+ return codeCfg.fn({ code, resType, url, request, event, customMsg, customPath });
2691
+ }
2692
+ return null;
2693
+ };
2694
+
2695
+ // 1. Check if the plugin values are correct
2696
+ if (typeof fetchResult.continueCheck !== 'boolean') {
2697
+ const fetchRes = buildReply(
2698
+ 500,
2699
+ 'Received fetch result data with invalid continueCheck format (expected boolean).',
2700
+ );
2701
+ if (fetchRes !== null) return fetchRes;
2702
+ }
2703
+
2704
+ if (!fetchResult.continueCheck) return fetch(request);
2705
+
2706
+ if (
2707
+ typeof fetchResult.customPath !== 'undefined' &&
2708
+ typeof fetchResult.customPath !== 'string'
2709
+ ) {
2710
+ const fetchRes = buildReply(
2711
+ 500,
2712
+ 'Received fetch result data with invalid customPath format (expected string).',
2713
+ );
2714
+ if (fetchRes !== null) return fetchRes;
2715
+ }
2716
+
2717
+ if (
2718
+ typeof fetchResult.customMsg !== 'undefined' &&
2719
+ typeof fetchResult.customMsg !== 'string'
2720
+ ) {
2721
+ const fetchRes = buildReply(
2722
+ 500,
2723
+ 'Received fetch result data with invalid customMsg format (expected string).',
2724
+ );
2725
+ if (fetchRes !== null) return fetchRes;
2726
+ }
2727
+
2728
+ if (
2729
+ typeof fetchResult.customResponse !== 'undefined' &&
2730
+ !(fetchResult.customResponse instanceof Response)
2731
+ ) {
2732
+ const fetchRes = buildReply(
2733
+ 500,
2734
+ 'Received fetch result data with invalid customResponse format (expected Response).',
2735
+ );
2736
+ if (fetchRes !== null) return fetchRes;
2737
+ }
2738
+
2739
+ if (typeof fetchResult.needValidation !== 'boolean') {
2740
+ const fetchRes = buildReply(
2741
+ 500,
2742
+ 'Received fetch result data with invalid needValidation format (expected boolean).',
2743
+ );
2744
+ if (fetchRes !== null) return fetchRes;
2745
+ }
2746
+
2747
+ if (
2748
+ typeof fetchResult.code !== 'number' ||
2749
+ Number.isNaN(fetchResult.code) ||
2750
+ fetchResult.code < 1
2751
+ ) {
2752
+ const fetchRes = buildReply(
2753
+ 500,
2754
+ 'Received fetch result data with invalid code format (expected number greater than 0).',
2755
+ );
2756
+ if (fetchRes !== null) return fetchRes;
2757
+ }
2758
+
2759
+ // 2. Check if a plugin provided a direct response (e.g., from Cache)
2760
+ if (fetchResult.customResponse instanceof Response) {
2761
+ return fetchResult.customResponse;
2762
+ }
2763
+
2764
+ // 3. Otherwise, proceed with the standard router logic
2765
+ if (fetchResult.needValidation) {
2766
+ const fetchRes = buildReply(
2767
+ fetchResult.code,
2768
+ fetchResult.customMsg,
2769
+ fetchResult.customPath,
2770
+ );
2771
+
2772
+ if (fetchRes !== null) return fetchRes;
2773
+ }
2774
+ return fetch(request);
2775
+ })(),
2776
+ );
2777
+ });
2778
+ }
2779
+
2780
+ // Registers the message event listener for communication with application pages.
2781
+ const msgCfg = this.#config.messaging;
2782
+ if (msgCfg.enabled) {
2783
+ sw.addEventListener('message', (event) => {
2784
+ // Validation: Ensure the source is a valid Client
2785
+ if (!(event.source instanceof Client)) {
2786
+ this.log('error', 'Message received from an invalid source (not a Client).');
2787
+ return;
2788
+ }
2789
+
2790
+ // Validation: Ensure event.data is a non-null object
2791
+ if (Array.isArray(event.data) || typeof event.data !== 'object' || event.data === null) {
2792
+ this.log('error', 'Received message with invalid data format (expected object).');
2793
+ return;
2794
+ }
2795
+ if (
2796
+ typeof event.data.data !== 'undefined' &&
2797
+ (Array.isArray(event.data.data) ||
2798
+ typeof event.data.data !== 'object' ||
2799
+ event.data.data === null)
2800
+ ) {
2801
+ this.log('error', 'Received message data with invalid data format (expected object).');
2802
+ return;
2803
+ }
2804
+
2805
+ // Validation: Ensure 'type' exists and is a string
2806
+ if (typeof event.data.type !== 'string') {
2807
+ this.log('error', 'Received message with missing or invalid "type" string.');
2808
+ return;
2809
+ }
2810
+
2811
+ if (this.#eventBlacklist.has(event.data.type)) {
2812
+ this.log('warn', `Ignored blacklisted event: ${event.data.type}`);
2813
+ return;
2814
+ }
2815
+
2816
+ /** @type {string} */
2817
+ const type = event.data.type;
2818
+ /** @type {MessagePayload|undefined} */
2819
+ const data = event.data.data;
2820
+ /** @type {Client} */
2821
+ const source = event.source;
2822
+ /** @type {string} */
2823
+ const clientId = source.id;
2824
+ /** @type {string} */
2825
+ const correlationId = event.data.correlationId;
2826
+ /** @type {boolean} */
2827
+ const isApi = event.data.isApi;
2828
+
2829
+ /** @type {MessageReply} */
2830
+ const reply = (nType, payload) => {
2831
+ if (!(event.source instanceof Client)) {
2832
+ this.log('warn', 'Attempted to reply to a non-client source.');
2833
+ return;
2834
+ }
2835
+ TinyServiceWorkerEngine.replyTo(event.source, nType, payload);
2836
+ };
2837
+
2838
+ // 1. Handle API responses coming from the Browser (Browser Response -> SW)
2839
+ if (type === 'sw:ApiResponse') {
2840
+ if (typeof correlationId !== 'string') {
2841
+ this.log('error', 'Received message with missing or invalid "correlationId" string.');
2842
+ return;
2843
+ }
2844
+ const pending = this.#pendingRequests.get(correlationId);
2845
+ if (pending) {
2846
+ this.#pendingRequests.delete(correlationId);
2847
+ if (data && data.error) {
2848
+ // Note: The error comes in the 'error' field as defined in the Browser
2849
+ // In the browser, 'error' was sent directly; adjusting for consistency
2850
+ pending.reject(new Error(event.data.error || data.error));
2851
+ } else {
2852
+ pending.resolve(data);
2853
+ }
2854
+ }
2855
+ return;
2856
+ }
2857
+
2858
+ // 2. Handle on-demand activation (Browser Request -> SW)
2859
+ if (type === 'sw:SkipWaiting') {
2860
+ event.waitUntil(
2861
+ this.skipWaiting(event).catch((error) => {
2862
+ this.emit('skipWaitingError', errorMaker(error, event));
2863
+ }),
2864
+ );
2865
+ return;
2866
+ }
2867
+
2868
+ // 3. Handle Browser Notification Clicks (Browser Click -> SW)
2869
+ if (type === 'sw:NotificationClicked') {
2870
+ // We create a pseudo-event that mimics a native NotificationEvent
2871
+ // so that #handleNotificationClick can treat it consistently.
2872
+ /** @type {NotificationEvent} */
2873
+ const pseudoEvent = new MockNotificationEvent(event, {
2874
+ title: data?.title,
2875
+ body: data?.body,
2876
+ options: data?.options,
2877
+ });
2878
+ event.waitUntil(this.#handleNotificationClick(pseudoEvent));
2879
+ return;
2880
+ }
2881
+
2882
+ // 3.1 Handle Browser Notification Closes (Browser Close -> SW)
2883
+ if (type === 'sw:NotificationClosed') {
2884
+ this.emit('notificationclose', { event, data });
2885
+ return;
2886
+ }
2887
+
2888
+ // 4. Handle API calls coming from the Browser (Browser Request -> SW)
2889
+ if (isApi === true) {
2890
+ if (typeof correlationId !== 'string') {
2891
+ this.log('error', 'Received message with missing or invalid "correlationId" string.');
2892
+ return;
2893
+ }
2894
+
2895
+ const handler = this.#apiHandlers.get(type);
2896
+ if (!handler) {
2897
+ source.postMessage({
2898
+ correlationId,
2899
+ type: 'sw:ApiResponse',
2900
+ error: `No API handler registered for type: ${type}`,
2901
+ });
2902
+ return;
2903
+ }
2904
+
2905
+ try {
2906
+ /** @param {MessagePayload} [r] */
2907
+ const sendResult = (r) =>
2908
+ source.postMessage({
2909
+ correlationId,
2910
+ type: 'sw:ApiResponse',
2911
+ data: r,
2912
+ });
2913
+ const result = handler({
2914
+ data,
2915
+ correlationId,
2916
+ clientId,
2917
+ reply,
2918
+ replyTemplate: TinyServiceWorkerEngine.replyTemplate,
2919
+ replyTo: TinyServiceWorkerEngine.replyTo,
2920
+ replyToAll: TinyServiceWorkerEngine.replyToAll,
2921
+ replyApi: async (nType, payload, timeout) => {
2922
+ if (!(event.source instanceof Client)) {
2923
+ this.log('warn', 'Attempted to reply to a non-client source.');
2924
+ return;
2925
+ }
2926
+ return this.emitApi(source, nType, payload, timeout);
2927
+ },
2928
+ });
2929
+ if (result instanceof Promise) result.then(sendResult);
2930
+ else sendResult(result);
2931
+ } catch (error) {
2932
+ source.postMessage({
2933
+ correlationId,
2934
+ type: 'sw:ApiResponse',
2935
+ error: error instanceof Error ? error.message : String(error),
2936
+ });
2937
+ }
2938
+ return;
2939
+ }
2940
+
2941
+ if (event.data.type === 'sw:PrepareUpdate') {
2942
+ this.log('info', 'Update signal received. Starting installation...');
2943
+
2944
+ // Force the browser to fetch the latest version of the SW script
2945
+ this.emit('beforeUpdated', { event });
2946
+ event.waitUntil(
2947
+ sw.registration
2948
+ .update()
2949
+ .then(() => {
2950
+ this.emit('afterUpdated', { event });
2951
+ TinyServiceWorkerEngine.#replyToAll({ type: 'sw:Updated' });
2952
+ this.log('info', 'Update successful, waiting for activation.');
2953
+ })
2954
+ .catch((error) => {
2955
+ const err = error instanceof Error ? error : new Error('Unknown Error.');
2956
+ this.emit('updateError', errorMaker(err, event));
2957
+ TinyServiceWorkerEngine.#replyToAll({
2958
+ type: 'sw:UpdateError',
2959
+ data: { message: err.message, name: err.name, stack: err.stack },
2960
+ });
2961
+ this.log('error', 'Update failed:', err);
2962
+ }),
2963
+ );
2964
+ }
2965
+
2966
+ // Get the registered message callback
2967
+ const message = this.#messages.get(type);
2968
+
2969
+ /** @type {MessageObj} */
2970
+ const msgData = {
2971
+ event,
2972
+ data,
2973
+ clientId,
2974
+ reply,
2975
+ replyTemplate: TinyServiceWorkerEngine.replyTemplate,
2976
+ replyTo: TinyServiceWorkerEngine.replyTo,
2977
+ replyToAll: TinyServiceWorkerEngine.replyToAll,
2978
+ };
2979
+
2980
+ /** @type {Error|null} */
2981
+ let err = null;
2982
+
2983
+ // Emit events
2984
+ this.emit('beforeMessage', { event, type, data: msgData });
2985
+ const afterData = () => ({ event, type, error: err, data: msgData });
2986
+ if (message) {
2987
+ /** @param {any} error */
2988
+ const sendErrorEvent = (error) => {
2989
+ err = error instanceof Error ? error : new Error('Unknown Error');
2990
+ this.log('error', `Error executing handler for message type "${type}":`, error);
2991
+ this.emit('messageError', afterData());
2992
+ };
2993
+ try {
2994
+ const callResult = message(msgData);
2995
+ if (callResult instanceof Promise) {
2996
+ event.waitUntil(
2997
+ callResult.then(() => this.emit('afterMessage', afterData())).catch(sendErrorEvent),
2998
+ );
2999
+ } else {
3000
+ this.emit('afterMessage', afterData());
3001
+ }
3002
+ } catch (error) {
3003
+ sendErrorEvent(error);
3004
+ }
3005
+ } else {
3006
+ this.emit('afterMessage', afterData());
3007
+ }
3008
+ });
3009
+ }
3010
+
3011
+ this.#started = true;
3012
+ this.log('info', 'Initialized with custom configuration.');
3013
+ }
3014
+ }
3015
+
3016
+ module.exports = TinyServiceWorkerEngine;