tempest-react-sdk 0.80.0 → 0.82.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 (247) hide show
  1. package/README.md +40 -10
  2. package/bin/create-tempest-app.e2e.test.mjs +31 -0
  3. package/bin/create-tempest-app.mjs +11 -1
  4. package/bin/lib/component/generate.test.mjs +13 -1
  5. package/bin/lib/doctor/doctor.e2e.test.mjs +29 -0
  6. package/bin/lib/doctor/vitest.mjs +120 -0
  7. package/bin/lib/doctor/vitest.test.mjs +139 -0
  8. package/bin/lib/fs/contain.mjs +7 -0
  9. package/bin/lib/openapi/escape.mjs +68 -4
  10. package/bin/lib/openapi/generate.hostile.test.mjs +150 -1
  11. package/bin/lib/openapi/generate.mjs +78 -13
  12. package/bin/lib/openapi/generate.test.mjs +13 -1
  13. package/bin/lib/openapi/pattern-risk.mjs +134 -0
  14. package/bin/lib/openapi/pattern-risk.test.mjs +45 -0
  15. package/bin/tempest-gen.test.mjs +100 -0
  16. package/bin/tempest.mjs +24 -8
  17. package/dist/access/use-can.cjs +1 -1
  18. package/dist/access/use-can.cjs.map +1 -1
  19. package/dist/access/use-can.js +25 -28
  20. package/dist/access/use-can.js.map +1 -1
  21. package/dist/auth/create-tempest-auth.cjs +1 -1
  22. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  23. package/dist/auth/create-tempest-auth.js +234 -122
  24. package/dist/auth/create-tempest-auth.js.map +1 -1
  25. package/dist/auth/session-identity.cjs +2 -0
  26. package/dist/auth/session-identity.cjs.map +1 -0
  27. package/dist/auth/session-identity.js +12 -0
  28. package/dist/auth/session-identity.js.map +1 -0
  29. package/dist/br/boleto.cjs +1 -1
  30. package/dist/br/boleto.cjs.map +1 -1
  31. package/dist/br/boleto.js +2 -1
  32. package/dist/br/boleto.js.map +1 -1
  33. package/dist/br.d.ts +22 -3
  34. package/dist/components/AuthForm/AuthFieldControl.cjs +2 -0
  35. package/dist/components/AuthForm/AuthFieldControl.cjs.map +1 -0
  36. package/dist/components/AuthForm/AuthFieldControl.js +85 -0
  37. package/dist/components/AuthForm/AuthFieldControl.js.map +1 -0
  38. package/dist/components/AuthForm/AuthForm.cjs +2 -0
  39. package/dist/components/AuthForm/AuthForm.cjs.map +1 -0
  40. package/dist/components/AuthForm/AuthForm.js +83 -0
  41. package/dist/components/AuthForm/AuthForm.js.map +1 -0
  42. package/dist/components/AuthForm/AuthForm.module.cjs +3 -0
  43. package/dist/components/AuthForm/AuthForm.module.cjs.map +1 -0
  44. package/dist/components/AuthForm/AuthForm.module.js +14 -0
  45. package/dist/components/AuthForm/AuthForm.module.js.map +1 -0
  46. package/dist/components/AuthForm/AuthLinks.cjs +2 -0
  47. package/dist/components/AuthForm/AuthLinks.cjs.map +1 -0
  48. package/dist/components/AuthForm/AuthLinks.js +32 -0
  49. package/dist/components/AuthForm/AuthLinks.js.map +1 -0
  50. package/dist/components/AuthForm/ForgotPasswordForm.cjs +2 -0
  51. package/dist/components/AuthForm/ForgotPasswordForm.cjs.map +1 -0
  52. package/dist/components/AuthForm/ForgotPasswordForm.js +32 -0
  53. package/dist/components/AuthForm/ForgotPasswordForm.js.map +1 -0
  54. package/dist/components/AuthForm/LoginForm.cjs +2 -0
  55. package/dist/components/AuthForm/LoginForm.cjs.map +1 -0
  56. package/dist/components/AuthForm/LoginForm.js +39 -0
  57. package/dist/components/AuthForm/LoginForm.js.map +1 -0
  58. package/dist/components/AuthForm/ResetPasswordForm.cjs +2 -0
  59. package/dist/components/AuthForm/ResetPasswordForm.cjs.map +1 -0
  60. package/dist/components/AuthForm/ResetPasswordForm.js +39 -0
  61. package/dist/components/AuthForm/ResetPasswordForm.js.map +1 -0
  62. package/dist/components/AuthForm/SignUpForm.cjs +2 -0
  63. package/dist/components/AuthForm/SignUpForm.cjs.map +1 -0
  64. package/dist/components/AuthForm/SignUpForm.js +32 -0
  65. package/dist/components/AuthForm/SignUpForm.js.map +1 -0
  66. package/dist/components/AuthForm/auth-defaults.cjs +2 -0
  67. package/dist/components/AuthForm/auth-defaults.cjs.map +1 -0
  68. package/dist/components/AuthForm/auth-defaults.js +54 -0
  69. package/dist/components/AuthForm/auth-defaults.js.map +1 -0
  70. package/dist/components/AuthForm/auth-schemas.cjs +2 -0
  71. package/dist/components/AuthForm/auth-schemas.cjs.map +1 -0
  72. package/dist/components/AuthForm/auth-schemas.js +26 -0
  73. package/dist/components/AuthForm/auth-schemas.js.map +1 -0
  74. package/dist/components/AuthPage/AuthPage.cjs +2 -0
  75. package/dist/components/AuthPage/AuthPage.cjs.map +1 -0
  76. package/dist/components/AuthPage/AuthPage.js +97 -0
  77. package/dist/components/AuthPage/AuthPage.js.map +1 -0
  78. package/dist/components/AuthPage/AuthPage.module.cjs +3 -0
  79. package/dist/components/AuthPage/AuthPage.module.cjs.map +1 -0
  80. package/dist/components/AuthPage/AuthPage.module.js +24 -0
  81. package/dist/components/AuthPage/AuthPage.module.js.map +1 -0
  82. package/dist/components/Breadcrumbs/Breadcrumbs.cjs +1 -1
  83. package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
  84. package/dist/components/Breadcrumbs/Breadcrumbs.js +22 -21
  85. package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  86. package/dist/components/Button/Button.cjs +1 -1
  87. package/dist/components/Button/Button.cjs.map +1 -1
  88. package/dist/components/Button/Button.js +41 -40
  89. package/dist/components/Button/Button.js.map +1 -1
  90. package/dist/components/Chat/ChatAttachments.cjs +1 -1
  91. package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
  92. package/dist/components/Chat/ChatAttachments.js +38 -38
  93. package/dist/components/FilterBar/filter-apply.cjs +1 -1
  94. package/dist/components/FilterBar/filter-apply.cjs.map +1 -1
  95. package/dist/components/FilterBar/filter-apply.js +7 -7
  96. package/dist/components/Form/Form.cjs +1 -1
  97. package/dist/components/Form/Form.cjs.map +1 -1
  98. package/dist/components/Form/Form.js +30 -32
  99. package/dist/components/Form/Form.js.map +1 -1
  100. package/dist/components/Layout/Layout.cjs +1 -1
  101. package/dist/components/Layout/Layout.cjs.map +1 -1
  102. package/dist/components/Layout/Layout.js +25 -24
  103. package/dist/components/Layout/Layout.js.map +1 -1
  104. package/dist/components/Layout/gap.cjs +2 -0
  105. package/dist/components/Layout/gap.cjs.map +1 -0
  106. package/dist/components/Layout/gap.js +15 -0
  107. package/dist/components/Layout/gap.js.map +1 -0
  108. package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
  109. package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
  110. package/dist/components/NavigationMenu/NavigationMenu.js +71 -70
  111. package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
  112. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  113. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  114. package/dist/components/Sidebar/SidebarEntry.cjs +1 -1
  115. package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -1
  116. package/dist/components/Sidebar/SidebarEntry.js +30 -29
  117. package/dist/components/Sidebar/SidebarEntry.js.map +1 -1
  118. package/dist/data/create-data-provider.cjs +1 -1
  119. package/dist/data/create-data-provider.cjs.map +1 -1
  120. package/dist/data/create-data-provider.js +1 -2
  121. package/dist/data/create-data-provider.js.map +1 -1
  122. package/dist/forms/index.js +7 -7
  123. package/dist/forms/masked-inputs.cjs +1 -1
  124. package/dist/forms/masked-inputs.cjs.map +1 -1
  125. package/dist/forms/masked-inputs.js +15 -15
  126. package/dist/forms/validate-form.cjs +1 -1
  127. package/dist/forms/validate-form.cjs.map +1 -1
  128. package/dist/forms/validate-form.js +6 -1
  129. package/dist/forms/validate-form.js.map +1 -1
  130. package/dist/forms/zod-resolver.cjs +1 -1
  131. package/dist/forms/zod-resolver.cjs.map +1 -1
  132. package/dist/forms/zod-resolver.js +8 -3
  133. package/dist/forms/zod-resolver.js.map +1 -1
  134. package/dist/http/api-client.cjs +1 -1
  135. package/dist/http/api-client.cjs.map +1 -1
  136. package/dist/http/api-client.js +137 -116
  137. package/dist/http/api-client.js.map +1 -1
  138. package/dist/http/credential-scope.cjs +1 -1
  139. package/dist/http/credential-scope.cjs.map +1 -1
  140. package/dist/http/credential-scope.js +33 -9
  141. package/dist/http/credential-scope.js.map +1 -1
  142. package/dist/http/presigned-post.cjs.map +1 -1
  143. package/dist/http/presigned-post.js.map +1 -1
  144. package/dist/http/resumable-upload.cjs +1 -1
  145. package/dist/http/resumable-upload.cjs.map +1 -1
  146. package/dist/http/resumable-upload.js +140 -134
  147. package/dist/http/resumable-upload.js.map +1 -1
  148. package/dist/http/upload-with-progress.cjs +1 -1
  149. package/dist/http/upload-with-progress.cjs.map +1 -1
  150. package/dist/http/upload-with-progress.js +17 -17
  151. package/dist/http/upload-with-progress.js.map +1 -1
  152. package/dist/http/use-describe-api-error.cjs +1 -1
  153. package/dist/http/use-describe-api-error.cjs.map +1 -1
  154. package/dist/http/use-describe-api-error.js +9 -9
  155. package/dist/oauth/GoogleSignIn.cjs +1 -1
  156. package/dist/oauth/GoogleSignIn.cjs.map +1 -1
  157. package/dist/oauth/GoogleSignIn.js +5 -4
  158. package/dist/oauth/GoogleSignIn.js.map +1 -1
  159. package/dist/offline/create-offline-sync.cjs +1 -1
  160. package/dist/offline/create-offline-sync.cjs.map +1 -1
  161. package/dist/offline/create-offline-sync.js +108 -68
  162. package/dist/offline/create-offline-sync.js.map +1 -1
  163. package/dist/query/offline-persistence.cjs +1 -1
  164. package/dist/query/offline-persistence.cjs.map +1 -1
  165. package/dist/query/offline-persistence.js +54 -44
  166. package/dist/query/offline-persistence.js.map +1 -1
  167. package/dist/sse/create-event-stream.cjs +1 -1
  168. package/dist/sse/create-event-stream.cjs.map +1 -1
  169. package/dist/sse/create-event-stream.js +48 -41
  170. package/dist/sse/create-event-stream.js.map +1 -1
  171. package/dist/sse/ticket-event-stream.cjs +1 -1
  172. package/dist/sse/ticket-event-stream.cjs.map +1 -1
  173. package/dist/sse/ticket-event-stream.js +48 -19
  174. package/dist/sse/ticket-event-stream.js.map +1 -1
  175. package/dist/sse/use-event-stream.cjs +1 -1
  176. package/dist/sse/use-event-stream.cjs.map +1 -1
  177. package/dist/sse/use-event-stream.js +19 -14
  178. package/dist/sse/use-event-stream.js.map +1 -1
  179. package/dist/styles/AuthForm.css +2 -0
  180. package/dist/styles/AuthPage.css +2 -0
  181. package/dist/styles/component/AuthForm.css +9 -0
  182. package/dist/styles/component/AuthPage.css +29 -0
  183. package/dist/styles/forms.css +36 -0
  184. package/dist/styles/manifest.json +63 -0
  185. package/dist/styles.css +1 -1
  186. package/dist/sw/background-sync.cjs +1 -1
  187. package/dist/sw/background-sync.cjs.map +1 -1
  188. package/dist/sw/background-sync.js +132 -112
  189. package/dist/sw/background-sync.js.map +1 -1
  190. package/dist/sw/cache.cjs +1 -1
  191. package/dist/sw/cache.cjs.map +1 -1
  192. package/dist/sw/cache.js +36 -30
  193. package/dist/sw/cache.js.map +1 -1
  194. package/dist/sw/create-push-handler.cjs +1 -1
  195. package/dist/sw/create-push-handler.cjs.map +1 -1
  196. package/dist/sw/create-push-handler.js +20 -10
  197. package/dist/sw/create-push-handler.js.map +1 -1
  198. package/dist/sw.d.ts +93 -28
  199. package/dist/tabular/compact.cjs +1 -1
  200. package/dist/tabular/compact.cjs.map +1 -1
  201. package/dist/tabular/compact.js +35 -16
  202. package/dist/tabular/compact.js.map +1 -1
  203. package/dist/tabular/manifest.cjs +1 -1
  204. package/dist/tabular/manifest.cjs.map +1 -1
  205. package/dist/tabular/manifest.js +33 -15
  206. package/dist/tabular/manifest.js.map +1 -1
  207. package/dist/tabular.d.ts +11 -1
  208. package/dist/tempest-react-sdk.cjs +1 -1
  209. package/dist/tempest-react-sdk.d.ts +1057 -83
  210. package/dist/tempest-react-sdk.js +373 -365
  211. package/dist/theme/initial-theme.cjs +4 -4
  212. package/dist/theme/initial-theme.cjs.map +1 -1
  213. package/dist/theme/initial-theme.js +8 -5
  214. package/dist/theme/initial-theme.js.map +1 -1
  215. package/dist/utils/compressed-storage.cjs +1 -1
  216. package/dist/utils/compressed-storage.cjs.map +1 -1
  217. package/dist/utils/compressed-storage.js +13 -7
  218. package/dist/utils/compressed-storage.js.map +1 -1
  219. package/dist/utils/format.cjs +1 -1
  220. package/dist/utils/format.cjs.map +1 -1
  221. package/dist/utils/format.js +25 -25
  222. package/dist/utils/numbers.cjs +1 -1
  223. package/dist/utils/numbers.cjs.map +1 -1
  224. package/dist/utils/numbers.js +7 -7
  225. package/dist/utils/objects.cjs +1 -1
  226. package/dist/utils/objects.cjs.map +1 -1
  227. package/dist/utils/objects.js +13 -7
  228. package/dist/utils/objects.js.map +1 -1
  229. package/dist/utils/safe-url.cjs +1 -1
  230. package/dist/utils/safe-url.cjs.map +1 -1
  231. package/dist/utils/safe-url.js +4 -1
  232. package/dist/utils/safe-url.js.map +1 -1
  233. package/dist/webrtc/peer-mesh.cjs +1 -1
  234. package/dist/webrtc/peer-mesh.cjs.map +1 -1
  235. package/dist/webrtc/peer-mesh.js +7 -1
  236. package/dist/webrtc/peer-mesh.js.map +1 -1
  237. package/dist/ws/create-web-socket.cjs +1 -1
  238. package/dist/ws/create-web-socket.cjs.map +1 -1
  239. package/dist/ws/create-web-socket.js +77 -61
  240. package/dist/ws/create-web-socket.js.map +1 -1
  241. package/dist/ws/use-web-socket.cjs +1 -1
  242. package/dist/ws/use-web-socket.cjs.map +1 -1
  243. package/dist/ws/use-web-socket.js +4 -3
  244. package/dist/ws/use-web-socket.js.map +1 -1
  245. package/package.json +1 -1
  246. package/template-pwa/src/pwa.tsx +42 -0
  247. package/template-pwa/src/sw.ts +15 -10
@@ -1 +1 @@
1
- {"version":3,"file":"use-web-socket.cjs","names":["useState","useRef","useLatestRef","createWebSocket","useCallback","require_use_latest_ref","require_create_web_socket","react","useWebSocket","options","status","setStatus","lastMessage","setLastMessage","controllerRef","optionsRef","protocolsKey","protocols","enabled","url","hasParser","hasParseError","hasValidationError","controller","raw","error","issues","event","attempt","total","reason","message","maxRetries","initialBackoff","maxBackoff","jitter","handshakeTimeout","silenceTimeout","waitForOnline","pingInterval","respondToPing","queueWhileClosed","maxQueuedMessages","payload","ms"],"sources":["../../src/ws/use-web-socket.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createWebSocket,\n type CreateWebSocketOptions,\n type WebSocketController,\n type WebSocketMessage,\n type WebSocketStatus,\n} from \"./create-web-socket\";\n\nexport interface UseWebSocketOptions<T> extends Omit<CreateWebSocketOptions<T>, \"onStatusChange\"> {\n /** When false, the socket is not opened. Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseWebSocketResult<T> {\n status: WebSocketStatus;\n /**\n * Last decoded frame received.\n *\n * A snapshot, not a stream: two frames arriving in the same tick collapse\n * into a single render and only the later one is ever visible. One server\n * action often emits several frames in a row, so anything that must see\n * every message has to use `onMessage`, which fires once per frame. Read\n * `lastMessage` for \"what is the current state\" rendering only.\n */\n lastMessage: WebSocketMessage<T> | null;\n /** Send a payload through the active connection. Returns false when not open. */\n send: (payload: string | Blob | BufferSource) => boolean;\n /** Force a reconnect, resetting the retry counter. */\n reconnect: () => void;\n /**\n * Change the silence watchdog at runtime, in ms. `0` disables it.\n *\n * For a server that announces its own heartbeat interval in the first frame,\n * so the tolerated silence is not hard-coded on both ends.\n */\n setSilenceTimeout: (ms: number) => void;\n}\n\n/**\n * React hook around {@link createWebSocket}. Manages the connection lifecycle\n * for the host component and tears it down on unmount.\n *\n * Every callback is read through a ref, so `onOpen` / `onMessage` / `onClose` /\n * `onError` / `onReconnecting` / `onReconnected` / `onLost` always run the\n * latest closure — an inline arrow function is fine and never reopens the\n * socket. Connection-shaping options (`protocols`, `maxRetries`,\n * `initialBackoff`, `maxBackoff`, `jitter`, `handshakeTimeout`,\n * `silenceTimeout`, `waitForOnline`, `pingInterval`, `queueWhileClosed`) are\n * baked into the connection, so changing one reopens it with the new value\n * rather than being silently ignored.\n *\n * `schema` is read when the socket opens, so declare it outside the component\n * (or memoize it): a schema built inline is a new object on every render, and\n * the one in force is whichever existed at the last open.\n *\n * @param url - Full ws:// or wss:// URL.\n * @param options - Connection configuration and callbacks.\n * @returns Status, last frame, and the `send` / `reconnect` controls.\n */\nexport function useWebSocket<T = unknown>(\n url: string,\n options: UseWebSocketOptions<T> = {},\n): UseWebSocketResult<T> {\n const {\n enabled = true,\n protocols,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n respondToPing,\n queueWhileClosed,\n maxQueuedMessages,\n } = options;\n const [status, setStatus] = useState<WebSocketStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<WebSocketMessage<T> | null>(null);\n const controllerRef = useRef<WebSocketController | null>(null);\n\n const optionsRef = useLatestRef(options);\n\n const protocolsKey = Array.isArray(protocols) ? protocols.join(\",\") : (protocols ?? \"\");\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n /*\n * Presence is read once, when the socket opens, and decides whether the\n * forwarder is passed at all. A forwarder is always truthy, so wrapping\n * an absent `parser` — or an absent `onParseError` — would tell\n * `decodeFrame` the caller had supplied one and silently pick the wrong\n * branch.\n */\n const hasParser = optionsRef.current.parser !== undefined;\n const hasParseError = optionsRef.current.onParseError !== undefined;\n const hasValidationError = optionsRef.current.onValidationError !== undefined;\n\n const controller = createWebSocket<T>(url, {\n protocols: optionsRef.current.protocols,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n pingPayload: optionsRef.current.pingPayload,\n respondToPing,\n pongPayload: optionsRef.current.pongPayload,\n queueWhileClosed,\n maxQueuedMessages,\n parser: hasParser ? (raw) => optionsRef.current.parser?.(raw) as T : undefined,\n onParseError: hasParseError\n ? (error, raw) => optionsRef.current.onParseError?.(error, raw)\n : undefined,\n schema: optionsRef.current.schema,\n onValidationError: hasValidationError\n ? (issues, raw) => optionsRef.current.onValidationError?.(issues, raw)\n : undefined,\n onStatusChange: setStatus,\n onOpen: (event) => optionsRef.current.onOpen?.(event),\n onClose: (event) => optionsRef.current.onClose?.(event),\n onError: (event) => optionsRef.current.onError?.(event),\n onReconnecting: (attempt, total) => optionsRef.current.onReconnecting?.(attempt, total),\n onReconnected: () => optionsRef.current.onReconnected?.(),\n onLost: (reason) => optionsRef.current.onLost?.(reason),\n onMessage: (message) => {\n setLastMessage(message);\n optionsRef.current.onMessage?.(message);\n },\n });\n controllerRef.current = controller;\n\n return () => {\n controller.close();\n controllerRef.current = null;\n };\n }, [\n url,\n enabled,\n protocolsKey,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n respondToPing,\n queueWhileClosed,\n maxQueuedMessages,\n optionsRef,\n ]);\n\n const send = useCallback((payload: string | Blob | BufferSource): boolean => {\n return controllerRef.current?.send(payload) ?? false;\n }, []);\n\n const reconnect = useCallback((): void => {\n controllerRef.current?.reconnect();\n }, []);\n\n const setSilenceTimeout = useCallback((ms: number): void => {\n controllerRef.current?.setSilenceTimeout(ms);\n }, []);\n\n return { status, lastMessage, send, reconnect, setSilenceTimeout };\n}\n"],"mappings":"A,2GA6DA,SAAgBQ,EACZ,EACA,EAAkC,CAAC,EACd,CACrB,GAAM,CACF,UAAU,GACV,YACA,aACA,iBACA,aACA,SACA,mBACA,iBACA,gBACA,eACA,gBACA,mBACA,qBACAC,EACE,CAACC,EAAQC,IAAA,EAAaX,EAAAA,SAAAA,CAA0B,MAAM,EACtD,CAACY,EAAaC,IAAA,EAAkBb,EAAAA,SAAAA,CAAqC,IAAI,EACzEc,GAAA,EAAgBb,EAAAA,OAAAA,CAAmC,IAAI,EAEvDc,EAAab,EAAAA,aAAaO,CAAO,EAEjCO,EAAe,MAAM,QAAQC,CAAS,EAAIA,EAAU,KAAK,GAAG,EAAKA,GAAa,GA0FpF,OAxFA,EAAAV,EAAA,UAAA,KAAgB,CACZ,GAAI,CAACW,GAAW,CAACC,EAAK,CAClBR,EAAU,MAAM,EAChB,MACJ,CASA,IAAMS,EAAYL,EAAW,QAAQ,SAAW,IAAA,GAC1CM,EAAgBN,EAAW,QAAQ,eAAiB,IAAA,GACpDO,EAAqBP,EAAW,QAAQ,oBAAsB,IAAA,GAE9DQ,EAAapB,EAAAA,gBAAmBgB,EAAK,CACvC,UAAWJ,EAAW,QAAQ,UAC9B,aACA,iBACA,aACA,SACA,mBACA,iBACA,gBACA,eACA,YAAaA,EAAW,QAAQ,YAChC,gBACA,YAAaA,EAAW,QAAQ,YAChC,mBACA,oBACA,OAAQK,EAAa,GAAQL,EAAW,QAAQ,SAASS,CAAG,EAAS,IAAA,GACrE,aAAcH,GACP,EAAO,IAAQN,EAAW,QAAQ,eAAeU,EAAOD,CAAG,EAC5D,IAAA,GACN,OAAQT,EAAW,QAAQ,OAC3B,kBAAmBO,GACZ,EAAQ,IAAQP,EAAW,QAAQ,oBAAoBW,EAAQF,CAAG,EACnE,IAAA,GACN,eAAgBb,EAChB,OAAS,GAAUI,EAAW,QAAQ,SAASY,CAAK,EACpD,QAAU,GAAUZ,EAAW,QAAQ,UAAUY,CAAK,EACtD,QAAU,GAAUZ,EAAW,QAAQ,UAAUY,CAAK,EACtD,gBAAiB,EAAS,IAAUZ,EAAW,QAAQ,iBAAiBa,EAASC,CAAK,EACtF,kBAAqBd,EAAW,QAAQ,gBAAgB,EACxD,OAAS,GAAWA,EAAW,QAAQ,SAASe,CAAM,EACtD,UAAY,GAAY,CACpBjB,EAAekB,CAAO,EACtBhB,EAAW,QAAQ,YAAYgB,CAAO,CAC1C,CACJ,CAAC,EAGD,MAFA,GAAc,QAAUR,MAEX,CACTA,EAAW,MAAM,EACjB,EAAc,QAAU,IAC5B,CACJ,EAAG,CACCJ,EACAD,EACAF,EACAgB,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACA3B,CACJ,CAAC,EAcM,CAAE,SAAQ,cAAa,MAAA,EAZjBX,EAAAA,YAAAA,CAAa,GACfU,EAAc,SAAS,KAAK6B,CAAO,GAAK,GAChD,CAAC,CAU0B,EAAM,WAAA,EARlBvC,EAAAA,YAAAA,KAAwB,CACtCU,EAAc,SAAS,UAAU,CACrC,EAAG,CAAC,CAMgC,EAAW,mBAAA,EAJrBV,EAAAA,YAAAA,CAAa,GAAqB,CACxDU,EAAc,SAAS,kBAAkB8B,CAAE,CAC/C,EAAG,CAAC,CAE2C,CAAkB,CACrE,C"}
1
+ {"version":3,"file":"use-web-socket.cjs","names":["useState","useRef","useLatestRef","createWebSocket","useCallback","require_use_latest_ref","require_create_web_socket","react","useWebSocket","options","status","setStatus","lastMessage","setLastMessage","controllerRef","optionsRef","protocolsKey","protocols","enabled","url","hasParser","hasParseError","hasValidationError","hasPrincipal","controller","raw","error","issues","event","attempt","total","reason","message","maxRetries","initialBackoff","maxBackoff","jitter","handshakeTimeout","silenceTimeout","waitForOnline","pingInterval","respondToPing","queueWhileClosed","maxQueuedMessages","payload","ms"],"sources":["../../src/ws/use-web-socket.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createWebSocket,\n type CreateWebSocketOptions,\n type WebSocketController,\n type WebSocketMessage,\n type WebSocketStatus,\n} from \"./create-web-socket\";\n\nexport interface UseWebSocketOptions<T> extends Omit<CreateWebSocketOptions<T>, \"onStatusChange\"> {\n /** When false, the socket is not opened. Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseWebSocketResult<T> {\n status: WebSocketStatus;\n /**\n * Last decoded frame received.\n *\n * A snapshot, not a stream: two frames arriving in the same tick collapse\n * into a single render and only the later one is ever visible. One server\n * action often emits several frames in a row, so anything that must see\n * every message has to use `onMessage`, which fires once per frame. Read\n * `lastMessage` for \"what is the current state\" rendering only.\n */\n lastMessage: WebSocketMessage<T> | null;\n /** Send a payload through the active connection. Returns false when not open. */\n send: (payload: string | Blob | BufferSource) => boolean;\n /** Force a reconnect, resetting the retry counter. */\n reconnect: () => void;\n /**\n * Change the silence watchdog at runtime, in ms. `0` disables it.\n *\n * For a server that announces its own heartbeat interval in the first frame,\n * so the tolerated silence is not hard-coded on both ends.\n */\n setSilenceTimeout: (ms: number) => void;\n}\n\n/**\n * React hook around {@link createWebSocket}. Manages the connection lifecycle\n * for the host component and tears it down on unmount.\n *\n * Every callback is read through a ref, so `onOpen` / `onMessage` / `onClose` /\n * `onError` / `onReconnecting` / `onReconnected` / `onLost` / `getPrincipal` always run the\n * latest closure — an inline arrow function is fine and never reopens the\n * socket. Connection-shaping options (`protocols`, `maxRetries`,\n * `initialBackoff`, `maxBackoff`, `jitter`, `handshakeTimeout`,\n * `silenceTimeout`, `waitForOnline`, `pingInterval`, `queueWhileClosed`) are\n * baked into the connection, so changing one reopens it with the new value\n * rather than being silently ignored.\n *\n * `schema` is read when the socket opens, so declare it outside the component\n * (or memoize it): a schema built inline is a new object on every render, and\n * the one in force is whichever existed at the last open.\n *\n * @param url - Full ws:// or wss:// URL.\n * @param options - Connection configuration and callbacks.\n * @returns Status, last frame, and the `send` / `reconnect` controls.\n */\nexport function useWebSocket<T = unknown>(\n url: string,\n options: UseWebSocketOptions<T> = {},\n): UseWebSocketResult<T> {\n const {\n enabled = true,\n protocols,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n respondToPing,\n queueWhileClosed,\n maxQueuedMessages,\n } = options;\n const [status, setStatus] = useState<WebSocketStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<WebSocketMessage<T> | null>(null);\n const controllerRef = useRef<WebSocketController | null>(null);\n\n const optionsRef = useLatestRef(options);\n\n const protocolsKey = Array.isArray(protocols) ? protocols.join(\",\") : (protocols ?? \"\");\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n /*\n * Presence is read once, when the socket opens, and decides whether the\n * forwarder is passed at all. A forwarder is always truthy, so wrapping\n * an absent `parser` — or an absent `onParseError` — would tell\n * `decodeFrame` the caller had supplied one and silently pick the wrong\n * branch.\n */\n const hasParser = optionsRef.current.parser !== undefined;\n const hasParseError = optionsRef.current.onParseError !== undefined;\n const hasValidationError = optionsRef.current.onValidationError !== undefined;\n const hasPrincipal = optionsRef.current.getPrincipal !== undefined;\n\n const controller = createWebSocket<T>(url, {\n protocols: optionsRef.current.protocols,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n pingPayload: optionsRef.current.pingPayload,\n respondToPing,\n pongPayload: optionsRef.current.pongPayload,\n queueWhileClosed,\n maxQueuedMessages,\n getPrincipal: hasPrincipal ? () => optionsRef.current.getPrincipal?.() : undefined,\n parser: hasParser ? (raw) => optionsRef.current.parser?.(raw) as T : undefined,\n onParseError: hasParseError\n ? (error, raw) => optionsRef.current.onParseError?.(error, raw)\n : undefined,\n schema: optionsRef.current.schema,\n onValidationError: hasValidationError\n ? (issues, raw) => optionsRef.current.onValidationError?.(issues, raw)\n : undefined,\n onStatusChange: setStatus,\n onOpen: (event) => optionsRef.current.onOpen?.(event),\n onClose: (event) => optionsRef.current.onClose?.(event),\n onError: (event) => optionsRef.current.onError?.(event),\n onReconnecting: (attempt, total) => optionsRef.current.onReconnecting?.(attempt, total),\n onReconnected: () => optionsRef.current.onReconnected?.(),\n onLost: (reason) => optionsRef.current.onLost?.(reason),\n onMessage: (message) => {\n setLastMessage(message);\n optionsRef.current.onMessage?.(message);\n },\n });\n controllerRef.current = controller;\n\n return () => {\n controller.close();\n controllerRef.current = null;\n };\n }, [\n url,\n enabled,\n protocolsKey,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n respondToPing,\n queueWhileClosed,\n maxQueuedMessages,\n optionsRef,\n ]);\n\n const send = useCallback((payload: string | Blob | BufferSource): boolean => {\n return controllerRef.current?.send(payload) ?? false;\n }, []);\n\n const reconnect = useCallback((): void => {\n controllerRef.current?.reconnect();\n }, []);\n\n const setSilenceTimeout = useCallback((ms: number): void => {\n controllerRef.current?.setSilenceTimeout(ms);\n }, []);\n\n return { status, lastMessage, send, reconnect, setSilenceTimeout };\n}\n"],"mappings":"A,2GA6DA,SAAgBQ,EACZ,EACA,EAAkC,CAAC,EACd,CACrB,GAAM,CACF,UAAU,GACV,YACA,aACA,iBACA,aACA,SACA,mBACA,iBACA,gBACA,eACA,gBACA,mBACA,qBACAC,EACE,CAACC,EAAQC,IAAA,EAAaX,EAAAA,SAAAA,CAA0B,MAAM,EACtD,CAACY,EAAaC,IAAA,EAAkBb,EAAAA,SAAAA,CAAqC,IAAI,EACzEc,GAAA,EAAgBb,EAAAA,OAAAA,CAAmC,IAAI,EAEvDc,EAAab,EAAAA,aAAaO,CAAO,EAEjCO,EAAe,MAAM,QAAQC,CAAS,EAAIA,EAAU,KAAK,GAAG,EAAKA,GAAa,GA4FpF,OA1FA,EAAAV,EAAA,UAAA,KAAgB,CACZ,GAAI,CAACW,GAAW,CAACC,EAAK,CAClBR,EAAU,MAAM,EAChB,MACJ,CASA,IAAMS,EAAYL,EAAW,QAAQ,SAAW,IAAA,GAC1CM,EAAgBN,EAAW,QAAQ,eAAiB,IAAA,GACpDO,EAAqBP,EAAW,QAAQ,oBAAsB,IAAA,GAC9DQ,EAAeR,EAAW,QAAQ,eAAiB,IAAA,GAEnDS,EAAarB,EAAAA,gBAAmBgB,EAAK,CACvC,UAAWJ,EAAW,QAAQ,UAC9B,aACA,iBACA,aACA,SACA,mBACA,iBACA,gBACA,eACA,YAAaA,EAAW,QAAQ,YAChC,gBACA,YAAaA,EAAW,QAAQ,YAChC,mBACA,oBACA,aAAcQ,MAAqBR,EAAW,QAAQ,eAAe,EAAI,IAAA,GACzE,OAAQK,EAAa,GAAQL,EAAW,QAAQ,SAASU,CAAG,EAAS,IAAA,GACrE,aAAcJ,GACP,EAAO,IAAQN,EAAW,QAAQ,eAAeW,EAAOD,CAAG,EAC5D,IAAA,GACN,OAAQV,EAAW,QAAQ,OAC3B,kBAAmBO,GACZ,EAAQ,IAAQP,EAAW,QAAQ,oBAAoBY,EAAQF,CAAG,EACnE,IAAA,GACN,eAAgBd,EAChB,OAAS,GAAUI,EAAW,QAAQ,SAASa,CAAK,EACpD,QAAU,GAAUb,EAAW,QAAQ,UAAUa,CAAK,EACtD,QAAU,GAAUb,EAAW,QAAQ,UAAUa,CAAK,EACtD,gBAAiB,EAAS,IAAUb,EAAW,QAAQ,iBAAiBc,EAASC,CAAK,EACtF,kBAAqBf,EAAW,QAAQ,gBAAgB,EACxD,OAAS,GAAWA,EAAW,QAAQ,SAASgB,CAAM,EACtD,UAAY,GAAY,CACpBlB,EAAemB,CAAO,EACtBjB,EAAW,QAAQ,YAAYiB,CAAO,CAC1C,CACJ,CAAC,EAGD,MAFA,GAAc,QAAUR,MAEX,CACTA,EAAW,MAAM,EACjB,EAAc,QAAU,IAC5B,CACJ,EAAG,CACCL,EACAD,EACAF,EACAiB,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACA5B,CACJ,CAAC,EAcM,CAAE,SAAQ,cAAa,MAAA,EAZjBX,EAAAA,YAAAA,CAAa,GACfU,EAAc,SAAS,KAAK8B,CAAO,GAAK,GAChD,CAAC,CAU0B,EAAM,WAAA,EARlBxC,EAAAA,YAAAA,KAAwB,CACtCU,EAAc,SAAS,UAAU,CACrC,EAAG,CAAC,CAMgC,EAAW,mBAAA,EAJrBV,EAAAA,YAAAA,CAAa,GAAqB,CACxDU,EAAc,SAAS,kBAAkB+B,CAAE,CAC/C,EAAG,CAAC,CAE2C,CAAkB,CACrE,C"}
@@ -9,7 +9,7 @@ function o(o, s = {}) {
9
9
  S("idle");
10
10
  return;
11
11
  }
12
- let e = E.current.parser !== void 0, n = E.current.onParseError !== void 0, r = E.current.onValidationError !== void 0, i = t(o, {
12
+ let e = E.current.parser !== void 0, n = E.current.onParseError !== void 0, r = E.current.onValidationError !== void 0, i = E.current.getPrincipal !== void 0, a = t(o, {
13
13
  protocols: E.current.protocols,
14
14
  maxRetries: u,
15
15
  initialBackoff: d,
@@ -24,6 +24,7 @@ function o(o, s = {}) {
24
24
  pongPayload: E.current.pongPayload,
25
25
  queueWhileClosed: y,
26
26
  maxQueuedMessages: b,
27
+ getPrincipal: i ? () => E.current.getPrincipal?.() : void 0,
27
28
  parser: e ? (e) => E.current.parser?.(e) : void 0,
28
29
  onParseError: n ? (e, t) => E.current.onParseError?.(e, t) : void 0,
29
30
  schema: E.current.schema,
@@ -39,8 +40,8 @@ function o(o, s = {}) {
39
40
  w(e), E.current.onMessage?.(e);
40
41
  }
41
42
  });
42
- return T.current = i, () => {
43
- i.close(), T.current = null;
43
+ return T.current = a, () => {
44
+ a.close(), T.current = null;
44
45
  };
45
46
  }, [
46
47
  o,
@@ -1 +1 @@
1
- {"version":3,"file":"use-web-socket.js","names":["useWebSocket","options","status","setStatus","useState","lastMessage","setLastMessage","controllerRef","useRef","optionsRef","useLatestRef","protocolsKey","protocols","useEffect","enabled","url","hasParser","hasParseError","hasValidationError","controller","createWebSocket","raw","error","issues","event","attempt","total","reason","message","maxRetries","initialBackoff","maxBackoff","jitter","handshakeTimeout","silenceTimeout","waitForOnline","pingInterval","respondToPing","queueWhileClosed","maxQueuedMessages","useCallback","payload","ms"],"sources":["../../src/ws/use-web-socket.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createWebSocket,\n type CreateWebSocketOptions,\n type WebSocketController,\n type WebSocketMessage,\n type WebSocketStatus,\n} from \"./create-web-socket\";\n\nexport interface UseWebSocketOptions<T> extends Omit<CreateWebSocketOptions<T>, \"onStatusChange\"> {\n /** When false, the socket is not opened. Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseWebSocketResult<T> {\n status: WebSocketStatus;\n /**\n * Last decoded frame received.\n *\n * A snapshot, not a stream: two frames arriving in the same tick collapse\n * into a single render and only the later one is ever visible. One server\n * action often emits several frames in a row, so anything that must see\n * every message has to use `onMessage`, which fires once per frame. Read\n * `lastMessage` for \"what is the current state\" rendering only.\n */\n lastMessage: WebSocketMessage<T> | null;\n /** Send a payload through the active connection. Returns false when not open. */\n send: (payload: string | Blob | BufferSource) => boolean;\n /** Force a reconnect, resetting the retry counter. */\n reconnect: () => void;\n /**\n * Change the silence watchdog at runtime, in ms. `0` disables it.\n *\n * For a server that announces its own heartbeat interval in the first frame,\n * so the tolerated silence is not hard-coded on both ends.\n */\n setSilenceTimeout: (ms: number) => void;\n}\n\n/**\n * React hook around {@link createWebSocket}. Manages the connection lifecycle\n * for the host component and tears it down on unmount.\n *\n * Every callback is read through a ref, so `onOpen` / `onMessage` / `onClose` /\n * `onError` / `onReconnecting` / `onReconnected` / `onLost` always run the\n * latest closure — an inline arrow function is fine and never reopens the\n * socket. Connection-shaping options (`protocols`, `maxRetries`,\n * `initialBackoff`, `maxBackoff`, `jitter`, `handshakeTimeout`,\n * `silenceTimeout`, `waitForOnline`, `pingInterval`, `queueWhileClosed`) are\n * baked into the connection, so changing one reopens it with the new value\n * rather than being silently ignored.\n *\n * `schema` is read when the socket opens, so declare it outside the component\n * (or memoize it): a schema built inline is a new object on every render, and\n * the one in force is whichever existed at the last open.\n *\n * @param url - Full ws:// or wss:// URL.\n * @param options - Connection configuration and callbacks.\n * @returns Status, last frame, and the `send` / `reconnect` controls.\n */\nexport function useWebSocket<T = unknown>(\n url: string,\n options: UseWebSocketOptions<T> = {},\n): UseWebSocketResult<T> {\n const {\n enabled = true,\n protocols,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n respondToPing,\n queueWhileClosed,\n maxQueuedMessages,\n } = options;\n const [status, setStatus] = useState<WebSocketStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<WebSocketMessage<T> | null>(null);\n const controllerRef = useRef<WebSocketController | null>(null);\n\n const optionsRef = useLatestRef(options);\n\n const protocolsKey = Array.isArray(protocols) ? protocols.join(\",\") : (protocols ?? \"\");\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n /*\n * Presence is read once, when the socket opens, and decides whether the\n * forwarder is passed at all. A forwarder is always truthy, so wrapping\n * an absent `parser` — or an absent `onParseError` — would tell\n * `decodeFrame` the caller had supplied one and silently pick the wrong\n * branch.\n */\n const hasParser = optionsRef.current.parser !== undefined;\n const hasParseError = optionsRef.current.onParseError !== undefined;\n const hasValidationError = optionsRef.current.onValidationError !== undefined;\n\n const controller = createWebSocket<T>(url, {\n protocols: optionsRef.current.protocols,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n pingPayload: optionsRef.current.pingPayload,\n respondToPing,\n pongPayload: optionsRef.current.pongPayload,\n queueWhileClosed,\n maxQueuedMessages,\n parser: hasParser ? (raw) => optionsRef.current.parser?.(raw) as T : undefined,\n onParseError: hasParseError\n ? (error, raw) => optionsRef.current.onParseError?.(error, raw)\n : undefined,\n schema: optionsRef.current.schema,\n onValidationError: hasValidationError\n ? (issues, raw) => optionsRef.current.onValidationError?.(issues, raw)\n : undefined,\n onStatusChange: setStatus,\n onOpen: (event) => optionsRef.current.onOpen?.(event),\n onClose: (event) => optionsRef.current.onClose?.(event),\n onError: (event) => optionsRef.current.onError?.(event),\n onReconnecting: (attempt, total) => optionsRef.current.onReconnecting?.(attempt, total),\n onReconnected: () => optionsRef.current.onReconnected?.(),\n onLost: (reason) => optionsRef.current.onLost?.(reason),\n onMessage: (message) => {\n setLastMessage(message);\n optionsRef.current.onMessage?.(message);\n },\n });\n controllerRef.current = controller;\n\n return () => {\n controller.close();\n controllerRef.current = null;\n };\n }, [\n url,\n enabled,\n protocolsKey,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n respondToPing,\n queueWhileClosed,\n maxQueuedMessages,\n optionsRef,\n ]);\n\n const send = useCallback((payload: string | Blob | BufferSource): boolean => {\n return controllerRef.current?.send(payload) ?? false;\n }, []);\n\n const reconnect = useCallback((): void => {\n controllerRef.current?.reconnect();\n }, []);\n\n const setSilenceTimeout = useCallback((ms: number): void => {\n controllerRef.current?.setSilenceTimeout(ms);\n }, []);\n\n return { status, lastMessage, send, reconnect, setSilenceTimeout };\n}\n"],"mappings":";;;;AA6DA,SAAgBA,EACZ,GACA,IAAkC,CAAC,GACd;CACrB,IAAM,EACF,aAAU,IACV,cACA,eACA,mBACA,eACA,WACA,qBACA,mBACA,kBACA,iBACA,kBACA,qBACA,yBACAC,GACE,CAACC,GAAQC,KAAaC,EAA0B,MAAM,GACtD,CAACC,GAAaC,KAAkBF,EAAqC,IAAI,GACzEG,IAAgBC,EAAmC,IAAI,GAEvDC,IAAaC,EAAaT,CAAO,GAEjCU,IAAe,MAAM,QAAQC,CAAS,IAAIA,EAAU,KAAK,GAAG,IAAKA,KAAa;CA0FpF,OAxFAC,QAAgB;EACZ,IAAI,CAACC,KAAW,CAACC,GAAK;GAClB,EAAU,MAAM;GAChB;EACJ;EASA,IAAMC,IAAYP,EAAW,QAAQ,WAAW,KAAA,GAC1CQ,IAAgBR,EAAW,QAAQ,iBAAiB,KAAA,GACpDS,IAAqBT,EAAW,QAAQ,sBAAsB,KAAA,GAE9DU,IAAaC,EAAmBL,GAAK;GACvC,WAAWN,EAAW,QAAQ;GAC9B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,aAAaA,EAAW,QAAQ;GAChC;GACA,aAAaA,EAAW,QAAQ;GAChC;GACA;GACA,QAAQO,KAAa,MAAQP,EAAW,QAAQ,SAASY,CAAG,IAAS,KAAA;GACrE,cAAcJ,KACP,GAAO,MAAQR,EAAW,QAAQ,eAAea,GAAOD,CAAG,IAC5D,KAAA;GACN,QAAQZ,EAAW,QAAQ;GAC3B,mBAAmBS,KACZ,GAAQ,MAAQT,EAAW,QAAQ,oBAAoBc,GAAQF,CAAG,IACnE,KAAA;GACN,gBAAgBlB;GAChB,SAAS,MAAUM,EAAW,QAAQ,SAASe,CAAK;GACpD,UAAU,MAAUf,EAAW,QAAQ,UAAUe,CAAK;GACtD,UAAU,MAAUf,EAAW,QAAQ,UAAUe,CAAK;GACtD,iBAAiB,GAAS,MAAUf,EAAW,QAAQ,iBAAiBgB,GAASC,CAAK;GACtF,qBAAqBjB,EAAW,QAAQ,gBAAgB;GACxD,SAAS,MAAWA,EAAW,QAAQ,SAASkB,CAAM;GACtD,YAAY,MAAY;IAEpB,AADArB,EAAesB,CAAO,GACtBnB,EAAW,QAAQ,YAAYmB,CAAO;GAC1C;EACJ,CAAC;EAGD,OAFA,EAAc,UAAUT,SAEX;GAET,AADAA,EAAW,MAAM,GACjB,EAAc,UAAU;EAC5B;CACJ,GAAG;EACCJ;EACAD;EACAH;EACAkB;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EACA9B;CACJ,CAAC,GAcM;EAAE;EAAQ;EAAa,MAZjB+B,GAAa,MACfjC,EAAc,SAAS,KAAKkC,CAAO,KAAK,IAChD,CAAC,CAU0B;EAAM,WARlBD,QAAwB;GACtC,EAAc,SAAS,UAAU;EACrC,GAAG,CAAC,CAMgC;EAAW,mBAJrBA,GAAa,MAAqB;GACxD,EAAc,SAAS,kBAAkBE,CAAE;EAC/C,GAAG,CAAC,CAE2C;CAAkB;AACrE"}
1
+ {"version":3,"file":"use-web-socket.js","names":["useWebSocket","options","status","setStatus","useState","lastMessage","setLastMessage","controllerRef","useRef","optionsRef","useLatestRef","protocolsKey","protocols","useEffect","enabled","url","hasParser","hasParseError","hasValidationError","hasPrincipal","controller","createWebSocket","raw","error","issues","event","attempt","total","reason","message","maxRetries","initialBackoff","maxBackoff","jitter","handshakeTimeout","silenceTimeout","waitForOnline","pingInterval","respondToPing","queueWhileClosed","maxQueuedMessages","useCallback","payload","ms"],"sources":["../../src/ws/use-web-socket.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n createWebSocket,\n type CreateWebSocketOptions,\n type WebSocketController,\n type WebSocketMessage,\n type WebSocketStatus,\n} from \"./create-web-socket\";\n\nexport interface UseWebSocketOptions<T> extends Omit<CreateWebSocketOptions<T>, \"onStatusChange\"> {\n /** When false, the socket is not opened. Default: true. */\n enabled?: boolean;\n}\n\nexport interface UseWebSocketResult<T> {\n status: WebSocketStatus;\n /**\n * Last decoded frame received.\n *\n * A snapshot, not a stream: two frames arriving in the same tick collapse\n * into a single render and only the later one is ever visible. One server\n * action often emits several frames in a row, so anything that must see\n * every message has to use `onMessage`, which fires once per frame. Read\n * `lastMessage` for \"what is the current state\" rendering only.\n */\n lastMessage: WebSocketMessage<T> | null;\n /** Send a payload through the active connection. Returns false when not open. */\n send: (payload: string | Blob | BufferSource) => boolean;\n /** Force a reconnect, resetting the retry counter. */\n reconnect: () => void;\n /**\n * Change the silence watchdog at runtime, in ms. `0` disables it.\n *\n * For a server that announces its own heartbeat interval in the first frame,\n * so the tolerated silence is not hard-coded on both ends.\n */\n setSilenceTimeout: (ms: number) => void;\n}\n\n/**\n * React hook around {@link createWebSocket}. Manages the connection lifecycle\n * for the host component and tears it down on unmount.\n *\n * Every callback is read through a ref, so `onOpen` / `onMessage` / `onClose` /\n * `onError` / `onReconnecting` / `onReconnected` / `onLost` / `getPrincipal` always run the\n * latest closure — an inline arrow function is fine and never reopens the\n * socket. Connection-shaping options (`protocols`, `maxRetries`,\n * `initialBackoff`, `maxBackoff`, `jitter`, `handshakeTimeout`,\n * `silenceTimeout`, `waitForOnline`, `pingInterval`, `queueWhileClosed`) are\n * baked into the connection, so changing one reopens it with the new value\n * rather than being silently ignored.\n *\n * `schema` is read when the socket opens, so declare it outside the component\n * (or memoize it): a schema built inline is a new object on every render, and\n * the one in force is whichever existed at the last open.\n *\n * @param url - Full ws:// or wss:// URL.\n * @param options - Connection configuration and callbacks.\n * @returns Status, last frame, and the `send` / `reconnect` controls.\n */\nexport function useWebSocket<T = unknown>(\n url: string,\n options: UseWebSocketOptions<T> = {},\n): UseWebSocketResult<T> {\n const {\n enabled = true,\n protocols,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n respondToPing,\n queueWhileClosed,\n maxQueuedMessages,\n } = options;\n const [status, setStatus] = useState<WebSocketStatus>(\"idle\");\n const [lastMessage, setLastMessage] = useState<WebSocketMessage<T> | null>(null);\n const controllerRef = useRef<WebSocketController | null>(null);\n\n const optionsRef = useLatestRef(options);\n\n const protocolsKey = Array.isArray(protocols) ? protocols.join(\",\") : (protocols ?? \"\");\n\n useEffect(() => {\n if (!enabled || !url) {\n setStatus(\"idle\");\n return;\n }\n\n /*\n * Presence is read once, when the socket opens, and decides whether the\n * forwarder is passed at all. A forwarder is always truthy, so wrapping\n * an absent `parser` — or an absent `onParseError` — would tell\n * `decodeFrame` the caller had supplied one and silently pick the wrong\n * branch.\n */\n const hasParser = optionsRef.current.parser !== undefined;\n const hasParseError = optionsRef.current.onParseError !== undefined;\n const hasValidationError = optionsRef.current.onValidationError !== undefined;\n const hasPrincipal = optionsRef.current.getPrincipal !== undefined;\n\n const controller = createWebSocket<T>(url, {\n protocols: optionsRef.current.protocols,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n pingPayload: optionsRef.current.pingPayload,\n respondToPing,\n pongPayload: optionsRef.current.pongPayload,\n queueWhileClosed,\n maxQueuedMessages,\n getPrincipal: hasPrincipal ? () => optionsRef.current.getPrincipal?.() : undefined,\n parser: hasParser ? (raw) => optionsRef.current.parser?.(raw) as T : undefined,\n onParseError: hasParseError\n ? (error, raw) => optionsRef.current.onParseError?.(error, raw)\n : undefined,\n schema: optionsRef.current.schema,\n onValidationError: hasValidationError\n ? (issues, raw) => optionsRef.current.onValidationError?.(issues, raw)\n : undefined,\n onStatusChange: setStatus,\n onOpen: (event) => optionsRef.current.onOpen?.(event),\n onClose: (event) => optionsRef.current.onClose?.(event),\n onError: (event) => optionsRef.current.onError?.(event),\n onReconnecting: (attempt, total) => optionsRef.current.onReconnecting?.(attempt, total),\n onReconnected: () => optionsRef.current.onReconnected?.(),\n onLost: (reason) => optionsRef.current.onLost?.(reason),\n onMessage: (message) => {\n setLastMessage(message);\n optionsRef.current.onMessage?.(message);\n },\n });\n controllerRef.current = controller;\n\n return () => {\n controller.close();\n controllerRef.current = null;\n };\n }, [\n url,\n enabled,\n protocolsKey,\n maxRetries,\n initialBackoff,\n maxBackoff,\n jitter,\n handshakeTimeout,\n silenceTimeout,\n waitForOnline,\n pingInterval,\n respondToPing,\n queueWhileClosed,\n maxQueuedMessages,\n optionsRef,\n ]);\n\n const send = useCallback((payload: string | Blob | BufferSource): boolean => {\n return controllerRef.current?.send(payload) ?? false;\n }, []);\n\n const reconnect = useCallback((): void => {\n controllerRef.current?.reconnect();\n }, []);\n\n const setSilenceTimeout = useCallback((ms: number): void => {\n controllerRef.current?.setSilenceTimeout(ms);\n }, []);\n\n return { status, lastMessage, send, reconnect, setSilenceTimeout };\n}\n"],"mappings":";;;;AA6DA,SAAgBA,EACZ,GACA,IAAkC,CAAC,GACd;CACrB,IAAM,EACF,aAAU,IACV,cACA,eACA,mBACA,eACA,WACA,qBACA,mBACA,kBACA,iBACA,kBACA,qBACA,yBACAC,GACE,CAACC,GAAQC,KAAaC,EAA0B,MAAM,GACtD,CAACC,GAAaC,KAAkBF,EAAqC,IAAI,GACzEG,IAAgBC,EAAmC,IAAI,GAEvDC,IAAaC,EAAaT,CAAO,GAEjCU,IAAe,MAAM,QAAQC,CAAS,IAAIA,EAAU,KAAK,GAAG,IAAKA,KAAa;CA4FpF,OA1FAC,QAAgB;EACZ,IAAI,CAACC,KAAW,CAACC,GAAK;GAClB,EAAU,MAAM;GAChB;EACJ;EASA,IAAMC,IAAYP,EAAW,QAAQ,WAAW,KAAA,GAC1CQ,IAAgBR,EAAW,QAAQ,iBAAiB,KAAA,GACpDS,IAAqBT,EAAW,QAAQ,sBAAsB,KAAA,GAC9DU,IAAeV,EAAW,QAAQ,iBAAiB,KAAA,GAEnDW,IAAaC,EAAmBN,GAAK;GACvC,WAAWN,EAAW,QAAQ;GAC9B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,aAAaA,EAAW,QAAQ;GAChC;GACA,aAAaA,EAAW,QAAQ;GAChC;GACA;GACA,cAAcU,UAAqBV,EAAW,QAAQ,eAAe,IAAI,KAAA;GACzE,QAAQO,KAAa,MAAQP,EAAW,QAAQ,SAASa,CAAG,IAAS,KAAA;GACrE,cAAcL,KACP,GAAO,MAAQR,EAAW,QAAQ,eAAec,GAAOD,CAAG,IAC5D,KAAA;GACN,QAAQb,EAAW,QAAQ;GAC3B,mBAAmBS,KACZ,GAAQ,MAAQT,EAAW,QAAQ,oBAAoBe,GAAQF,CAAG,IACnE,KAAA;GACN,gBAAgBnB;GAChB,SAAS,MAAUM,EAAW,QAAQ,SAASgB,CAAK;GACpD,UAAU,MAAUhB,EAAW,QAAQ,UAAUgB,CAAK;GACtD,UAAU,MAAUhB,EAAW,QAAQ,UAAUgB,CAAK;GACtD,iBAAiB,GAAS,MAAUhB,EAAW,QAAQ,iBAAiBiB,GAASC,CAAK;GACtF,qBAAqBlB,EAAW,QAAQ,gBAAgB;GACxD,SAAS,MAAWA,EAAW,QAAQ,SAASmB,CAAM;GACtD,YAAY,MAAY;IAEpB,AADAtB,EAAeuB,CAAO,GACtBpB,EAAW,QAAQ,YAAYoB,CAAO;GAC1C;EACJ,CAAC;EAGD,OAFA,EAAc,UAAUT,SAEX;GAET,AADAA,EAAW,MAAM,GACjB,EAAc,UAAU;EAC5B;CACJ,GAAG;EACCL;EACAD;EACAH;EACAmB;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EACA/B;CACJ,CAAC,GAcM;EAAE;EAAQ;EAAa,MAZjBgC,GAAa,MACflC,EAAc,SAAS,KAAKmC,CAAO,KAAK,IAChD,CAAC,CAU0B;EAAM,WARlBD,QAAwB;GACtC,EAAc,SAAS,UAAU;EACrC,GAAG,CAAC,CAMgC;EAAW,mBAJrBA,GAAa,MAAqB;GACxD,EAAc,SAAS,kBAAkBE,CAAE;EAC/C,GAAG,CAAC,CAE2C;CAAkB;AACrE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tempest-react-sdk",
3
- "version": "0.80.0",
3
+ "version": "0.82.0",
4
4
  "description": "SDK público da Tempest com componentes, hooks e integrações para projetos React.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -8,8 +8,33 @@ import {
8
8
  requestPersistentStorage,
9
9
  useServiceWorkerUpdate,
10
10
  } from "tempest-react-sdk";
11
+ import { clearSessionData } from "tempest-react-sdk/sw";
11
12
  import { useAuth } from "@/stores/auth";
12
13
 
14
+ /**
15
+ * Remove what this device keeps for the account that just signed out: the
16
+ * service worker's runtime caches and background-sync queue
17
+ * (`clearSessionData()`, which keeps the app-shell precache), and the browser's
18
+ * push subscription, so the next person on this device does not receive the
19
+ * previous account's notifications.
20
+ *
21
+ * The push subscription is released in the browser only: the backend delete
22
+ * needs the token that logout just dropped. The push service answers the
23
+ * backend's next send to the dead endpoint with `404`/`410`, which is when the
24
+ * backend prunes it. Every step is best-effort — logout must not fail because
25
+ * the worker is missing (as under `npm run dev`) or push is unsupported.
26
+ */
27
+ async function forgetSignedOutAccount(): Promise<void> {
28
+ await Promise.allSettled([
29
+ clearSessionData(),
30
+ (async () => {
31
+ const registration = await navigator.serviceWorker?.getRegistration();
32
+ const subscription = await registration?.pushManager?.getSubscription();
33
+ await subscription?.unsubscribe();
34
+ })(),
35
+ ]);
36
+ }
37
+
13
38
  /**
14
39
  * PWA shell: registers the service worker, wires the consent-based update
15
40
  * prompt and the offline indicator, requests persistent storage (so the
@@ -24,6 +49,12 @@ import { useAuth } from "@/stores/auth";
24
49
  * would wait unsent until they expire. A project without accounts deletes this
25
50
  * effect along with `@/stores/auth`.
26
51
  *
52
+ * And it ends the device session whenever the signed-in account goes away —
53
+ * the Logout button, a `401` reaching `onUnauthorized`, or a different account
54
+ * signing in over it — by watching the auth store instead of each logout path
55
+ * (see `forgetSignedOutAccount`). A token refresh keeps the same user, so it
56
+ * clears nothing.
57
+ *
27
58
  * Wraps the app so the fixed-position overlays render as siblings of the tree.
28
59
  * The SDK helpers no-op gracefully where a capability is unsupported.
29
60
  */
@@ -49,6 +80,17 @@ export function PwaShell({ children }: { children: ReactNode }) {
49
80
  [],
50
81
  );
51
82
 
83
+ useEffect(
84
+ () =>
85
+ useAuth.subscribe((state, previous) => {
86
+ const ended =
87
+ previous.isAuthenticated &&
88
+ (!state.isAuthenticated || state.user?.id !== previous.user?.id);
89
+ if (ended) void forgetSignedOutAccount();
90
+ }),
91
+ [],
92
+ );
93
+
52
94
  useEffect(() => {
53
95
  if (!registration) return;
54
96
  void registerPeriodicSync({ registration, minIntervalMinutes: 360 });
@@ -14,27 +14,30 @@ import {
14
14
  *
15
15
  * Layers, in order:
16
16
  * 1. Push + notifications + skip-waiting (the SDK push helpers).
17
- * 2. Background sync — failed POST/PUT/PATCH/DELETE to `/api` are queued and
18
- * replayed online. Credential headers are never stored; on replay the
19
- * worker asks the open pages for the current ones and for who is signed in,
20
- * which `src/pwa.tsx` answers from the auth store
17
+ * 2. Background sync — failed POST/PUT/PATCH/DELETE to this origin's `/api`
18
+ * are queued and replayed online. Credential headers are never stored; on
19
+ * replay the worker asks the open pages for the current ones and for who
20
+ * is signed in, which `src/pwa.tsx` answers from the auth store
21
21
  * (`provideBackgroundSyncCredentials`). Remove that and queued
22
- * authenticated mutations never replay.
22
+ * authenticated mutations never replay. The credential only goes back to
23
+ * this origin: an API on another host goes in both the `match` and
24
+ * `trustedOrigins`, or its credentialed entries are dropped on replay.
23
25
  * 3. Runtime caching: unauthenticated API GETs (network-first) + media
24
26
  * (cache-first with Range support, so audio/video seeking works offline).
25
27
  * Every request the API client sends with a bearer (`Authorization`, or any
26
28
  * other credential header) bypasses this cache and goes to the network: in
27
29
  * an app with accounts, most `/api` GETs. To serve them offline, add
28
30
  * `cacheAuthenticated: true` to the route — entries then vary on the
29
- * credential, so another account never reads them — and keep
30
- * `clearSessionData()` in the logout flow.
31
+ * credential, so another account never reads them. `src/pwa.tsx` already
32
+ * calls `clearSessionData()` whenever the signed-in account goes away.
31
33
  * 4. Precache of the app shell so the app launches offline (API navigations
32
34
  * excluded). Reads the `precache-manifest.json` emitted by
33
35
  * `tempestPwaManifest()` in `vite.config.ts`.
34
36
  *
35
37
  * Cache names carry the `tempest-` prefix so `inspectCaches({ filter: "tempest-" })`
36
- * reports them. On logout, call `clearSessionData()` from the page: it drops
37
- * the runtime caches and the background-sync queue, keeping the precache.
38
+ * reports them. On logout `src/pwa.tsx` calls `clearSessionData()` from the
39
+ * page: it drops the runtime caches and the background-sync queue, keeping the
40
+ * precache.
38
41
  *
39
42
  * `installRuntimeCache` is registered BEFORE `installPrecache` so its specific
40
43
  * routes win over the precache catch-all. The trailing `activate` listener takes
@@ -53,7 +56,9 @@ installPushHandler({
53
56
  installNotificationClickHandler();
54
57
  installSkipWaitingListener();
55
58
 
56
- installBackgroundSync({ match: (url) => url.pathname.startsWith("/api/") });
59
+ installBackgroundSync({
60
+ match: (url) => url.origin === self.location.origin && url.pathname.startsWith("/api/"),
61
+ });
57
62
 
58
63
  installRuntimeCache([
59
64
  {