-----------------------------------------------------------------------------
{-# LANGUAGE TypeApplications           #-}
{-# LANGUAGE CPP                        #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Miso.WebSocket
-- Copyright   :  (C) 2016-2026 David M. Johnson
-- License     :  BSD3-style (see the file LICENSE)
-- Maintainer  :  David M. Johnson <code@dmj.io>
-- Stability   :  experimental
-- Portability :  non-portable
--
-- = Overview
--
-- "Miso.WebSocket" provides a full-duplex
-- <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket WebSocket>
-- client that integrates directly into the MVU loop. Every operation
-- — connecting, sending, and closing — returns an 'Miso.Effect.Effect',
-- making WebSocket communication a first-class citizen of the @update@
-- function.
--
-- = Quick start
--
-- @
-- import "Miso"
-- import "Miso.WebSocket"
--
-- data Action
--   = Connect
--   | Connected  'WebSocket'
--   | Received   'Miso.String.MisoString'
--   | Disconnected 'Closed'
--   | WsError    'Miso.String.MisoString'
--   | Send       'Miso.String.MisoString'
--   | Disconnect
--
-- -- Hold the socket handle in the model so we can send later
-- data Model = Model { ws :: 'WebSocket' }
--
-- update :: Action -> 'Miso.Effect.Effect' p props Model Action
-- update Connect =
--   'connectText' \"wss:\/\/echo.websocket.org\"
--     Connected Disconnected Received WsError
-- update (Connected sock) =
--   'Miso.State.modify' (\\m -> m { ws = sock })
-- update (Received msg) =
--   'Miso.Effect.io_' (consoleLog msg)
-- update (Send txt) = do
--   sock <- 'Miso.State.gets' ws
--   'sendText' sock txt
-- update Disconnect = do
--   sock <- 'Miso.State.gets' ws
--   'close' sock
-- update _ = pure ()
-- @
--
-- = Connection variants
--
-- Five @connect@ functions cover every wire format. They all share the
-- same callback signature — @onOpen@, @onClosed@, @onMessage@, @onError@
-- — but differ in how the message payload is decoded:
--
-- ['connectText'] 'Miso.String.MisoString' — plain UTF-8 text
-- ['connectJSON'] @json@ ('Miso.JSON.FromJSON' json) — auto-decoded from JSON
-- ['connectBLOB'] 'Blob' — raw binary Blob
-- ['connectArrayBuffer'] 'ArrayBuffer' — raw binary buffer
-- ['connect'] @'Payload' json@ — mixed; caller pattern-matches the payload ADT
--
-- = Sending messages
--
-- The 'WebSocket' handle delivered to @onOpen@ must be stored in the
-- model and passed to each send call:
--
-- @
-- 'sendText'        :: 'WebSocket' -> 'Miso.String.MisoString' -> 'Miso.Effect.Effect' p props model action
-- 'sendJSON'        :: 'Miso.JSON.ToJSON' json => 'WebSocket' -> json -> 'Miso.Effect.Effect' p props model action
-- 'sendBLOB'        :: 'WebSocket' -> 'Blob' -> 'Miso.Effect.Effect' p props model action
-- 'sendArrayBuffer' :: 'WebSocket' -> 'ArrayBuffer' -> 'Miso.Effect.Effect' p props model action
-- @
--
-- = Lifecycle
--
-- * Always call 'close' when the connection is no longer needed to avoid
--   resource leaks. It is safe to call 'close' multiple times — subsequent
--   calls are no-ops.
-- * 'socketState' lets you query the current 'SocketState'
--   (@CONNECTING@, @OPEN@, @CLOSING@, @CLOSED@) asynchronously.
-- * 'emptyWebSocket' (@= -1@) is a null sentinel you can store in the
--   model before a connection has been established.
--
-- = Types
--
-- * 'WebSocket' — an opaque integer file descriptor returned by @onOpen@.
-- * 'URL' — alias for 'Miso.String.MisoString'.
-- * 'SocketState' — four-state enum mirroring the JS @readyState@ property.
-- * 'Closed' — close event data: 'closedCode', 'wasClean', 'reason'.
-- * 'CloseCode' — typed close codes from
--   <https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code RFC 6455>.
-- * 'Payload' — @JSON value | BLOB Blob | TEXT MisoString | BUFFER ArrayBuffer@.
-- * 'Blob' / 'ArrayBuffer' — re-exported from "Miso.FFI.Internal".
--
-- = See also
--
-- * "Miso.EventSource" — Server-Sent Events (SSE), a unidirectional alternative
-- * "Miso.Fetch" — one-shot HTTP requests
-- * "Miso.Effect" — 'Miso.Effect.Effect', 'Miso.Effect.io_'
----------------------------------------------------------------------------
module Miso.WebSocket
  ( -- *** t'WebSocket'
    connect
  , connectJSON
  , connectText
  , connectBLOB
  , connectArrayBuffer
  , sendText
  , sendJSON
  , sendBLOB
  , sendArrayBuffer
  , close
  , socketState
  -- *** Defaults
  , emptyWebSocket
  -- *** Types
  , WebSocket   (..)
  , URL
  , SocketState (..)
  , CloseCode   (..)
  , Closed      (..)
  , Payload     (..)
  , Blob        (..)
  , ArrayBuffer (..)
  ) where
-----------------------------------------------------------------------------
import           Miso.Effect
import           Miso.JSON
import           Miso.Runtime
import           Miso.String (MisoString)
import           Miso.FFI (Blob(..), ArrayBuffer(..))
-----------------------------------------------------------------------------
-- | <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket>
--
-- Establishes a t'WebSocket' server that receives potentially multiple different t'Payload'.
--
-- It's more common to use 'connectJSON' or 'connectText'. But 'connect' can be used to received multiple
-- different kinds of data from a t'WebSocket' server.
--
connect
  :: FromJSON json
  => URL
  -- ^ URL endpoint for a t'WebSocket' connection
  -> (WebSocket -> action)
  -- ^ @onOpen@ callback w/ t'WebSocket' object for successful connection. t'WebSocket' is used here to send messages.
  -> (Closed -> action)
  -- ^ @onClosed@ method that is called when a t'WebSocket' connection has closed.
  -> (Payload json -> action)
  -- ^ @onMessage@ is a callback invoked when a message has been received from the t'WebSocket' server.
  -> (MisoString -> action)
  -- ^ Error message callback
  -> Effect parent props model action
connect :: forall json action parent props model.
FromJSON json =>
URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (Payload json -> action)
-> (URL -> action)
-> Effect parent props model action
connect = URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (Payload json -> action)
-> (URL -> action)
-> Effect parent props model action
forall json action parent props model.
FromJSON json =>
URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (Payload json -> action)
-> (URL -> action)
-> Effect parent props model action
websocketConnect
-----------------------------------------------------------------------------
-- | <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket>
--
-- Establishes a t'WebSocket' server that assumes a JSON-encoded protocol.
--
connectJSON
  :: FromJSON json
  => URL
  -- ^ URL endpoint for a t'WebSocket' connection
  -> (WebSocket -> action)
  -- ^ @onOpen@ callback w/ t'WebSocket' object for successful connection. t'WebSocket' is used here to send messages.
  -> (Closed -> action)
  -- ^ @onClosed@ method that is called when a t'WebSocket' connection has closed.
  -> (json -> action)
  -- ^ @onMessage@ is a callback invoked when a JSON-encoded message has been received from the t'WebSocket' server.
  -> (MisoString -> action)
  -- ^ Error message callback
  -> Effect parent props model action
connectJSON :: forall json action parent props model.
FromJSON json =>
URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (json -> action)
-> (URL -> action)
-> Effect parent props model action
connectJSON = URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (json -> action)
-> (URL -> action)
-> Effect parent props model action
forall json action parent props model.
FromJSON json =>
URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (json -> action)
-> (URL -> action)
-> Effect parent props model action
websocketConnectJSON
-----------------------------------------------------------------------------
-- | <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket>
--
-- Establishes a t'WebSocket' server that assumes a text-encoded protocol.
--
connectText
  :: URL
  -- ^ URL endpoint for a t'WebSocket' connection
  -> (WebSocket -> action)
  -- ^ @onOpen@ callback w/ t'WebSocket' object for successful connection. t'WebSocket' is used here to send messages.
  -> (Closed -> action)
  -- ^ @onClosed@ method that is called when a t'WebSocket' connection has closed.
  -> (MisoString -> action)
  -- ^ @onMessage@ is a callback invoked when a text-encoded message has been received from the t'WebSocket' server.
  -> (MisoString -> action)
  -- ^ Error message callback
  -> Effect parent props model action
connectText :: forall action parent props model.
URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (URL -> action)
-> (URL -> action)
-> Effect parent props model action
connectText = URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (URL -> action)
-> (URL -> action)
-> Effect parent props model action
forall action parent props model.
URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (URL -> action)
-> (URL -> action)
-> Effect parent props model action
websocketConnectText
-----------------------------------------------------------------------------
-- | <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket>
--
-- Establishes a t'WebSocket' server that assumes a binary-encoded protocol.
--
connectBLOB
  :: URL
  -- ^ URL endpoint for a t'WebSocket' connection
  -> (WebSocket -> action)
  -- ^ @onOpen@ callback w/ t'WebSocket' object for successful connection. t'WebSocket' is used here to send messages.
  -> (Closed -> action)
  -- ^ @onClosed@ method that is called when a t'WebSocket' connection has closed.
  -> (Blob -> action)
  -- ^ @onMessage@ is a callback invoked when a binary-encoded message has been received from the t'WebSocket' server.
  -> (MisoString -> action)
  -- ^ @onError@ callback
  -> Effect parent props model action
connectBLOB :: forall action parent props model.
URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (Blob -> action)
-> (URL -> action)
-> Effect parent props model action
connectBLOB = URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (Blob -> action)
-> (URL -> action)
-> Effect parent props model action
forall action parent props model.
URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (Blob -> action)
-> (URL -> action)
-> Effect parent props model action
websocketConnectBLOB
-----------------------------------------------------------------------------
-- | <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket>
--
-- Establishes a t'WebSocket' server that assumes an t'ArrayBuffer' protocol.
--
connectArrayBuffer
  :: URL
  -- ^ URL endpoint for a t'WebSocket' connection
  -> (WebSocket -> action)
  -- ^ @onOpen@ callback w/ t'WebSocket' object for successful connection. t'WebSocket' is used here to send messages.
  -> (Closed -> action)
  -- ^ @onClosed@ method that is called when a t'WebSocket' connection has closed.
  -> (ArrayBuffer -> action)
  -- ^ @onMessage@ is a callback invoked when an t'ArrayBuffer' message has been received from the t'WebSocket' server.
  -> (MisoString -> action)
  -- ^ @onError@ callback
  -> Effect parent props model action
connectArrayBuffer :: forall action parent props model.
URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (ArrayBuffer -> action)
-> (URL -> action)
-> Effect parent props model action
connectArrayBuffer = URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (ArrayBuffer -> action)
-> (URL -> action)
-> Effect parent props model action
forall action parent props model.
URL
-> (WebSocket -> action)
-> (Closed -> action)
-> (ArrayBuffer -> action)
-> (URL -> action)
-> Effect parent props model action
websocketConnectArrayBuffer
-----------------------------------------------------------------------------
-- | <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send>
--
-- @
--
-- data Person = Person { name :: MisoString, age :: Int }
--   deriving (Show, Eq)
--
-- instance ToJSON Person where
--   toJSON (Person name age) = object [ "name" .= name, "age" .= age ]
--
-- test :: WebSocket -> Effect parent props model action
-- test connection = do
--   sendJSON (connection :: WebSocket) (Person "alice" 42)
--   sendJSON (connection :: WebSocket) (Person "bob" 42)
--
-- @
--
sendJSON
  :: ToJSON json
  => WebSocket
  -- ^ t'WebSocket' descriptor required to send a message to the server.
  -> json
  -- ^ A JSON-encoded message
  -> Effect parent props model action
sendJSON :: forall json parent props model action.
ToJSON json =>
WebSocket -> json -> Effect parent props model action
sendJSON WebSocket
socket json
x = WebSocket -> Payload json -> Effect parent props model action
forall value parent props model action.
ToJSON value =>
WebSocket -> Payload value -> Effect parent props model action
websocketSend WebSocket
socket (json -> Payload json
forall value. value -> Payload value
JSON json
x)
-----------------------------------------------------------------------------
-- | <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send>
sendBLOB
  :: WebSocket
  -- ^ t'WebSocket' descriptor required to send a message to the server.
  -> Blob
  -- ^ An t'Blob' payload to send to the t'WebSocket' server
  -> Effect parent props model action
sendBLOB :: forall parent props model action.
WebSocket -> Blob -> Effect parent props model action
sendBLOB WebSocket
socket Blob
x = forall value parent props model action.
ToJSON value =>
WebSocket -> Payload value -> Effect parent props model action
websocketSend @() WebSocket
socket (Blob -> Payload ()
forall value. Blob -> Payload value
blob Blob
x)
-----------------------------------------------------------------------------
-- | <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send>
sendArrayBuffer
  :: WebSocket
  -- ^ t'WebSocket' descriptor required to send a message to the server.
  -> ArrayBuffer
  -- ^ An t'ArrayBuffer' payload to send to the t'WebSocket' server
  -> Effect parent props model action
sendArrayBuffer :: forall parent props model action.
WebSocket -> ArrayBuffer -> Effect parent props model action
sendArrayBuffer WebSocket
socket ArrayBuffer
x = forall value parent props model action.
ToJSON value =>
WebSocket -> Payload value -> Effect parent props model action
websocketSend @() WebSocket
socket (ArrayBuffer -> Payload ()
forall value. ArrayBuffer -> Payload value
arrayBuffer ArrayBuffer
x)
-----------------------------------------------------------------------------
-- | <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send>
sendText
  :: WebSocket
  -- ^ t'WebSocket' descriptor required to send a message to the server.
  -> MisoString
  -- ^ A text payload to send to t'WebSocket' server
  -> Effect parent props model action
sendText :: forall parent props model action.
WebSocket -> URL -> Effect parent props model action
sendText WebSocket
socket URL
x = forall value parent props model action.
ToJSON value =>
WebSocket -> Payload value -> Effect parent props model action
websocketSend @() WebSocket
socket (URL -> Payload ()
forall value. URL -> Payload value
TEXT URL
x)
-----------------------------------------------------------------------------
-- | <https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close>
--
-- It is very important to close the t'WebSocket', otherwise leaks can occur.
--
-- 'close' is a no-op if invoked multiple times.
--
close
  :: WebSocket
  -- ^ t'WebSocket' descriptor required to close the socket on the server.
  -> Effect parent props model action
close :: forall parent props model action.
WebSocket -> Effect parent props model action
close = WebSocket -> Effect parent props model action
forall parent props model action.
WebSocket -> Effect parent props model action
websocketClose
-----------------------------------------------------------------------------