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.
- package/README.md +40 -10
- package/bin/create-tempest-app.e2e.test.mjs +31 -0
- package/bin/create-tempest-app.mjs +11 -1
- package/bin/lib/component/generate.test.mjs +13 -1
- package/bin/lib/doctor/doctor.e2e.test.mjs +29 -0
- package/bin/lib/doctor/vitest.mjs +120 -0
- package/bin/lib/doctor/vitest.test.mjs +139 -0
- package/bin/lib/fs/contain.mjs +7 -0
- package/bin/lib/openapi/escape.mjs +68 -4
- package/bin/lib/openapi/generate.hostile.test.mjs +150 -1
- package/bin/lib/openapi/generate.mjs +78 -13
- package/bin/lib/openapi/generate.test.mjs +13 -1
- package/bin/lib/openapi/pattern-risk.mjs +134 -0
- package/bin/lib/openapi/pattern-risk.test.mjs +45 -0
- package/bin/tempest-gen.test.mjs +100 -0
- package/bin/tempest.mjs +24 -8
- package/dist/access/use-can.cjs +1 -1
- package/dist/access/use-can.cjs.map +1 -1
- package/dist/access/use-can.js +25 -28
- package/dist/access/use-can.js.map +1 -1
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +234 -122
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/auth/session-identity.cjs +2 -0
- package/dist/auth/session-identity.cjs.map +1 -0
- package/dist/auth/session-identity.js +12 -0
- package/dist/auth/session-identity.js.map +1 -0
- package/dist/br/boleto.cjs +1 -1
- package/dist/br/boleto.cjs.map +1 -1
- package/dist/br/boleto.js +2 -1
- package/dist/br/boleto.js.map +1 -1
- package/dist/br.d.ts +22 -3
- package/dist/components/AuthForm/AuthFieldControl.cjs +2 -0
- package/dist/components/AuthForm/AuthFieldControl.cjs.map +1 -0
- package/dist/components/AuthForm/AuthFieldControl.js +85 -0
- package/dist/components/AuthForm/AuthFieldControl.js.map +1 -0
- package/dist/components/AuthForm/AuthForm.cjs +2 -0
- package/dist/components/AuthForm/AuthForm.cjs.map +1 -0
- package/dist/components/AuthForm/AuthForm.js +83 -0
- package/dist/components/AuthForm/AuthForm.js.map +1 -0
- package/dist/components/AuthForm/AuthForm.module.cjs +3 -0
- package/dist/components/AuthForm/AuthForm.module.cjs.map +1 -0
- package/dist/components/AuthForm/AuthForm.module.js +14 -0
- package/dist/components/AuthForm/AuthForm.module.js.map +1 -0
- package/dist/components/AuthForm/AuthLinks.cjs +2 -0
- package/dist/components/AuthForm/AuthLinks.cjs.map +1 -0
- package/dist/components/AuthForm/AuthLinks.js +32 -0
- package/dist/components/AuthForm/AuthLinks.js.map +1 -0
- package/dist/components/AuthForm/ForgotPasswordForm.cjs +2 -0
- package/dist/components/AuthForm/ForgotPasswordForm.cjs.map +1 -0
- package/dist/components/AuthForm/ForgotPasswordForm.js +32 -0
- package/dist/components/AuthForm/ForgotPasswordForm.js.map +1 -0
- package/dist/components/AuthForm/LoginForm.cjs +2 -0
- package/dist/components/AuthForm/LoginForm.cjs.map +1 -0
- package/dist/components/AuthForm/LoginForm.js +39 -0
- package/dist/components/AuthForm/LoginForm.js.map +1 -0
- package/dist/components/AuthForm/ResetPasswordForm.cjs +2 -0
- package/dist/components/AuthForm/ResetPasswordForm.cjs.map +1 -0
- package/dist/components/AuthForm/ResetPasswordForm.js +39 -0
- package/dist/components/AuthForm/ResetPasswordForm.js.map +1 -0
- package/dist/components/AuthForm/SignUpForm.cjs +2 -0
- package/dist/components/AuthForm/SignUpForm.cjs.map +1 -0
- package/dist/components/AuthForm/SignUpForm.js +32 -0
- package/dist/components/AuthForm/SignUpForm.js.map +1 -0
- package/dist/components/AuthForm/auth-defaults.cjs +2 -0
- package/dist/components/AuthForm/auth-defaults.cjs.map +1 -0
- package/dist/components/AuthForm/auth-defaults.js +54 -0
- package/dist/components/AuthForm/auth-defaults.js.map +1 -0
- package/dist/components/AuthForm/auth-schemas.cjs +2 -0
- package/dist/components/AuthForm/auth-schemas.cjs.map +1 -0
- package/dist/components/AuthForm/auth-schemas.js +26 -0
- package/dist/components/AuthForm/auth-schemas.js.map +1 -0
- package/dist/components/AuthPage/AuthPage.cjs +2 -0
- package/dist/components/AuthPage/AuthPage.cjs.map +1 -0
- package/dist/components/AuthPage/AuthPage.js +97 -0
- package/dist/components/AuthPage/AuthPage.js.map +1 -0
- package/dist/components/AuthPage/AuthPage.module.cjs +3 -0
- package/dist/components/AuthPage/AuthPage.module.cjs.map +1 -0
- package/dist/components/AuthPage/AuthPage.module.js +24 -0
- package/dist/components/AuthPage/AuthPage.module.js.map +1 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js +22 -21
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/Button/Button.cjs +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js +41 -40
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Chat/ChatAttachments.cjs +1 -1
- package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
- package/dist/components/Chat/ChatAttachments.js +38 -38
- package/dist/components/FilterBar/filter-apply.cjs +1 -1
- package/dist/components/FilterBar/filter-apply.cjs.map +1 -1
- package/dist/components/FilterBar/filter-apply.js +7 -7
- package/dist/components/Form/Form.cjs +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js +30 -32
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/Layout/Layout.cjs +1 -1
- package/dist/components/Layout/Layout.cjs.map +1 -1
- package/dist/components/Layout/Layout.js +25 -24
- package/dist/components/Layout/Layout.js.map +1 -1
- package/dist/components/Layout/gap.cjs +2 -0
- package/dist/components/Layout/gap.cjs.map +1 -0
- package/dist/components/Layout/gap.js +15 -0
- package/dist/components/Layout/gap.js.map +1 -0
- package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js +71 -70
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.js +30 -29
- package/dist/components/Sidebar/SidebarEntry.js.map +1 -1
- package/dist/data/create-data-provider.cjs +1 -1
- package/dist/data/create-data-provider.cjs.map +1 -1
- package/dist/data/create-data-provider.js +1 -2
- package/dist/data/create-data-provider.js.map +1 -1
- package/dist/forms/index.js +7 -7
- package/dist/forms/masked-inputs.cjs +1 -1
- package/dist/forms/masked-inputs.cjs.map +1 -1
- package/dist/forms/masked-inputs.js +15 -15
- package/dist/forms/validate-form.cjs +1 -1
- package/dist/forms/validate-form.cjs.map +1 -1
- package/dist/forms/validate-form.js +6 -1
- package/dist/forms/validate-form.js.map +1 -1
- package/dist/forms/zod-resolver.cjs +1 -1
- package/dist/forms/zod-resolver.cjs.map +1 -1
- package/dist/forms/zod-resolver.js +8 -3
- package/dist/forms/zod-resolver.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +137 -116
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/credential-scope.cjs +1 -1
- package/dist/http/credential-scope.cjs.map +1 -1
- package/dist/http/credential-scope.js +33 -9
- package/dist/http/credential-scope.js.map +1 -1
- package/dist/http/presigned-post.cjs.map +1 -1
- package/dist/http/presigned-post.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +140 -134
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +17 -17
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/http/use-describe-api-error.cjs +1 -1
- package/dist/http/use-describe-api-error.cjs.map +1 -1
- package/dist/http/use-describe-api-error.js +9 -9
- package/dist/oauth/GoogleSignIn.cjs +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js +5 -4
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- package/dist/offline/create-offline-sync.cjs +1 -1
- package/dist/offline/create-offline-sync.cjs.map +1 -1
- package/dist/offline/create-offline-sync.js +108 -68
- package/dist/offline/create-offline-sync.js.map +1 -1
- package/dist/query/offline-persistence.cjs +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/offline-persistence.js +54 -44
- package/dist/query/offline-persistence.js.map +1 -1
- package/dist/sse/create-event-stream.cjs +1 -1
- package/dist/sse/create-event-stream.cjs.map +1 -1
- package/dist/sse/create-event-stream.js +48 -41
- package/dist/sse/create-event-stream.js.map +1 -1
- package/dist/sse/ticket-event-stream.cjs +1 -1
- package/dist/sse/ticket-event-stream.cjs.map +1 -1
- package/dist/sse/ticket-event-stream.js +48 -19
- package/dist/sse/ticket-event-stream.js.map +1 -1
- package/dist/sse/use-event-stream.cjs +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/sse/use-event-stream.js +19 -14
- package/dist/sse/use-event-stream.js.map +1 -1
- package/dist/styles/AuthForm.css +2 -0
- package/dist/styles/AuthPage.css +2 -0
- package/dist/styles/component/AuthForm.css +9 -0
- package/dist/styles/component/AuthPage.css +29 -0
- package/dist/styles/forms.css +36 -0
- package/dist/styles/manifest.json +63 -0
- package/dist/styles.css +1 -1
- package/dist/sw/background-sync.cjs +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js +132 -112
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw/cache.cjs +1 -1
- package/dist/sw/cache.cjs.map +1 -1
- package/dist/sw/cache.js +36 -30
- package/dist/sw/cache.js.map +1 -1
- package/dist/sw/create-push-handler.cjs +1 -1
- package/dist/sw/create-push-handler.cjs.map +1 -1
- package/dist/sw/create-push-handler.js +20 -10
- package/dist/sw/create-push-handler.js.map +1 -1
- package/dist/sw.d.ts +93 -28
- package/dist/tabular/compact.cjs +1 -1
- package/dist/tabular/compact.cjs.map +1 -1
- package/dist/tabular/compact.js +35 -16
- package/dist/tabular/compact.js.map +1 -1
- package/dist/tabular/manifest.cjs +1 -1
- package/dist/tabular/manifest.cjs.map +1 -1
- package/dist/tabular/manifest.js +33 -15
- package/dist/tabular/manifest.js.map +1 -1
- package/dist/tabular.d.ts +11 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +1057 -83
- package/dist/tempest-react-sdk.js +373 -365
- package/dist/theme/initial-theme.cjs +4 -4
- package/dist/theme/initial-theme.cjs.map +1 -1
- package/dist/theme/initial-theme.js +8 -5
- package/dist/theme/initial-theme.js.map +1 -1
- package/dist/utils/compressed-storage.cjs +1 -1
- package/dist/utils/compressed-storage.cjs.map +1 -1
- package/dist/utils/compressed-storage.js +13 -7
- package/dist/utils/compressed-storage.js.map +1 -1
- package/dist/utils/format.cjs +1 -1
- package/dist/utils/format.cjs.map +1 -1
- package/dist/utils/format.js +25 -25
- package/dist/utils/numbers.cjs +1 -1
- package/dist/utils/numbers.cjs.map +1 -1
- package/dist/utils/numbers.js +7 -7
- package/dist/utils/objects.cjs +1 -1
- package/dist/utils/objects.cjs.map +1 -1
- package/dist/utils/objects.js +13 -7
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/safe-url.cjs +1 -1
- package/dist/utils/safe-url.cjs.map +1 -1
- package/dist/utils/safe-url.js +4 -1
- package/dist/utils/safe-url.js.map +1 -1
- package/dist/webrtc/peer-mesh.cjs +1 -1
- package/dist/webrtc/peer-mesh.cjs.map +1 -1
- package/dist/webrtc/peer-mesh.js +7 -1
- package/dist/webrtc/peer-mesh.js.map +1 -1
- package/dist/ws/create-web-socket.cjs +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js +77 -61
- package/dist/ws/create-web-socket.js.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +4 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +1 -1
- package/template-pwa/src/pwa.tsx +42 -0
- 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,
|
|
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 =
|
|
43
|
-
|
|
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;
|
|
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
package/template-pwa/src/pwa.tsx
CHANGED
|
@@ -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 });
|
package/template-pwa/src/sw.ts
CHANGED
|
@@ -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`
|
|
18
|
-
* replayed online. Credential headers are never stored; on
|
|
19
|
-
* worker asks the open pages for the current ones and for who
|
|
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
|
|
30
|
-
* `clearSessionData()`
|
|
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
|
|
37
|
-
* the runtime caches and the background-sync queue, keeping the
|
|
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({
|
|
59
|
+
installBackgroundSync({
|
|
60
|
+
match: (url) => url.origin === self.location.origin && url.pathname.startsWith("/api/"),
|
|
61
|
+
});
|
|
57
62
|
|
|
58
63
|
installRuntimeCache([
|
|
59
64
|
{
|