summaryrefslogtreecommitdiff
path: root/src/Attach.hs
diff options
context:
space:
mode:
authorRoman Smrž <roman.smrz@seznam.cz>2026-07-04 21:56:35 +0200
committerRoman Smrž <roman.smrz@seznam.cz>2026-07-22 21:15:38 +0200
commit7811d5658c75344dd1787cd821c5c28e2a3dab32 (patch)
tree6a4316e0761f35f19f1caa4958b7839155b7f9da /src/Attach.hs
parent9686f788eab5690edad7a6b692aa2a926cddc8b8 (diff)
Attach service handling using URL parameter
Diffstat (limited to 'src/Attach.hs')
-rw-r--r--src/Attach.hs157
1 files changed, 157 insertions, 0 deletions
diff --git a/src/Attach.hs b/src/Attach.hs
new file mode 100644
index 0000000..c15b0a5
--- /dev/null
+++ b/src/Attach.hs
@@ -0,0 +1,157 @@
+module Attach (
+ AttachState(..),
+ attachTemplate,
+ initiateAttachConnection,
+) where
+
+import Control.Concurrent
+import Control.Monad
+import Control.Monad.Except
+import Control.Monad.Reader
+
+import Data.Proxy
+import Data.Text qualified as T
+
+import Erebos.Attach
+import Erebos.Discovery
+import Erebos.Error
+import Erebos.Identity
+import Erebos.Network
+import Erebos.Object
+import Erebos.Pairing
+import Erebos.Service
+import Erebos.State
+import Erebos.Storable
+import Erebos.Storage
+import Erebos.Storage.Key
+
+import Text.Blaze.Html5 ((!))
+import Text.Blaze.Html5 qualified as H
+import Text.Blaze.Html5.Attributes qualified as A
+
+import JavaScript
+import WebSocket (startClient, receiveMessage)
+
+
+data AttachState = AttachState
+ { asStorage :: Storage
+ , asHead :: Head LocalState
+ , asServer :: Server
+ }
+
+
+attachTemplate :: H.Html
+attachTemplate = do
+ H.h2 $ "Attach to other device"
+
+ H.div ! A.class_ "content" $ do
+ H.div ! A.class_ "warning" $ do
+ "Deletes all local data, including conversations and contacts,\
+ \ and synchronizes with the remote device."
+
+ H.div ! A.id "attach_details" $ do
+ H.div ! A.class_ "attach_details_item" $ do
+ "Name: "
+ H.span ! A.id "attach_details_name" $ return ()
+ H.div ! A.class_ "attach_details_item" $ do
+ "Confirmation code: "
+ H.span ! A.id "attach_details_code" $ return ()
+
+ H.button ! A.id "attach_confirm" ! A.disabled "" $ do
+ "Confirm and proceed (deleting data)"
+
+ H.button ! A.id "attach_cancel" $ do
+ "Cancel"
+
+initiateAttachConnection :: Head LocalState -> RefDigest -> IO AttachState
+initiateAttachConnection baseHead dgst = do
+ let baseStorage = headStorage baseHead
+ Just nameElem <- getElementById "attach_details_name"
+ Just codeElem <- getElementById "attach_details_code"
+ Just confirmButton <- getElementById "attach_confirm"
+ Just cancelButton <- getElementById "attach_cancel"
+
+ asStorage <- deriveEphemeralStorage baseStorage
+ asHead <- (either (fail . showErebosError) return =<<) $ runExceptT $ flip runReaderT asStorage $ do
+ let devName = T.pack "WebApp"
+ identity <- createIdentity (Just devName) Nothing
+ storeHead asStorage $ LocalState
+ { lsPrev = Nothing
+ , lsIdentity = idExtData identity
+ , lsShared = []
+ , lsOther = []
+ }
+
+ asServer <- startServer defaultServerOptions asHead consoleLog
+ [ someServiceAttr (defaultPairingAttributes (Proxy @AttachService))
+ { pairingHookResponse = \confirm ->
+ afterCommit $ do
+ setTextContent confirm codeElem
+ removeAttribute "disabled" confirmButton
+ , pairingHookAcceptedResponse = do
+ afterCommit $ do
+ Just chead <- reloadHead asHead
+ moveKeys asStorage baseStorage
+ void $ updateHead_ baseHead $ \_ -> return (headStoredObject chead)
+ locationAssign "./"
+ }
+ , someService @DiscoveryService Proxy
+ ]
+
+ attachRequested <- newMVar Nothing
+ addEventListener confirmButton "click" $ \_ -> do
+ readMVar attachRequested >>= \case
+ Just peer -> do
+ runExceptT (attachAccept peer) >>= \case
+ Right _ -> do
+ consoleLog "Attach accepted"
+ Left err -> do
+ consoleLog $ "Failed to accept attach: " <> showErebosError err
+ Nothing -> do
+ consoleLog $ "Attach not requested yet"
+ addEventListener cancelButton "click" $ \_ -> do
+ readMVar attachRequested >>= \case
+ Just peer -> do
+ runExceptT (attachReject peer) >>= \case
+ Right _ -> do
+ consoleLog "Attach rejected"
+ Left err -> do
+ consoleLog $ "Failed to reject attach: " <> showErebosError err
+ Nothing -> return ()
+ locationAssign "./"
+
+ startClient asServer "a.discovery.erebosprotocol.net" 443 "" $ \conn -> do
+ void $ forkIO $ forever $ do
+ msg <- receiveMessage conn
+ receivedFromCustomAddress asServer conn msg
+ void $ serverPeerCustom asServer conn
+
+ runExceptT (discoverySearch asServer dgst) >>= \case
+ Right _ -> return ()
+ Left err -> consoleLog $ "Failed to search for " <> show dgst <> ": " <> showErebosError err
+
+ void $ forkIO $ do
+ let loop = do
+ peer <- getNextPeerChange asServer
+ getPeerIdentity peer >>= \case
+ PeerIdentityFull pid
+ | dgst `elem` identityDigests pid -> do
+ modifyMVar_ attachRequested $ \case
+ Nothing -> do
+ runExceptT (attachToOwner peer) >>= \case
+ Right _ -> do
+ consoleLog "Attach request sent"
+ return $ Just peer
+ Left err -> do
+ consoleLog $ "Failed send attach request to " <> show dgst <> ": " <> showErebosError err
+ return $ Nothing
+ cur -> return cur
+ setTextContent (T.unpack $ displayIdentity pid) nameElem
+ _ -> loop
+ loop
+
+ return AttachState {..}
+
+
+identityDigests :: Foldable f => Identity f -> [ RefDigest ]
+identityDigests pid = map (refDigest . storedRef) $ idDataF =<< unfoldOwners pid