tiny-essentials 1.28.18 → 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 (567) 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/css/TinyCookieConsent.min.css +1 -0
  109. package/dist/v1/css/TinyCookieConsent.min.css.map +1 -0
  110. package/dist/v1/css/TinyDraggerExample.min.css +1 -0
  111. package/dist/v1/css/TinyDraggerExample.min.css.map +1 -0
  112. package/dist/v1/css/TinyLoadingScreen.min.css +1 -0
  113. package/dist/v1/css/TinyLoadingScreen.min.css.map +1 -0
  114. package/dist/v1/css/TinyNotify.min.css +1 -0
  115. package/dist/v1/css/TinyNotify.min.css.map +1 -0
  116. package/dist/v1/css/aiMarker.min.css +1 -0
  117. package/dist/v1/css/aiMarker.min.css.map +1 -0
  118. package/dist/v1/index.cjs +88 -3
  119. package/dist/v1/index.d.mts +31 -6
  120. package/dist/v1/index.d.mts.map +1 -1
  121. package/dist/v1/index.mjs +32 -7
  122. package/dist/v1/jsdoc/EventEmitter.cjs +4 -0
  123. package/dist/v1/jsdoc/EventEmitter.d.mts +33 -0
  124. package/dist/v1/jsdoc/EventEmitter.d.mts.map +1 -0
  125. package/dist/v1/jsdoc/EventEmitter.mjs +18 -0
  126. package/dist/v1/libs/array/TinyArrayComparator.cjs +240 -25
  127. package/dist/v1/libs/array/TinyArrayComparator.d.mts +130 -22
  128. package/dist/v1/libs/array/TinyArrayComparator.d.mts.map +1 -1
  129. package/dist/v1/libs/array/TinyArrayComparator.mjs +210 -24
  130. package/dist/v1/libs/fs/constants.cjs +74 -0
  131. package/dist/v1/libs/fs/constants.d.mts +22 -0
  132. package/dist/v1/libs/fs/constants.d.mts.map +1 -0
  133. package/dist/v1/libs/fs/constants.mjs +53 -0
  134. package/dist/v1/libs/fs/encoding.cjs +118 -0
  135. package/dist/v1/libs/fs/encoding.d.mts +50 -0
  136. package/dist/v1/libs/fs/encoding.d.mts.map +1 -0
  137. package/dist/v1/libs/fs/encoding.mjs +102 -0
  138. package/dist/v1/libs/fs/error.cjs +188 -0
  139. package/dist/v1/libs/fs/error.d.mts +105 -0
  140. package/dist/v1/libs/fs/error.d.mts.map +1 -0
  141. package/dist/v1/libs/fs/error.mjs +158 -0
  142. package/dist/v1/libs/fs/handle.cjs +121 -0
  143. package/dist/v1/libs/fs/handle.d.mts +56 -0
  144. package/dist/v1/libs/fs/handle.d.mts.map +1 -0
  145. package/dist/v1/libs/fs/handle.mjs +100 -0
  146. package/dist/v1/libs/fs/index.cjs +700 -0
  147. package/dist/v1/libs/fs/index.d.mts +377 -0
  148. package/dist/v1/libs/fs/index.d.mts.map +1 -0
  149. package/dist/v1/libs/fs/index.mjs +630 -0
  150. package/dist/v1/libs/fs/path.cjs +234 -0
  151. package/dist/v1/libs/fs/path.d.mts +176 -0
  152. package/dist/v1/libs/fs/path.d.mts.map +1 -0
  153. package/dist/v1/libs/fs/path.mjs +197 -0
  154. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.cjs +202 -0
  155. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.d.mts +121 -0
  156. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.d.mts.map +1 -0
  157. package/dist/v1/libs/fs/plugins/OPFS/StorageBucket.mjs +172 -0
  158. package/dist/v1/libs/fs/plugins/OPFS/index.cjs +334 -0
  159. package/dist/v1/libs/fs/plugins/OPFS/index.d.mts +98 -0
  160. package/dist/v1/libs/fs/plugins/OPFS/index.d.mts.map +1 -0
  161. package/dist/v1/libs/fs/plugins/OPFS/index.mjs +300 -0
  162. package/dist/v1/libs/fs/stats.cjs +245 -0
  163. package/dist/v1/libs/fs/stats.d.mts +226 -0
  164. package/dist/v1/libs/fs/stats.d.mts.map +1 -0
  165. package/dist/v1/libs/fs/stats.mjs +197 -0
  166. package/dist/v1/libs/html/events/TinyIframeEvents.cjs +10 -1
  167. package/dist/v1/libs/html/events/TinyIframeEvents.d.mts +9 -2
  168. package/dist/v1/libs/html/events/TinyIframeEvents.d.mts.map +1 -1
  169. package/dist/v1/libs/html/events/TinyIframeEvents.mjs +8 -1
  170. package/dist/v1/libs/html/events/TinyNewWinEvents.cjs +14 -3
  171. package/dist/v1/libs/html/events/TinyNewWinEvents.d.mts +21 -5
  172. package/dist/v1/libs/html/events/TinyNewWinEvents.d.mts.map +1 -1
  173. package/dist/v1/libs/html/events/TinyNewWinEvents.mjs +12 -3
  174. package/dist/v1/libs/html/scroll/TinySmartScroller.cjs +13 -1
  175. package/dist/v1/libs/html/scroll/TinySmartScroller.d.mts +29 -2
  176. package/dist/v1/libs/html/scroll/TinySmartScroller.d.mts.map +1 -1
  177. package/dist/v1/libs/html/scroll/TinySmartScroller.mjs +11 -1
  178. package/dist/v1/libs/math/TinyAdvancedRaffle.cjs +13 -1
  179. package/dist/v1/libs/math/TinyAdvancedRaffle.d.mts +32 -2
  180. package/dist/v1/libs/math/TinyAdvancedRaffle.d.mts.map +1 -1
  181. package/dist/v1/libs/math/TinyAdvancedRaffle.mjs +11 -1
  182. package/dist/v1/libs/math/TinyTimeout.cjs +0 -14
  183. package/dist/v1/libs/math/TinyTimeout.d.mts +0 -11
  184. package/dist/v1/libs/math/TinyTimeout.d.mts.map +1 -1
  185. package/dist/v1/libs/math/TinyTimeout.mjs +0 -13
  186. package/dist/v1/libs/media/TinyMediaPlayer/index.cjs +25 -1
  187. package/dist/v1/libs/media/TinyMediaPlayer/index.d.mts +35 -2
  188. package/dist/v1/libs/media/TinyMediaPlayer/index.d.mts.map +1 -1
  189. package/dist/v1/libs/media/TinyMediaPlayer/index.mjs +23 -1
  190. package/dist/v1/libs/media/TinyMediaPlayer.cjs +2 -2
  191. package/dist/v1/libs/media/TinyMediaPlayer.d.mts +2 -2
  192. package/dist/v1/libs/media/TinyMediaPlayer.d.mts.map +1 -1
  193. package/dist/v1/libs/media/TinyMediaPlayer.mjs +2 -2
  194. package/dist/v1/libs/media/TinyRadioFm.cjs +19 -1
  195. package/dist/v1/libs/media/TinyRadioFm.d.mts +78 -2
  196. package/dist/v1/libs/media/TinyRadioFm.d.mts.map +1 -1
  197. package/dist/v1/libs/media/TinyRadioFm.mjs +17 -1
  198. package/dist/v1/libs/plugin/TinyPlugin.cjs +1630 -0
  199. package/dist/v1/libs/plugin/TinyPlugin.d.mts +753 -0
  200. package/dist/v1/libs/plugin/TinyPlugin.d.mts.map +1 -0
  201. package/dist/v1/libs/plugin/TinyPlugin.mjs +1376 -0
  202. package/dist/v1/libs/plugin/TinyVersion.cjs +204 -0
  203. package/dist/v1/libs/plugin/TinyVersion.d.mts +70 -0
  204. package/dist/v1/libs/plugin/TinyVersion.d.mts.map +1 -0
  205. package/dist/v1/libs/plugin/TinyVersion.mjs +179 -0
  206. package/dist/v1/libs/router/TinyRouter.cjs +661 -0
  207. package/dist/v1/libs/router/TinyRouter.d.mts +320 -0
  208. package/dist/v1/libs/router/TinyRouter.d.mts.map +1 -0
  209. package/dist/v1/libs/router/TinyRouter.mjs +572 -0
  210. package/dist/v1/libs/storage/TinyLocalStorage.cjs +20 -1
  211. package/dist/v1/libs/storage/TinyLocalStorage.d.mts +64 -2
  212. package/dist/v1/libs/storage/TinyLocalStorage.d.mts.map +1 -1
  213. package/dist/v1/libs/storage/TinyLocalStorage.mjs +18 -1
  214. package/dist/v1/libs/storage/TinyMapCache.cjs +373 -0
  215. package/dist/v1/libs/storage/TinyMapCache.d.mts +274 -0
  216. package/dist/v1/libs/storage/TinyMapCache.d.mts.map +1 -0
  217. package/dist/v1/libs/storage/TinyMapCache.mjs +333 -0
  218. package/dist/v1/libs/storage/TinySetMapDatabase.cjs +1167 -0
  219. package/dist/v1/libs/storage/TinySetMapDatabase.d.mts +603 -0
  220. package/dist/v1/libs/storage/TinySetMapDatabase.d.mts.map +1 -0
  221. package/dist/v1/libs/storage/TinySetMapDatabase.mjs +1059 -0
  222. package/dist/v1/libs/sw/browser/TinyNotificationAdapter.cjs +126 -0
  223. package/dist/v1/libs/sw/browser/TinyNotificationAdapter.d.mts +132 -0
  224. package/dist/v1/libs/sw/browser/TinyNotificationAdapter.d.mts.map +1 -0
  225. package/dist/v1/libs/sw/browser/TinyNotificationAdapter.mjs +109 -0
  226. package/dist/v1/libs/sw/browser/TinyPushManager.cjs +310 -0
  227. package/dist/v1/libs/sw/browser/TinyPushManager.d.mts +168 -0
  228. package/dist/v1/libs/sw/browser/TinyPushManager.d.mts.map +1 -0
  229. package/dist/v1/libs/sw/browser/TinyPushManager.mjs +264 -0
  230. package/dist/v1/libs/sw/browser/TinyServiceWorker.cjs +1060 -0
  231. package/dist/v1/libs/sw/browser/TinyServiceWorker.d.mts +348 -0
  232. package/dist/v1/libs/sw/browser/TinyServiceWorker.d.mts.map +1 -0
  233. package/dist/v1/libs/sw/browser/TinyServiceWorker.mjs +933 -0
  234. package/dist/v1/libs/sw/browser/plugins/TabManager.cjs +315 -0
  235. package/dist/v1/libs/sw/browser/plugins/TabManager.d.mts +178 -0
  236. package/dist/v1/libs/sw/browser/plugins/TabManager.d.mts.map +1 -0
  237. package/dist/v1/libs/sw/browser/plugins/TabManager.mjs +274 -0
  238. package/dist/v1/libs/sw/browser/utils.cjs +28 -0
  239. package/dist/v1/libs/sw/browser/utils.d.mts +8 -0
  240. package/dist/v1/libs/sw/browser/utils.d.mts.map +1 -0
  241. package/dist/v1/libs/sw/browser/utils.mjs +21 -0
  242. package/dist/v1/libs/sw/service/TinyPushRouter.cjs +222 -0
  243. package/dist/v1/libs/sw/service/TinyPushRouter.d.mts +138 -0
  244. package/dist/v1/libs/sw/service/TinyPushRouter.d.mts.map +1 -0
  245. package/dist/v1/libs/sw/service/TinyPushRouter.mjs +201 -0
  246. package/dist/v1/libs/sw/service/TinyServiceWorkerEngine.cjs +3016 -0
  247. package/dist/v1/libs/sw/service/TinyServiceWorkerEngine.d.mts +993 -0
  248. package/dist/v1/libs/sw/service/TinyServiceWorkerEngine.d.mts.map +1 -0
  249. package/dist/v1/libs/sw/service/TinyServiceWorkerEngine.mjs +2558 -0
  250. package/dist/v1/libs/sw/service/plugins/GlobBypassPlugin.cjs +88 -0
  251. package/dist/v1/libs/sw/service/plugins/GlobBypassPlugin.d.mts +32 -0
  252. package/dist/v1/libs/sw/service/plugins/GlobBypassPlugin.d.mts.map +1 -0
  253. package/dist/v1/libs/sw/service/plugins/GlobBypassPlugin.mjs +69 -0
  254. package/dist/v1/libs/sw/service/plugins/GlobCachePlugin.cjs +112 -0
  255. package/dist/v1/libs/sw/service/plugins/GlobCachePlugin.d.mts +31 -0
  256. package/dist/v1/libs/sw/service/plugins/GlobCachePlugin.d.mts.map +1 -0
  257. package/dist/v1/libs/sw/service/plugins/GlobCachePlugin.mjs +91 -0
  258. package/dist/v1/libs/sw/service/plugins/Ping.cjs +32 -0
  259. package/dist/v1/libs/sw/service/plugins/Ping.d.mts +9 -0
  260. package/dist/v1/libs/sw/service/plugins/Ping.d.mts.map +1 -0
  261. package/dist/v1/libs/sw/service/plugins/Ping.mjs +25 -0
  262. package/dist/v1/libs/sw/service/plugins/TabManager.cjs +457 -0
  263. package/dist/v1/libs/sw/service/plugins/TabManager.d.mts +91 -0
  264. package/dist/v1/libs/sw/service/plugins/TabManager.d.mts.map +1 -0
  265. package/dist/v1/libs/sw/service/plugins/TabManager.mjs +372 -0
  266. package/dist/v1/libs/sw/service/plugins/ViteFileDetector.cjs +180 -0
  267. package/dist/v1/libs/sw/service/plugins/ViteFileDetector.d.mts +28 -0
  268. package/dist/v1/libs/sw/service/plugins/ViteFileDetector.d.mts.map +1 -0
  269. package/dist/v1/libs/sw/service/plugins/ViteFileDetector.mjs +148 -0
  270. package/dist/v1/libs/sw/service/shared/TinyPushPayload.cjs +199 -0
  271. package/dist/v1/libs/sw/service/shared/TinyPushPayload.d.mts +80 -0
  272. package/dist/v1/libs/sw/service/shared/TinyPushPayload.d.mts.map +1 -0
  273. package/dist/v1/libs/sw/service/shared/TinyPushPayload.mjs +174 -0
  274. package/dist/v1/libs/sw/utils.cjs +57 -0
  275. package/dist/v1/libs/sw/utils.d.mts +173 -0
  276. package/dist/v1/libs/sw/utils.d.mts.map +1 -0
  277. package/dist/v1/libs/sw/utils.mjs +50 -0
  278. package/dist/v1/libs/text/TinyI18/Browser-NON-DOM.cjs +57 -0
  279. package/dist/v1/libs/text/TinyI18/Browser-NON-DOM.d.mts +39 -0
  280. package/dist/v1/libs/text/TinyI18/Browser-NON-DOM.d.mts.map +1 -0
  281. package/dist/v1/libs/text/TinyI18/Browser-NON-DOM.mjs +48 -0
  282. package/dist/v1/libs/text/TinyI18/Browser.cjs +64 -0
  283. package/dist/v1/libs/text/TinyI18/Browser.d.mts +47 -0
  284. package/dist/v1/libs/text/TinyI18/Browser.d.mts.map +1 -0
  285. package/dist/v1/libs/text/TinyI18/Browser.mjs +57 -0
  286. package/dist/v1/libs/text/TinyI18/Node.cjs +49 -0
  287. package/dist/v1/libs/text/TinyI18/Node.d.mts +38 -0
  288. package/dist/v1/libs/text/TinyI18/Node.d.mts.map +1 -0
  289. package/dist/v1/libs/text/TinyI18/Node.mjs +43 -0
  290. package/dist/v1/libs/text/TinyI18/browser-utils.cjs +17 -0
  291. package/dist/v1/libs/text/TinyI18/browser-utils.d.mts +8 -0
  292. package/dist/v1/libs/text/TinyI18/browser-utils.d.mts.map +1 -0
  293. package/dist/v1/libs/text/TinyI18/browser-utils.mjs +13 -0
  294. package/dist/v1/libs/text/{TinyI18.cjs → TinyI18/index.cjs} +205 -104
  295. package/dist/v1/libs/text/{TinyI18.d.mts → TinyI18/index.d.mts} +141 -92
  296. package/dist/v1/libs/text/TinyI18/index.d.mts.map +1 -0
  297. package/dist/v1/libs/text/{TinyI18.mjs → TinyI18/index.mjs} +195 -101
  298. package/dist/v1/libs/tools/TinyBrowserMonitor.cjs +1088 -0
  299. package/dist/v1/libs/tools/TinyBrowserMonitor.d.mts +621 -0
  300. package/dist/v1/libs/tools/TinyBrowserMonitor.d.mts.map +1 -0
  301. package/dist/v1/libs/tools/TinyBrowserMonitor.mjs +940 -0
  302. package/dist/v1/libs/tools/TinyDebugger.cjs +180 -19
  303. package/dist/v1/libs/tools/TinyDebugger.d.mts +220 -24
  304. package/dist/v1/libs/tools/TinyDebugger.d.mts.map +1 -1
  305. package/dist/v1/libs/tools/TinyDebugger.mjs +161 -19
  306. package/dist/v1/libs/tools/TinyHtmlTagRegexBuilder.cjs +256 -0
  307. package/dist/v1/libs/tools/TinyHtmlTagRegexBuilder.d.mts +125 -0
  308. package/dist/v1/libs/tools/TinyHtmlTagRegexBuilder.d.mts.map +1 -0
  309. package/dist/v1/libs/tools/TinyHtmlTagRegexBuilder.mjs +223 -0
  310. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser-NON-DOM.cjs +23 -0
  311. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser-NON-DOM.d.mts +17 -0
  312. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser-NON-DOM.d.mts.map +1 -0
  313. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser-NON-DOM.mjs +19 -0
  314. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser.cjs +23 -0
  315. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser.d.mts +17 -0
  316. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser.d.mts.map +1 -0
  317. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Browser.mjs +19 -0
  318. package/dist/v1/libs/tools/TinyHttpResponseRegistry/EmptyRequestCodes.cjs +390 -0
  319. package/dist/v1/libs/tools/TinyHttpResponseRegistry/EmptyRequestCodes.d.mts +9 -0
  320. package/dist/v1/libs/tools/TinyHttpResponseRegistry/EmptyRequestCodes.d.mts.map +1 -0
  321. package/dist/v1/libs/tools/TinyHttpResponseRegistry/EmptyRequestCodes.mjs +387 -0
  322. package/dist/v1/libs/tools/TinyHttpResponseRegistry/JsDoc.cjs +2 -0
  323. package/dist/v1/libs/tools/TinyHttpResponseRegistry/JsDoc.d.mts +27 -0
  324. package/dist/v1/libs/tools/TinyHttpResponseRegistry/JsDoc.d.mts.map +1 -0
  325. package/dist/v1/libs/tools/TinyHttpResponseRegistry/JsDoc.mjs +12 -0
  326. package/dist/v1/libs/tools/TinyHttpResponseRegistry/MozillaRequestCodes/LICENSE +53 -0
  327. package/dist/v1/libs/tools/TinyHttpResponseRegistry/MozillaRequestCodes/index.cjs +405 -0
  328. package/dist/v1/libs/tools/TinyHttpResponseRegistry/MozillaRequestCodes/index.d.mts +34 -0
  329. package/dist/v1/libs/tools/TinyHttpResponseRegistry/MozillaRequestCodes/index.d.mts.map +1 -0
  330. package/dist/v1/libs/tools/TinyHttpResponseRegistry/MozillaRequestCodes/index.mjs +342 -0
  331. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Node.cjs +23 -0
  332. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Node.d.mts +17 -0
  333. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Node.d.mts.map +1 -0
  334. package/dist/v1/libs/tools/TinyHttpResponseRegistry/Node.mjs +19 -0
  335. package/dist/v1/libs/tools/TinyHttpResponseRegistry/RequestCodes.cjs +327 -0
  336. package/dist/v1/libs/tools/TinyHttpResponseRegistry/RequestCodes.d.mts +9 -0
  337. package/dist/v1/libs/tools/TinyHttpResponseRegistry/RequestCodes.d.mts.map +1 -0
  338. package/dist/v1/libs/tools/TinyHttpResponseRegistry/RequestCodes.mjs +324 -0
  339. package/dist/v1/libs/tools/TinyHttpResponseRegistry/index.cjs +268 -0
  340. package/dist/v1/libs/tools/TinyHttpResponseRegistry/index.d.mts +107 -0
  341. package/dist/v1/libs/tools/TinyHttpResponseRegistry/index.d.mts.map +1 -0
  342. package/dist/v1/libs/tools/TinyHttpResponseRegistry/index.mjs +238 -0
  343. package/dist/v1/libs/tools/TinyPasswordValidator.cjs +491 -0
  344. package/dist/v1/libs/tools/TinyPasswordValidator.d.mts +246 -0
  345. package/dist/v1/libs/tools/TinyPasswordValidator.d.mts.map +1 -0
  346. package/dist/v1/libs/tools/TinyPasswordValidator.mjs +434 -0
  347. package/dist/v1/libs/tools/TinyPkgExportValidator.cjs +48 -0
  348. package/dist/v1/libs/tools/TinyPkgExportValidator.d.mts +14 -0
  349. package/dist/v1/libs/tools/TinyPkgExportValidator.d.mts.map +1 -1
  350. package/dist/v1/libs/tools/TinyPkgExportValidator.mjs +44 -0
  351. package/dist/v1/libs/tools/TinySiteMap.cjs +1029 -0
  352. package/dist/v1/libs/tools/TinySiteMap.d.mts +429 -0
  353. package/dist/v1/libs/tools/TinySiteMap.d.mts.map +1 -0
  354. package/dist/v1/libs/tools/TinySiteMap.mjs +910 -0
  355. package/dist/v1/libs/tools/TinySiteMapStream.cjs +121 -0
  356. package/dist/v1/libs/tools/TinySiteMapStream.d.mts +51 -0
  357. package/dist/v1/libs/tools/TinySiteMapStream.d.mts.map +1 -0
  358. package/dist/v1/libs/tools/TinySiteMapStream.mjs +85 -0
  359. package/dist/v1/libs/tools/TinyURLSecurityVerifier.cjs +444 -0
  360. package/dist/v1/libs/tools/TinyURLSecurityVerifier.d.mts +217 -0
  361. package/dist/v1/libs/tools/TinyURLSecurityVerifier.d.mts.map +1 -0
  362. package/dist/v1/libs/tools/TinyURLSecurityVerifier.mjs +394 -0
  363. package/dist/v1/libs/tools/TinyUriParser/BlueSky.cjs +613 -0
  364. package/dist/v1/libs/tools/TinyUriParser/BlueSky.d.mts +152 -0
  365. package/dist/v1/libs/tools/TinyUriParser/BlueSky.d.mts.map +1 -0
  366. package/dist/v1/libs/tools/TinyUriParser/BlueSky.mjs +527 -0
  367. package/dist/v1/libs/tools/TinyUriParser/Discord.cjs +1119 -0
  368. package/dist/v1/libs/tools/TinyUriParser/Discord.d.mts +306 -0
  369. package/dist/v1/libs/tools/TinyUriParser/Discord.d.mts.map +1 -0
  370. package/dist/v1/libs/tools/TinyUriParser/Discord.mjs +968 -0
  371. package/dist/v1/libs/tools/TinyUriParser/MatrixProtocol.cjs +469 -0
  372. package/dist/v1/libs/tools/TinyUriParser/MatrixProtocol.d.mts +80 -0
  373. package/dist/v1/libs/tools/TinyUriParser/MatrixProtocol.d.mts.map +1 -0
  374. package/dist/v1/libs/tools/TinyUriParser/MatrixProtocol.mjs +379 -0
  375. package/dist/v1/libs/tools/TinyUriParser.cjs +217 -0
  376. package/dist/v1/libs/tools/TinyUriParser.d.mts +109 -0
  377. package/dist/v1/libs/tools/TinyUriParser.d.mts.map +1 -0
  378. package/dist/v1/libs/tools/TinyUriParser.mjs +183 -0
  379. package/dist/v1/libs/utils/TinyCloner.cjs +466 -0
  380. package/dist/v1/libs/utils/TinyCloner.d.mts +221 -0
  381. package/dist/v1/libs/utils/TinyCloner.d.mts.map +1 -0
  382. package/dist/v1/libs/utils/TinyCloner.mjs +416 -0
  383. package/dist/v1/libs/utils/TinyPromiseQueue.cjs +1 -1
  384. package/dist/v1/libs/utils/TinyPromiseQueue.d.mts +2 -2
  385. package/dist/v1/libs/utils/TinyPromiseQueue.d.mts.map +1 -1
  386. package/dist/v1/libs/utils/TinyPromiseQueue.mjs +1 -1
  387. package/dist/v1/libs/utils/TinyThrottledApi.cjs +333 -0
  388. package/dist/v1/libs/utils/TinyThrottledApi.d.mts +163 -0
  389. package/dist/v1/libs/utils/TinyThrottledApi.d.mts.map +1 -0
  390. package/dist/v1/libs/utils/TinyThrottledApi.mjs +294 -0
  391. package/dist/v1/libs/worker/browser/TinyWebWorker.cjs +342 -0
  392. package/dist/v1/libs/worker/browser/TinyWebWorker.d.mts +97 -0
  393. package/dist/v1/libs/worker/browser/TinyWebWorker.d.mts.map +1 -0
  394. package/dist/v1/libs/worker/browser/TinyWebWorker.mjs +305 -0
  395. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.cjs +346 -0
  396. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.d.mts +127 -0
  397. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.d.mts.map +1 -0
  398. package/dist/v1/libs/worker/engine/TinyWebWorkerEngine.mjs +304 -0
  399. package/dist/v1/regexp/Email.cjs +185 -0
  400. package/dist/v1/regexp/Email.d.mts +73 -0
  401. package/dist/v1/regexp/Email.d.mts.map +1 -0
  402. package/dist/v1/regexp/Email.mjs +153 -0
  403. package/dist/v1/regexp/Glob.cjs +293 -0
  404. package/dist/v1/regexp/Glob.d.mts +45 -0
  405. package/dist/v1/regexp/Glob.d.mts.map +1 -0
  406. package/dist/v1/regexp/Glob.mjs +260 -0
  407. package/dist/v1/regexp/Ipv4.cjs +57 -0
  408. package/dist/v1/regexp/Ipv4.d.mts +29 -0
  409. package/dist/v1/regexp/Ipv4.d.mts.map +1 -0
  410. package/dist/v1/regexp/Ipv4.mjs +45 -0
  411. package/dist/v1/regexp/Ipv6.cjs +67 -0
  412. package/dist/v1/regexp/Ipv6.d.mts +29 -0
  413. package/dist/v1/regexp/Ipv6.d.mts.map +1 -0
  414. package/dist/v1/regexp/Ipv6.mjs +54 -0
  415. package/dist/v1/regexp/SegmentExtractor.cjs +145 -0
  416. package/dist/v1/regexp/SegmentExtractor.d.mts +91 -0
  417. package/dist/v1/regexp/SegmentExtractor.d.mts.map +1 -0
  418. package/dist/v1/regexp/SegmentExtractor.mjs +116 -0
  419. package/dist/v1/regexp/UrlDetector.cjs +125 -0
  420. package/dist/v1/regexp/UrlDetector.d.mts +67 -0
  421. package/dist/v1/regexp/UrlDetector.d.mts.map +1 -0
  422. package/dist/v1/regexp/UrlDetector.mjs +102 -0
  423. package/dist/v1/regexp/Username/index.cjs +233 -0
  424. package/dist/v1/regexp/Username/index.d.mts +77 -0
  425. package/dist/v1/regexp/Username/index.d.mts.map +1 -0
  426. package/dist/v1/regexp/Username/index.mjs +199 -0
  427. package/dist/v1/regexp/Username/jsDoc.cjs +2 -0
  428. package/dist/v1/regexp/Username/jsDoc.d.mts +57 -0
  429. package/dist/v1/regexp/Username/jsDoc.d.mts.map +1 -0
  430. package/dist/v1/regexp/Username/jsDoc.mjs +18 -0
  431. package/dist/v1/regexp/Username/templates/BlueSky.cjs +56 -0
  432. package/dist/v1/regexp/Username/templates/BlueSky.d.mts +35 -0
  433. package/dist/v1/regexp/Username/templates/BlueSky.d.mts.map +1 -0
  434. package/dist/v1/regexp/Username/templates/BlueSky.mjs +52 -0
  435. package/dist/v1/regexp/Username/templates/Discord.cjs +505 -0
  436. package/dist/v1/regexp/Username/templates/Discord.d.mts +209 -0
  437. package/dist/v1/regexp/Username/templates/Discord.d.mts.map +1 -0
  438. package/dist/v1/regexp/Username/templates/Discord.mjs +458 -0
  439. package/dist/v1/regexp/Username/templates/MatrixProtocol.cjs +143 -0
  440. package/dist/v1/regexp/Username/templates/MatrixProtocol.d.mts +70 -0
  441. package/dist/v1/regexp/Username/templates/MatrixProtocol.d.mts.map +1 -0
  442. package/dist/v1/regexp/Username/templates/MatrixProtocol.mjs +128 -0
  443. package/dist/v1/regexp/Username/templates/deprecated/Discord.cjs +24 -0
  444. package/dist/v1/regexp/Username/templates/deprecated/Discord.d.mts +17 -0
  445. package/dist/v1/regexp/Username/templates/deprecated/Discord.d.mts.map +1 -0
  446. package/dist/v1/regexp/Username/templates/deprecated/Discord.mjs +20 -0
  447. package/dist/v1/regexp/Username/templates/index.cjs +11 -0
  448. package/dist/v1/regexp/Username/templates/index.d.mts +5 -0
  449. package/dist/v1/regexp/Username/templates/index.d.mts.map +1 -0
  450. package/dist/v1/regexp/Username/templates/index.mjs +4 -0
  451. package/dist/v1/regexp/index.cjs +41 -0
  452. package/dist/v1/regexp/index.d.mts +9 -0
  453. package/dist/v1/regexp/index.d.mts.map +1 -0
  454. package/dist/v1/regexp/index.mjs +8 -0
  455. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Browser.cjs +64 -0
  456. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Browser.d.mts +11 -0
  457. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Browser.d.mts.map +1 -0
  458. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Browser.mjs +51 -0
  459. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/FileInput.cjs +66 -0
  460. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/FileInput.d.mts +11 -0
  461. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/FileInput.d.mts.map +1 -0
  462. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/FileInput.mjs +51 -0
  463. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Node.cjs +39 -0
  464. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Node.d.mts +11 -0
  465. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Node.d.mts.map +1 -0
  466. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Node.mjs +28 -0
  467. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Template.cjs +494 -0
  468. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Template.d.mts +142 -0
  469. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Template.d.mts.map +1 -0
  470. package/dist/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters/Template.mjs +445 -0
  471. package/dist/v1/webTemplates/bootstrap/5.3/html/BootstrapDialogs.cjs +725 -0
  472. package/dist/v1/webTemplates/bootstrap/5.3/html/BootstrapDialogs.d.mts +293 -0
  473. package/dist/v1/webTemplates/bootstrap/5.3/html/BootstrapDialogs.d.mts.map +1 -0
  474. package/dist/v1/webTemplates/bootstrap/5.3/html/BootstrapDialogs.mjs +614 -0
  475. package/dist/v1/webTemplates/bootstrap/5.3/scss/input/Placeholder-Colors-Fix.scss +64 -0
  476. package/dist/v1/webTemplates/bootstrap/5.3/scss/spacing/Spacing-Plus.scss +31 -0
  477. package/dist/v1/webTemplates/media/MediaValidator/v1/Browser/index.cjs +135 -0
  478. package/dist/v1/webTemplates/media/MediaValidator/v1/Browser/index.d.mts +83 -0
  479. package/dist/v1/webTemplates/media/MediaValidator/v1/Browser/index.d.mts.map +1 -0
  480. package/dist/v1/webTemplates/media/MediaValidator/v1/Browser/index.mjs +120 -0
  481. package/dist/v1/webTemplates/media/MediaValidator/v1/Node/index.cjs +127 -0
  482. package/dist/v1/webTemplates/media/MediaValidator/v1/Node/index.d.mts +95 -0
  483. package/dist/v1/webTemplates/media/MediaValidator/v1/Node/index.d.mts.map +1 -0
  484. package/dist/v1/webTemplates/media/MediaValidator/v1/Node/index.mjs +109 -0
  485. package/dist/v1/webTemplates/multiformats/14.0/IPFSHasher/index.cjs +83 -0
  486. package/dist/v1/webTemplates/multiformats/14.0/IPFSHasher/index.d.mts +46 -0
  487. package/dist/v1/webTemplates/multiformats/14.0/IPFSHasher/index.d.mts.map +1 -0
  488. package/dist/v1/webTemplates/multiformats/14.0/IPFSHasher/index.mjs +71 -0
  489. package/dist/v1/webTemplates/vite/7.3/plugins/TinyVitePwaPlugin.cjs +493 -0
  490. package/dist/v1/webTemplates/vite/7.3/plugins/TinyVitePwaPlugin.d.mts +67 -0
  491. package/dist/v1/webTemplates/vite/7.3/plugins/TinyVitePwaPlugin.d.mts.map +1 -0
  492. package/dist/v1/webTemplates/vite/7.3/plugins/TinyVitePwaPlugin.mjs +399 -0
  493. package/dist/v1/webTemplates/vite/7.3/plugins/githubUtils.cjs +35 -0
  494. package/dist/v1/webTemplates/vite/7.3/plugins/githubUtils.d.mts +6 -0
  495. package/dist/v1/webTemplates/vite/7.3/plugins/githubUtils.d.mts.map +1 -0
  496. package/dist/v1/webTemplates/vite/7.3/plugins/githubUtils.mjs +29 -0
  497. package/docs/info/RegExp.md +74 -0
  498. package/docs/v1/Exports.md +111 -0
  499. package/docs/v1/README.md +117 -13
  500. package/docs/v1/basics/asyncReplace.md +2 -0
  501. package/docs/v1/basics/browserDetector.md +28 -11
  502. package/docs/v1/basics/crypto.md +85 -0
  503. package/docs/v1/basics/deprecated/html.md +2 -0
  504. package/docs/v1/basics/deprecated/objFilter.md +2 -0
  505. package/docs/v1/basics/html.md +2 -0
  506. package/docs/v1/basics/objChecker.md +55 -0
  507. package/docs/v1/basics/objFilter.md +57 -86
  508. package/docs/v1/basics/promiseUtils.md +12 -3
  509. package/docs/v1/basics/text.md +0 -1
  510. package/docs/v1/libs/array/TinyArrayComparator.md +50 -39
  511. package/docs/v1/libs/fs/TinyFS.md +752 -0
  512. package/docs/v1/libs/fs/TinyOPFSFileSystem.md +495 -0
  513. package/docs/v1/libs/fs/TinyStorageBucketFileSystem.md +407 -0
  514. package/docs/v1/libs/plugin/TinyPlugin.md +441 -0
  515. package/docs/v1/libs/plugin/TinyVersion.md +145 -0
  516. package/docs/v1/libs/router/TinyRouter.md +216 -0
  517. package/docs/v1/libs/storage/TinyMapCache.md +219 -0
  518. package/docs/v1/libs/storage/TinySetMapDatabase.md +478 -0
  519. package/docs/v1/libs/sw/browser/TinyNotificationAdapter.md +280 -0
  520. package/docs/v1/libs/sw/browser/TinyPushManager.md +782 -0
  521. package/docs/v1/libs/sw/browser/TinyServiceWorker.md +365 -0
  522. package/docs/v1/libs/sw/browser/plugins/TabManager.md +133 -0
  523. package/docs/v1/libs/sw/service/TinyPushRouter.md +314 -0
  524. package/docs/v1/libs/sw/service/TinyServiceWorkerEngine.md +748 -0
  525. package/docs/v1/libs/sw/service/plugins/GlobBypassPlugin.md +229 -0
  526. package/docs/v1/libs/sw/service/plugins/GlobCachePlugin.md +91 -0
  527. package/docs/v1/libs/sw/service/plugins/Ping.md +36 -0
  528. package/docs/v1/libs/sw/service/plugins/TabManager.md +64 -0
  529. package/docs/v1/libs/sw/service/plugins/ViteFileDetector.md +92 -0
  530. package/docs/v1/libs/sw/service/shared/TinyPushPayload.md +316 -0
  531. package/docs/v1/libs/text/TinyI18.md +80 -56
  532. package/docs/v1/libs/tools/TinyBrowserMonitor.md +170 -0
  533. package/docs/v1/libs/tools/TinyDebugger.md +117 -1
  534. package/docs/v1/libs/tools/TinyHtmlTagRegexBuilder.md +141 -0
  535. package/docs/v1/libs/tools/TinyHttpResponseRegistry.md +129 -0
  536. package/docs/v1/libs/tools/TinyPasswordValidator.md +153 -0
  537. package/docs/v1/libs/tools/TinyPkgExportValidator.md +45 -1
  538. package/docs/v1/libs/tools/TinySiteMap.md +134 -0
  539. package/docs/v1/libs/tools/TinySiteMapStream.md +97 -0
  540. package/docs/v1/libs/tools/TinyURLSecurityVerifier.md +146 -0
  541. package/docs/v1/libs/tools/TinyUriParser/Discord.md +374 -0
  542. package/docs/v1/libs/tools/TinyUriParser/MatrixProtocol.md +54 -0
  543. package/docs/v1/libs/tools/TinyUriParser.md +128 -0
  544. package/docs/v1/libs/utils/TinyCloner.md +126 -0
  545. package/docs/v1/libs/utils/TinyPromiseQueue.md +1 -1
  546. package/docs/v1/libs/utils/TinyThrottledApi.md +219 -0
  547. package/docs/v1/libs/worker/browser/TinyWebWorker.md +331 -0
  548. package/docs/v1/libs/worker/engine/TinyWebWorkerEngine.md +137 -0
  549. package/docs/v1/regexp/Email.md +127 -0
  550. package/docs/v1/regexp/Glob.md +155 -0
  551. package/docs/v1/regexp/IPv4.md +103 -0
  552. package/docs/v1/regexp/IPv6.md +122 -0
  553. package/docs/v1/regexp/SegmentExtractor.md +120 -0
  554. package/docs/v1/regexp/UrlDetector.md +117 -0
  555. package/docs/v1/regexp/Username/Base.md +131 -0
  556. package/docs/v1/regexp/Username/Discord.md +267 -0
  557. package/docs/v1/regexp/Username/MatrixProtocol.md +131 -0
  558. package/docs/v1/webTemplates/ag-psd/31.0/ProcessSolidFilters.md +569 -0
  559. package/docs/v1/webTemplates/bootstrap/5.3/html/BootstrapDialogs.md +174 -0
  560. package/docs/v1/webTemplates/media/MediaValidator/v1/Browser.md +123 -0
  561. package/docs/v1/webTemplates/media/MediaValidator/v1/Node.md +136 -0
  562. package/docs/v1/webTemplates/multiformats/14.0/IPFSHasher.md +310 -0
  563. package/docs/v1/webTemplates/vite/7.3/plugins/TinyVitePwaPlugin.md +131 -0
  564. package/docs/v1/webTemplates/vite/7.3/plugins/githubUtils.md +50 -0
  565. package/package.json +313 -18
  566. package/types/EventEmitter.d.ts +48 -0
  567. package/dist/v1/libs/text/TinyI18.d.mts.map +0 -1
@@ -0,0 +1,782 @@
1
+ # 📬 TinyPushManager
2
+
3
+ > A tiny, framework-agnostic, zero-dependency wrapper around the browser **Push API**.
4
+ > It owns the full `PushSubscription` lifecycle and keeps your backend in sync.
5
+
6
+ [![No dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](#-installation)
7
+ [![Module](https://img.shields.io/badge/module-ESM-blue)](#-installation)
8
+ [![License](https://img.shields.io/badge/license-MIT-black)](#-license)
9
+
10
+ ---
11
+
12
+ ## 📖 Table of Contents
13
+
14
+ - [What is this?](#-what-is-this)
15
+ - [Mental model](#-mental-model)
16
+ - [Installation](#-installation)
17
+ - [Quick start](#-quick-start)
18
+ - [The two files you must not forget](#-the-two-files-you-must-not-forget)
19
+ - [API reference](#-api-reference)
20
+ - [Static members](#static-members)
21
+ - [Instance members](#instance-members)
22
+ - [State machine](#-state-machine)
23
+ - [Type definitions](#-type-definitions)
24
+ - [Recipes](#-recipes)
25
+ - [Vanilla JS](#recipe-1--vanilla-js)
26
+ - [React hook](#recipe-2--react-hook)
27
+ - [Service worker](#recipe-3--service-worker)
28
+ - [Backend contract](#recipe-4--backend-contract)
29
+ - [Security checklist](#-security-checklist)
30
+ - [Apache2 reverse proxy](#-apache2-reverse-proxy)
31
+ - [Troubleshooting](#-troubleshooting)
32
+ - [Testing](#-testing)
33
+
34
+ ---
35
+
36
+ ## ✨ What is this?
37
+
38
+ `TinyPushManager` is a single class that answers one question:
39
+
40
+ > *"Is this browser subscribed to push notifications, and how do I keep my
41
+ > backend in sync with that answer?"*
42
+
43
+ It does **not** talk to a push service. It never touches FCM, Mozilla
44
+ autopush or APNs. It delegates:
45
+
46
+ | Responsibility | Delegated to |
47
+ | --- | --- |
48
+ | Asking the user for permission | `Notification.requestPermission()` |
49
+ | Creating / destroying the subscription | `PushManager` |
50
+ | Persisting the subscription | **Your** `POST` / `DELETE` endpoints |
51
+ | Rendering the notification | **Your** service worker |
52
+
53
+ That is the whole point: the manager is a thin, testable, observable state
54
+ machine on top of a very stateful browser API.
55
+
56
+ ---
57
+
58
+ ## 🧠 Mental model
59
+
60
+ ```
61
+ ┌───────────────┐
62
+ │ Your UI │
63
+ └───────┬───────┘
64
+ │ 1. subscribe() / unsubscribe()
65
+ ▼
66
+ ┌───────────────────────┐ 2. PushManager.subscribe()
67
+ │ TinyPushManager │──────────────────────────────▶ 🌐 Browser
68
+ │ (page context only) │◀──────────────────────────────
69
+ └───────┬───────────────┘ 3. PushSubscription
70
+ │
71
+ │ 4. POST /api/push (your backend)
72
+ ▼
73
+ ┌───────────────┐
74
+ │ Your server │──── 5. web-push / FCM ──▶ 📱 Device
75
+ └───────────────┘
76
+ ```
77
+
78
+ **Golden rule:** the browser is the source of truth for the subscription.
79
+ Your backend is a *replica*. When they disagree, the browser wins — that is
80
+ what `syncWithServer()` is for.
81
+
82
+ ---
83
+
84
+ ## 📦 Installation
85
+
86
+ The manager is a single ES module with **zero runtime dependencies**.
87
+
88
+ ```text
89
+ src/
90
+ ├── push/
91
+ │ ├── tiny-push-manager.mjs # 👈 the class documented here
92
+ │ └── utils.mjs # exports requestNotificationPermission()
93
+ └── utils.mjs # exports PUSH_TYPE
94
+ ```
95
+
96
+ ```js
97
+ import TinyPushManager from 'tiny-essentials/libs/sw/browser/TinyPushManager';
98
+ ```
99
+
100
+ > 📌 **Assumption:** `PUSH_TYPE` is a project constant
101
+ > re-exported as `TinyPushManager.PUSH_TYPE`. It is meant to be shared with the
102
+ > service worker so both sides agree on the payload shape.
103
+
104
+ ---
105
+
106
+ ## 🚀 Quick start
107
+
108
+ ```js
109
+ import TinyPushManager from 'tiny-essentials/libs/sw/browser/TinyPushManager';
110
+
111
+ // 1. Fail fast with a friendly message.
112
+ if (!TinyPushManager.isSupported()) {
113
+ console.warn('Push notifications are not available in this browser.');
114
+ }
115
+
116
+ // 2. Create one manager per page.
117
+ const push = new TinyPushManager({
118
+ registration: await navigator.serviceWorker.ready,
119
+ vapidPublicKey: import.meta.env.VITE_VAPID_PUBLIC_KEY,
120
+ endpoints: {
121
+ subscribe: '/api/push',
122
+ unsubscribe: '/api/push',
123
+ },
124
+ onChange: (state) => {
125
+ document.body.dataset.pushStatus = state.status;
126
+ },
127
+ });
128
+
129
+ // 3. Subscribe. Must be called from a user gesture on iOS Safari.
130
+ document.querySelector('#notify-me').addEventListener('click', async () => {
131
+ try {
132
+ await push.subscribe();
133
+ } catch (error) {
134
+ console.error(error.message);
135
+ }
136
+ });
137
+ ```
138
+
139
+ ---
140
+
141
+ ## 🧩 The two files you must not forgetZ
142
+
143
+ `TinyPushManager.requestPermission()` is a thin, testable wrapper. Keep the
144
+ browser call isolated so it can be mocked:
145
+
146
+ ```js
147
+ /**
148
+ * Requests the notification permission from the user.
149
+ *
150
+ * @returns {Promise<NotificationPermission>} The resulting permission.
151
+ */
152
+ export async function requestNotificationPermission() {
153
+ return Notification.requestPermission();
154
+ }
155
+ ```
156
+
157
+ ### 2. `sw.js` — the service worker
158
+
159
+ The manager never renders a notification. If your service worker does not call
160
+ `showNotification()`, Chrome will show a generic "This site has been updated in
161
+ the background" message and may revoke the subscription.
162
+
163
+ ```js
164
+ self.addEventListener('push', (event) => {
165
+ const payload = event.data ? event.data.json() : {};
166
+
167
+ event.waitUntil(
168
+ self.registration.showNotification(payload.title ?? 'New message', {
169
+ body: payload.body ?? '',
170
+ icon: '/icons/icon-192.png',
171
+ data: { url: payload.url ?? '/' },
172
+ }),
173
+ );
174
+ });
175
+
176
+ self.addEventListener('notificationclick', (event) => {
177
+ event.notification.close();
178
+ event.waitUntil(self.clients.openWindow(event.notification.data.url));
179
+ });
180
+ ```
181
+
182
+ ---
183
+
184
+ ## 📚 API reference
185
+
186
+ ### Static members
187
+
188
+ #### `TinyPushManager.PUSH_TYPE` — `*` *(read-only)*
189
+
190
+ The push payload discriminator. Use it in the
191
+ service worker to ignore messages that are not yours.
192
+
193
+ ```js
194
+ if (payload.type !== PUSH_TYPE) return;
195
+ ```
196
+
197
+ ---
198
+
199
+ #### `TinyPushManager.isSupported()` → `boolean`
200
+
201
+ Returns `true` only when **all** of the following are true:
202
+
203
+ - `navigator.serviceWorker` exists,
204
+ - `PushManager` exists on `globalThis`,
205
+ - `Notification` exists on `globalThis`.
206
+
207
+ ```js
208
+ if (!TinyPushManager.isSupported()) {
209
+ // Hide the "Enable notifications" button entirely.
210
+ }
211
+ ```
212
+
213
+ ---
214
+
215
+ #### `TinyPushManager.permission` → `NotificationPermission`
216
+
217
+ A **synchronous** getter for the current permission. Returns `'denied'` when
218
+ the Push API is unsupported, so you can always render it safely.
219
+
220
+ ```js
221
+ switch (TinyPushManager.permission) {
222
+ case 'granted':
223
+ break;
224
+ case 'denied':
225
+ renderBlockedBanner();
226
+ break;
227
+ default:
228
+ renderOptInButton();
229
+ }
230
+ ```
231
+
232
+ ---
233
+
234
+ #### `TinyPushManager.requestPermission()` → `Promise<NotificationPermission>`
235
+
236
+ | | |
237
+ | --- | --- |
238
+ | **Returns** | The resulting permission. |
239
+ | **Throws** | `Error` when the Push API is not supported. |
240
+
241
+ > ⚠️ On iOS Safari this **must** be called from a user gesture, otherwise the
242
+ > promise resolves to `'denied'` and the user never sees a prompt.
243
+
244
+ ---
245
+
246
+ #### `TinyPushManager.decodeVapidKey(base64)` → `BufferSource`
247
+
248
+ Decodes a Base64 **URL-safe** VAPID public key into a `Uint8Array`.
249
+ The result is **memoized**: calling it twice with the same string returns the
250
+ exact same `Uint8Array` instance.
251
+
252
+ | Parameter | Type | Description |
253
+ | --- | --- | --- |
254
+ | `base64` | `string` | A non-empty Base64 URL-safe VAPID public key. |
255
+
256
+ | | |
257
+ | --- | --- |
258
+ | **Returns** | `BufferSource` — the decoded key. |
259
+ | **Throws** | `TypeError` if `base64` is not a non-empty string. |
260
+ | **Throws** | `DOMException` (`InvalidCharacterError`) if the key is not valid Base64. |
261
+
262
+ ```js
263
+ const key = TinyPushManager.decodeVapidKey(process.env.VAPID_PUBLIC_KEY);
264
+ console.log(key instanceof Uint8Array); // true
265
+ ```
266
+
267
+ ---
268
+
269
+ ### Instance members
270
+
271
+ #### `new TinyPushManager(options)`
272
+
273
+ | Option | Type | Required | Default | Description |
274
+ | --- | --- | --- | --- | --- |
275
+ | `registration` | `ServiceWorkerRegistration` | ✅ | — | The active registration. |
276
+ | `vapidPublicKey` | `string` | ✅ | — | Base64 URL-safe VAPID **public** key. |
277
+ | `endpoints` | `TinyPushEndpoints` | ❌ | `{}` | Backend routes. |
278
+ | `fetch` | `typeof fetch` | ❌ | `globalThis.fetch` | Injectable for tests. |
279
+ | `onChange` | `(state: TinyPushState) => void` | ❌ | `null` | State observer. |
280
+
281
+ **Throws** `TypeError` if `options` is not a plain object, if `registration` is
282
+ not an object, if `vapidPublicKey` is not a non-empty string, if
283
+ `endpoints.subscribe` / `endpoints.unsubscribe` are not strings, or if
284
+ `onChange` is not a function.
285
+
286
+ ```js
287
+ const push = new TinyPushManager({
288
+ registration: await navigator.serviceWorker.ready,
289
+ vapidPublicKey: VAPID_PUBLIC_KEY,
290
+ endpoints: { subscribe: '/api/push', unsubscribe: '/api/push' },
291
+ onChange: ({ status, error }) => console.log(status, error),
292
+ });
293
+ ```
294
+
295
+ ---
296
+
297
+ #### `get state` → `TinyPushState`
298
+
299
+ A **frozen** snapshot of the current state. Mutating it does nothing.
300
+
301
+ ```js
302
+ const { status, subscription, error } = push.state;
303
+ ```
304
+
305
+ ---
306
+
307
+ #### `getSubscription()` → `Promise<PushSubscription | null>`
308
+
309
+ Reads the subscription from the browser. Returns `null` when the Push API is
310
+ unsupported. **Does not** hit your backend and **does not** change the state.
311
+
312
+ ```js
313
+ const subscription = await push.getSubscription();
314
+ if (subscription) {
315
+ console.log(subscription.endpoint);
316
+ }
317
+ ```
318
+
319
+ ---
320
+
321
+ #### `subscribe(options?)` → `Promise<PushSubscription>`
322
+
323
+ The main entry point. Performs, in order:
324
+
325
+ 1. Throws if the Push API is unsupported.
326
+ 2. Sets `status` to `'pending'`.
327
+ 3. Requests the notification permission.
328
+ 4. Sets `status` to `'denied'` and throws if not granted.
329
+ 5. Reuses the existing subscription, or creates a new one.
330
+ 6. Uploads it to `endpoints.subscribe` (if configured).
331
+ 7. Sets `status` to `'subscribed'`.
332
+
333
+ | Parameter | Type | Description |
334
+ | --- | --- | --- |
335
+ | `options` | `PushSubscriptionOptionsInit` | Merged into `pushManager.subscribe()`. |
336
+
337
+ > 🔒 `userVisibleOnly: true` and `applicationServerKey` are **always** forced
338
+ > by the manager and cannot be overridden.
339
+
340
+ | | |
341
+ | --- | --- |
342
+ | **Returns** | The active `PushSubscription`. |
343
+ | **Throws** | `Error` if unsupported, denied, or if the upload fails. |
344
+
345
+ ```js
346
+ try {
347
+ const subscription = await push.subscribe();
348
+ console.log('Subscribed:', subscription.endpoint);
349
+ } catch (error) {
350
+ console.error('Could not subscribe:', error.message);
351
+ }
352
+ ```
353
+
354
+ ---
355
+
356
+ #### `unsubscribe()` → `Promise<boolean>`
357
+
358
+ 1. Deletes the subscription on the backend (fire-and-forget, errors swallowed).
359
+ 2. Calls `PushSubscription.unsubscribe()`.
360
+ 3. Sets `status` to `'unsubscribed'`.
361
+
362
+ | | |
363
+ | --- | --- |
364
+ | **Returns** | `true` when a subscription was removed, `false` otherwise. |
365
+
366
+ > 🧯 Backend errors are **intentionally ignored**. A user must always be able to
367
+ > unsubscribe locally, even when the network is down. Reconcile later with a
368
+ > cron job that prunes `410 Gone` endpoints.
369
+
370
+ ```js
371
+ const removed = await push.unsubscribe();
372
+ ```
373
+
374
+ ---
375
+
376
+ #### `syncWithServer()` → `Promise<PushSubscription | null>`
377
+
378
+ Re-uploads the current subscription. Call it:
379
+
380
+ - after a `pushsubscriptionchange` event,
381
+ - after the user logs in on a new device,
382
+ - when your backend lost its database.
383
+
384
+ | | |
385
+ | --- | --- |
386
+ | **Returns** | The refreshed subscription, or `null` if there is none. |
387
+
388
+ ```js
389
+ await push.syncWithServer();
390
+ ```
391
+
392
+ ---
393
+
394
+ ### 🔄 State machine
395
+
396
+ ```
397
+ ┌────────────────┐
398
+ │ unsubscribed │◀──────────────┐
399
+ └───────┬────────┘ │
400
+ │ subscribe() │
401
+ ▼ │
402
+ ┌────────────────┐ │
403
+ │ pending │ │
404
+ └───┬────────┬───┘ │
405
+ granted │ │ denied │
406
+ ▼ ▼ │
407
+ ┌──────────────┐ ┌──────────┐ │
408
+ │ subscribed │ │ denied │ │
409
+ └──────┬───────┘ └──────────┘ │
410
+ │ unsubscribe() │
411
+ └────────────────────────────────┘
412
+ ```
413
+
414
+ | From | Call | To | Notes |
415
+ | --- | --- | --- | --- |
416
+ | `unsubscribed` | `subscribe()` | `pending` → `subscribed` | Happy path. |
417
+ | `unsubscribed` | `subscribe()` | `pending` → `denied` | Throws. |
418
+ | `subscribed` | `unsubscribe()` | `unsubscribed` | Returns `true`. |
419
+ | `unsubscribed` | `unsubscribe()` | `unsubscribed` | Returns `false`. |
420
+ | `subscribed` | `syncWithServer()` | `subscribed` | Re-uploads. |
421
+ | any | `subscribe()` + upload failure | `pending` | ⚠️ See [gotchas](#-known-gotchas). |
422
+
423
+ ---
424
+
425
+ ### 🧾 Type definitions
426
+
427
+ #### `TinyPushEndpoints`
428
+
429
+ | Property | Type | Description |
430
+ | --- | --- | --- |
431
+ | `subscribe` | `string \| undefined` | `POST` route that persists a subscription. |
432
+ | `unsubscribe` | `string \| undefined` | `DELETE` route that removes a subscription. |
433
+
434
+ #### `TinyPushManagerOptions`
435
+
436
+ | Property | Type | Description |
437
+ | --- | --- | --- |
438
+ | `registration` | `ServiceWorkerRegistration` | The active registration. |
439
+ | `vapidPublicKey` | `string` | Base64 URL-safe VAPID public key. |
440
+ | `endpoints` | `TinyPushEndpoints \| undefined` | Backend routes. |
441
+ | `fetch` | `typeof fetch \| undefined` | Injectable for tests. |
442
+ | `onChange` | `((state: TinyPushState) => void) \| undefined` | State observer. |
443
+
444
+ #### `TinyPushState`
445
+
446
+ | Property | Type | Description |
447
+ | --- | --- | --- |
448
+ | `status` | `'unsupported' \| 'unsubscribed' \| 'subscribed' \| 'denied' \| 'pending'` | Current status. |
449
+ | `subscription` | `PushSubscription \| null` | The active subscription, when any. |
450
+ | `error` | `string \| null` | The last error message, when any. |
451
+
452
+ ---
453
+
454
+ ## 🍳 Recipes
455
+
456
+ ### Recipe 1 — Vanilla JS
457
+
458
+ ```js
459
+ const push = new TinyPushManager({
460
+ registration: await navigator.serviceWorker.ready,
461
+ vapidPublicKey: VAPID_PUBLIC_KEY,
462
+ endpoints: { subscribe: '/api/push', unsubscribe: '/api/push' },
463
+ onChange: ({ status }) => {
464
+ button.disabled = status === 'pending';
465
+ button.textContent = status === 'subscribed' ? 'Disable' : 'Enable';
466
+ },
467
+ });
468
+
469
+ button.addEventListener('click', async () => {
470
+ const isSubscribed = push.state.status === 'subscribed';
471
+ await (isSubscribed ? push.unsubscribe() : push.subscribe());
472
+ });
473
+ ```
474
+
475
+ ---
476
+
477
+ ### Recipe 2 — React hook
478
+
479
+ ```jsx
480
+ import { useCallback, useEffect, useRef, useState } from 'react';
481
+ import TinyPushManager from 'tiny-essentials/libs/sw/browser/TinyPushManager';
482
+
483
+ /**
484
+ * @typedef {Object} UsePushResult
485
+ * @property {import('tiny-essentials/libs/sw/browser/TinyPushManager').TinyPushState} state - The manager state.
486
+ * @property {boolean} isSupported - Whether the Push API is available.
487
+ * @property {() => Promise<void>} toggle - Subscribes or unsubscribes.
488
+ */
489
+
490
+ /**
491
+ * Binds a `TinyPushManager` instance to a React component.
492
+ *
493
+ * @param {Object} options - The hook options.
494
+ * @param {string} options.vapidPublicKey - The VAPID public key.
495
+ * @param {string} options.endpoint - The backend route for both verbs.
496
+ * @returns {UsePushResult} The current state and the toggle callback.
497
+ */
498
+ export function usePush({ vapidPublicKey, endpoint }) {
499
+ const managerRef = useRef(null);
500
+ const [state, setState] = useState({
501
+ status: 'unsubscribed',
502
+ subscription: null,
503
+ error: null,
504
+ });
505
+
506
+ useEffect(() => {
507
+ let cancelled = false;
508
+
509
+ navigator.serviceWorker.ready.then((registration) => {
510
+ if (cancelled) return;
511
+
512
+ managerRef.current = new TinyPushManager({
513
+ registration,
514
+ vapidPublicKey,
515
+ endpoints: { subscribe: endpoint, unsubscribe: endpoint },
516
+ onChange: setState,
517
+ });
518
+
519
+ setState(managerRef.current.state);
520
+ });
521
+
522
+ return () => {
523
+ cancelled = true;
524
+ };
525
+ }, [vapidPublicKey, endpoint]);
526
+
527
+ const toggle = useCallback(async () => {
528
+ const manager = managerRef.current;
529
+ if (!manager) return;
530
+
531
+ if (manager.state.status === 'subscribed') {
532
+ await manager.unsubscribe();
533
+ } else {
534
+ await manager.subscribe();
535
+ }
536
+ }, []);
537
+
538
+ return { state, isSupported: TinyPushManager.isSupported(), toggle };
539
+ }
540
+ ```
541
+
542
+ ---
543
+
544
+ ### Recipe 3 — Service worker
545
+
546
+ Handle `pushsubscriptionchange` so a rotated endpoint never goes stale:
547
+
548
+ ```js
549
+ self.addEventListener('pushsubscriptionchange', (event) => {
550
+ event.waitUntil(
551
+ (async () => {
552
+ const subscription = await self.registration.pushManager.subscribe(
553
+ event.oldSubscription.options,
554
+ );
555
+
556
+ await fetch('/api/push', {
557
+ method: 'POST',
558
+ headers: { 'Content-Type': 'application/json' },
559
+ body: JSON.stringify(subscription.toJSON()),
560
+ });
561
+ })(),
562
+ );
563
+ });
564
+ ```
565
+
566
+ ---
567
+
568
+ ### Recipe 4 — Backend contract
569
+
570
+ #### `POST /api/push`
571
+
572
+ ```jsonc
573
+ // Request
574
+ {
575
+ "endpoint": "https://fcm.googleapis.com/fcm/send/abc123",
576
+ "expirationTime": null,
577
+ "keys": {
578
+ "p256dh": "BNc...",
579
+ "auth": "k9F..."
580
+ }
581
+ }
582
+ ```
583
+
584
+ | Status | Meaning |
585
+ | --- | --- |
586
+ | `201` | Subscription stored. |
587
+ | `400` | Malformed body. |
588
+ | `401` | Not authenticated. |
589
+ | `429` | Rate limited. |
590
+
591
+ #### `DELETE /api/push`
592
+
593
+ ```jsonc
594
+ { "endpoint": "https://fcm.googleapis.com/fcm/send/abc123" }
595
+ ```
596
+
597
+ | Status | Meaning |
598
+ | --- | --- |
599
+ | `204` | Removed (idempotent). |
600
+ | `400` | Malformed body. |
601
+ | `401` | Not authenticated. |
602
+
603
+ ---
604
+
605
+ ## 🔒 Security checklist
606
+
607
+ The manager handles the **browser** half. The backend half is on you.
608
+
609
+ - [ ] **Authenticate both routes.** A session cookie plus a CSRF token, or a
610
+ short-lived Bearer token. An unauthenticated `POST /api/push` is an open
611
+ relay.
612
+ - [ ] **Never trust a `userId` in the body.** Derive the owner from the session
613
+ and store `(user_id, endpoint)` with a `UNIQUE` constraint.
614
+ - [ ] **Validate the endpoint URL.** Reject anything that is not `https:`:
615
+
616
+ ```js
617
+ const url = new URL(req.body.endpoint);
618
+ if (url.protocol !== 'https:') {
619
+ return res.status(400).json({ error: 'Invalid endpoint.' });
620
+ }
621
+ ```
622
+
623
+ - [ ] **Never `fetch()` a client-supplied endpoint** without an allowlist.
624
+ That is a Server-Side Request Forgery (SSRF) vector.
625
+ - [ ] **Rate limit** by session and by IP. Subscribing is cheap; spamming is not.
626
+ - [ ] **Keep the VAPID private key server-side only.** Only the public key
627
+ reaches the browser, and it is not a secret.
628
+ - [ ] **Prune dead subscriptions.** When a push returns `404` or `410`, delete
629
+ the row.
630
+ - [ ] **Serve over HTTPS.** Service workers require a secure context
631
+ (`localhost` is the only exception).
632
+ - [ ] **Add a Content-Security-Policy** that allows your API origin:
633
+
634
+ ```http
635
+ Content-Security-Policy: default-src 'self'; connect-src 'self' https://api.example.com
636
+ ```
637
+
638
+ ---
639
+
640
+ ## 🪶 Apache2 reverse proxy
641
+
642
+ Enable the required modules once:
643
+
644
+ ```bash
645
+ sudo a2enmod proxy proxy_http headers ssl
646
+ sudo systemctl reload apache2
647
+ ```
648
+
649
+ ```apache
650
+ <VirtualHost *:443>
651
+ ServerName app.example.com
652
+
653
+ SSLEngine on
654
+ SSLCertificateFile /etc/letsencrypt/live/app.example.com/fullchain.pem
655
+ SSLCertificateKeyFile /etc/letsencrypt/live/app.example.com/privkey.pem
656
+
657
+ # The service worker must never be served from a stale cache.
658
+ <Files "sw.js">
659
+ Header always set Cache-Control "no-cache, no-store, must-revalidate"
660
+ </Files>
661
+
662
+ # The push API must never be cached by an intermediary.
663
+ <Location "/api/push">
664
+ Header always set Cache-Control "no-store"
665
+ Require all granted
666
+ </Location>
667
+
668
+ ProxyPreserveHost On
669
+ RequestHeader set X-Forwarded-Proto "https"
670
+
671
+ ProxyPass /api/ http://127.0.0.1:3000/api/
672
+ ProxyPassReverse /api/ http://127.0.0.1:3000/api/
673
+
674
+ ProxyPass / http://127.0.0.1:5173/
675
+ ProxyPassReverse / http://127.0.0.1:5173/
676
+
677
+ Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
678
+ Header always set X-Content-Type-Options "nosniff"
679
+ Header always set Referrer-Policy "strict-origin-when-cross-origin"
680
+ </VirtualHost>
681
+ ```
682
+
683
+ ---
684
+
685
+ ## 🩺 Troubleshooting
686
+
687
+ | Symptom | Likely cause | Fix |
688
+ | --- | --- | --- |
689
+ | `Push API is not supported in this browser.` | No service worker, no `PushManager`, or an insecure context. | Serve over HTTPS. |
690
+ | `Notification permission was not granted: denied.` | The user blocked notifications. | Reset the permission in the site settings. |
691
+ | `Failed to upload subscription: 401.` | The session expired between page load and the click. | Re-authenticate, then call `syncWithServer()`. |
692
+ | `TypeError: decodeVapidKey: base64 must be a non-empty string.` | The VAPID key is `undefined`. | Check your environment variable at build time. |
693
+ | `InvalidCharacterError` from `atob`. | You passed the **private** key, or a PEM file. | Use the Base64 URL-safe **public** key. |
694
+ | `AbortError: Registration failed - permission denied`. | `subscribe()` was called without a user gesture. | Wrap it in a `click` handler. |
695
+ | The state is stuck on `'pending'`. | `#upload()` threw. | See [gotchas](#-known-gotchas). |
696
+ | The notification never appears. | The service worker does not call `showNotification()`. | See [the service worker recipe](#recipe-3--service-worker). |
697
+
698
+ ---
699
+
700
+ ## ⚠️ Known gotchas
701
+
702
+ ### 1. A failed upload leaves the state on `'pending'`
703
+
704
+ `subscribe()` sets `status: 'pending'` **before** awaiting the backend. If
705
+ `#upload()` throws, the error propagates and the state is never advanced.
706
+
707
+ ```js
708
+ try {
709
+ await push.subscribe();
710
+ } catch (error) {
711
+ // The browser IS subscribed, but the backend does not know yet.
712
+ // Retry the upload without asking for permission again:
713
+ await push.syncWithServer();
714
+ }
715
+ ```
716
+
717
+ ### 2. `isSupported()` throws in Node.js < 21
718
+
719
+ The guard is `'serviceWorker' in navigator`. In a server-side rendering
720
+ environment where `navigator` is not defined at all, this throws a
721
+ `ReferenceError` before the check can return `false`.
722
+
723
+ ```js
724
+ // Safe guard for SSR:
725
+ const canUsePush = typeof window !== 'undefined' && TinyPushManager.isSupported();
726
+ ```
727
+
728
+ ### 3. `decodeVapidKey()` caches exactly one key
729
+
730
+ The memoization stores a single `(source, key)` pair. If you rotate keys at
731
+ runtime, the first call after the rotation is a cache miss, which is correct —
732
+ just be aware that it is not an LRU cache.
733
+
734
+ ### 4. `unsubscribe()` swallows backend errors
735
+
736
+ This is deliberate. A user must always be able to unsubscribe locally. Schedule
737
+ a reconciliation job to delete orphaned rows.
738
+
739
+ ### 5. `syncWithServer()` is a no-op when there is no subscription
740
+
741
+ It returns `null` and does **not** reset the state. If you need a hard reset,
742
+ recreate the manager.
743
+
744
+ ---
745
+
746
+ ## 🧪 Testing
747
+
748
+ Inject `fetch` to test without a network:
749
+
750
+ ```js
751
+ import { describe, expect, it, vi } from 'vitest';
752
+ import TinyPushManager from 'tiny-essentials/libs/sw/browser/TinyPushManager';
753
+
754
+ describe('TinyPushManager', () => {
755
+ it('throws when the VAPID key is missing', () => {
756
+ expect(
757
+ () => new TinyPushManager({ registration: {}, vapidPublicKey: '' }),
758
+ ).toThrow(TypeError);
759
+ });
760
+
761
+ it('uploads the subscription on subscribe()', async () => {
762
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 201 });
763
+ const subscription = { toJSON: () => ({ endpoint: 'https://push.test/1' }) };
764
+
765
+ const manager = new TinyPushManager({
766
+ registration: {
767
+ pushManager: { getSubscription: vi.fn().mockResolvedValue(subscription) },
768
+ },
769
+ vapidPublicKey: 'AAAA',
770
+ endpoints: { subscribe: '/api/push' },
771
+ fetch: fetchMock,
772
+ });
773
+
774
+ await manager.syncWithServer();
775
+
776
+ expect(fetchMock).toHaveBeenCalledWith(
777
+ '/api/push',
778
+ expect.objectContaining({ method: 'POST' }),
779
+ );
780
+ });
781
+ });
782
+ ```