packages feed

haste-lib-0.6.0.0: src/Haste/Crypto/Prim.hs

{-# LANGUAGE OverloadedStrings #-}
module Haste.Crypto.Prim where
import Haste
import Haste.Foreign
import Haste.Crypto.Types
import Data.Array.Unboxed
import Data.Array.IO
import Data.Word
import Haste.Concurrent

-- | Fill the given array with random bits, using the browser's @window.crypto@
--   object.
randomBits :: ArrView e => IOUArray Word32 e -> IO ()
randomBits = ffi "(function(arr){window['__haste_crypto'].getRandomValues(arr);})"

-- | Wait for a promise to either complete or get rejected. Promises used with
--   this function should not get rejected unless there is a bug in this very
--   library.
--   Promises that may fail for normal reasons should not use this function.
promise :: MonadConc m
        => ((a -> IO ()) -> (JSString -> IO ()) -> IO ())
        -> m a
promise f = liftCIO $ do
  res <- promiseE f
  case res of
    Right x -> return x
    Left e  -> error ("promise rejected: " ++ fromJSStr e)

-- | A promise that might fail.
promiseE :: MonadConc m
        => ((a -> IO ()) -> (JSString -> IO ()) -> IO ())
        -> m (Either JSString a)
promiseE f = liftCIO $ do
  v <- newEmptyMVar
  liftIO $ f (concurrent . putMVar v . Right) (concurrent . putMVar v . Left)
  takeMVar v

generateKey' :: Cipher -> (SymmetricKey -> IO ()) -> (JSString -> IO ()) -> IO ()
generateKey' = ffi "(function(c, yay, nay) {\
  \window['__haste_crypto'].subtle.generateKey(c, true, ['encrypt','decrypt'])\
  \.then(function(k) {yay({key: k, cipher: c});})\
  \.catch(nay);\
  \})"

encrypt' :: (Ix i, ArrView e)
         => SymmetricKey -> IV -> UArray i e
         -> (UArray Word32 Word8 -> IO ())
         -> (JSString -> IO ())
         -> IO ()
encrypt' = ffi "(function(k, iv, data, yay, nay) {\
  \k.cipher.iv = iv;\
  \window['__haste_crypto'].subtle.encrypt(k.cipher, k.key, data)\
  \.then(function(enc){yay(new Uint8Array(enc));})\
  \.catch(nay);\
  \})"

decrypt' :: (Ix i, ArrView e)
         => SymmetricKey -> IV -> UArray i e
         -> (UArray Word32 Word8 -> IO ())
         -> (JSString -> IO ())
         -> IO ()
decrypt' = ffi "(function(k, iv, data, yay, nay) {\
  \k.cipher.iv = iv;\
  \window['__haste_crypto'].subtle.decrypt(k.cipher, k.key, data)\
  \.then(function(dec){yay(new Uint8Array(dec));})\
  \.catch(nay);\
  \})"

keyBytes' :: SymmetricKey
          -> (UArray Word32 Word8 -> IO ())
          -> (JSString -> IO ())
          -> IO ()
keyBytes' = ffi "(function(k, yay, nay){\
  \window['__haste_crypto'].subtle.exportKey('raw', k.key)\
  \.then(function(dec){yay(new Uint8Array(dec));})\
  \.catch(nay);\
  \})"

keyFromBytes' :: Cipher
              -> UArray Word32 Word8
              -> (SymmetricKey -> IO ())
              -> (JSString -> IO ())
              -> IO ()
keyFromBytes' = ffi "(function(alg, k, yay, nay){\
  \window['__haste_crypto'].subtle.importKey('raw', k, alg, true, ['encrypt', 'decrypt'])\
  \.then(function(k) {yay({key: k, cipher: alg});})\
  \.catch(nay);\
  \})"

deriveKey' :: Cipher -> Salt -> Int -> UArray Word32 Word8 -> (Maybe SymmetricKey -> IO ()) -> (JSString -> IO ()) -> IO ()
deriveKey' = ffi "(function(alg, s, n, k, yay, nay){\
  \window['__haste_crypto'].subtle.importKey('raw', k, {name:'PBKDF2'}, false, ['deriveKey']).then(function(mk) {\
  \window['__haste_crypto'].subtle.deriveKey({name:'PBKDF2',salt:s,iterations:n,hash:'SHA-256'}, mk, alg, true, ['encrypt', 'decrypt']).then(function(k){yay({key: k, cipher: alg});}).catch(function(_){yay(null);});\
  \});})"

encodeUtf8' :: JSString -> IO (UArray Word32 Word8)
encodeUtf8' = ffi "(function(s){\
  \s = unescape(encodeURIComponent(s));\
  \var arr = new Uint8Array(s.length);\
  \for(var i = 0 ; i < s.length; ++i) {\
  \  arr[i] = s.charCodeAt(i);\
  \}\
  \return arr;\
  \})"