diff --git a/go.mod b/go.mod index 43305904a3acf6edaa1be95ae7cb30ca9e523316..680aeae38ca4edd1294355cfe02f54d947f3a74f 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ require ( github.com/hack-pad/go-indexeddb v0.2.0 github.com/pkg/errors v0.9.1 github.com/spf13/jwalterweatherman v1.1.0 - gitlab.com/elixxir/client/v4 v4.3.9-0.20221210003613-b73478d56e0d - gitlab.com/elixxir/crypto v0.0.7-0.20221209195912-492ced8f7c92 - gitlab.com/elixxir/primitives v0.0.3-0.20221114231218-cc461261a6af + gitlab.com/elixxir/client/v4 v4.3.12-0.20221219215743-62264017437e + gitlab.com/elixxir/crypto v0.0.7-0.20221219161351-7c3751afd8f2 + gitlab.com/elixxir/primitives v0.0.3-0.20221214192222-988b44a6958a gitlab.com/xx_network/crypto v0.0.5-0.20221121220724-8eefdbb0eb46 gitlab.com/xx_network/primitives v0.0.4-0.20221209210320-376735467d58 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa @@ -17,7 +17,7 @@ require ( require ( filippo.io/edwards25519 v1.0.0 // indirect - git.xx.network/elixxir/grpc-web-go-client v0.0.0-20221102223039-dc1f37d94e70 // indirect + git.xx.network/elixxir/grpc-web-go-client v0.0.0-20221215201903-f66fe2cea35f // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/badoux/checkmail v1.2.1 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect @@ -59,9 +59,9 @@ require ( github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/zeebo/blake3 v0.2.3 // indirect gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f // indirect - gitlab.com/elixxir/comms v0.0.4-0.20221110181420-84bca6216fe4 // indirect + gitlab.com/elixxir/comms v0.0.4-0.20221215214627-7807bfdde33a // indirect gitlab.com/elixxir/ekv v0.2.1 // indirect - gitlab.com/xx_network/comms v0.0.4-0.20221207203143-462f82d6ec01 // indirect + gitlab.com/xx_network/comms v0.0.4-0.20221215214252-1275cef8760e // indirect gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect gitlab.com/yawning/nyquist.git v0.0.0-20221003103146-de5645224a22 // indirect @@ -78,4 +78,5 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.7 // indirect + src.agwa.name/tlshacks v0.0.0-20220518131152-d2c6f4e2b780 // indirect ) diff --git a/go.sum b/go.sum index f4fa8682d9207c9ee0037e02dacf10c361217d50..003ae101f705ae6473a5c2d1aa11d4987a3d3da2 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,10 @@ filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.xx.network/elixxir/grpc-web-go-client v0.0.0-20221102223039-dc1f37d94e70 h1:p24wUpzdil0wgyFerGJM69fD5Xz9hsBDBK8f9m01pq8= git.xx.network/elixxir/grpc-web-go-client v0.0.0-20221102223039-dc1f37d94e70/go.mod h1:uFKw2wmgtlYMdiIm08dM0Vj4XvX9ZKVCj71c8O7SAPo= +git.xx.network/elixxir/grpc-web-go-client v0.0.0-20221215181401-0b8a26d47532 h1:EH4TFLgXGgofV2MsUOgNDmn3X+qfhbQ2RV6zOYRaSdU= +git.xx.network/elixxir/grpc-web-go-client v0.0.0-20221215181401-0b8a26d47532/go.mod h1:uFKw2wmgtlYMdiIm08dM0Vj4XvX9ZKVCj71c8O7SAPo= +git.xx.network/elixxir/grpc-web-go-client v0.0.0-20221215201903-f66fe2cea35f h1:0/09N8iy4t3a//5z5VB1tNjmx6XnPxtcjpOGGPoVs5c= +git.xx.network/elixxir/grpc-web-go-client v0.0.0-20221215201903-f66fe2cea35f/go.mod h1:uFKw2wmgtlYMdiIm08dM0Vj4XvX9ZKVCj71c8O7SAPo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -496,18 +500,50 @@ gitlab.com/elixxir/client/v4 v4.3.9-0.20221208215024-325fafebf519 h1:nxGzvotSXP/ gitlab.com/elixxir/client/v4 v4.3.9-0.20221208215024-325fafebf519/go.mod h1:ID5txokZGTr7l+xTAoEtQMSSdvNZUBntJAWTR81upds= gitlab.com/elixxir/client/v4 v4.3.9-0.20221210003613-b73478d56e0d h1:Ydy9DnxHrrCfHY2UI6//88wT9L2kKtXUuA6di7ER3Ew= gitlab.com/elixxir/client/v4 v4.3.9-0.20221210003613-b73478d56e0d/go.mod h1:76yQ2oAQgAIFsb71+sZXeb361RBEfOU7jjY8gtyXTgU= +gitlab.com/elixxir/client/v4 v4.3.11 h1:LGXdAjnGdWbK1eVtrAn5Fwage7vrlrgPXsoEtUJ4Lyg= +gitlab.com/elixxir/client/v4 v4.3.11/go.mod h1:gxRW2YXDxCzESBnYqMDKH2I25TGv226TDujdQno3JQw= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219162223-d8e2974631fd h1:/GmQucH2O1LLLQ0FrexmUMivW2BuMfLTXQ8n3nYPKj4= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219162223-d8e2974631fd/go.mod h1:lwm2uYZQHy12rDtANimw02Hfws8osuVkCKN6snm/jCU= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219191501-3789240dd3ed h1:5Gahh0ZkYoJEFgqDm6IYhPQYsUBVmfexVLcxB5ewauQ= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219191501-3789240dd3ed/go.mod h1:lwm2uYZQHy12rDtANimw02Hfws8osuVkCKN6snm/jCU= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219191912-e8cd5d26d6df h1:E+LeA/BoZVkAiqjJ09QkIHXeAld5guOoaTuFkR1kwtY= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219191912-e8cd5d26d6df/go.mod h1:lwm2uYZQHy12rDtANimw02Hfws8osuVkCKN6snm/jCU= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219194340-7f6106b6ec9c h1:LP07xMoRjwngjY2CIr8+ArZ3r8sCXnDUNe6CWLLg9xU= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219194340-7f6106b6ec9c/go.mod h1:lwm2uYZQHy12rDtANimw02Hfws8osuVkCKN6snm/jCU= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219194906-a8842179cf63 h1:4Adk73psccchWDHeI9pm/uI/ztIwErU/3bnxCCUe9kA= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219194906-a8842179cf63/go.mod h1:lwm2uYZQHy12rDtANimw02Hfws8osuVkCKN6snm/jCU= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219202220-4a509612689a h1:UR69fckejY5S1hjg/qkDLu+hi6rT0gYUPgq6fXYGC8k= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219202220-4a509612689a/go.mod h1:lwm2uYZQHy12rDtANimw02Hfws8osuVkCKN6snm/jCU= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219212339-818b3e804db2 h1:G+yJqmvXOmCmskwRndMg5MS9hFXgeBhWCcPyDv+L99w= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219212339-818b3e804db2/go.mod h1:lwm2uYZQHy12rDtANimw02Hfws8osuVkCKN6snm/jCU= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219214237-b1d228b3a2ee h1:STHVMACRZRSiC2+d5opRbbqwNp7jmOVemxbrPju6/mo= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219214237-b1d228b3a2ee/go.mod h1:lwm2uYZQHy12rDtANimw02Hfws8osuVkCKN6snm/jCU= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219215313-24f256274d49 h1:CHKcCZ0RIsfF0/KnEmeq9ca4gDLLTDzkhCw29u/a+OY= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219215313-24f256274d49/go.mod h1:lwm2uYZQHy12rDtANimw02Hfws8osuVkCKN6snm/jCU= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219215743-62264017437e h1:LoQu539ZiyEagrfrs8gaZ0w4IyjQomfZwSK/QVJ4ejA= +gitlab.com/elixxir/client/v4 v4.3.12-0.20221219215743-62264017437e/go.mod h1:lwm2uYZQHy12rDtANimw02Hfws8osuVkCKN6snm/jCU= gitlab.com/elixxir/comms v0.0.4-0.20221110181420-84bca6216fe4 h1:bLRjVCyMVde4n2hTVgoyyIAWrKI4CevpChchkPeb6A0= gitlab.com/elixxir/comms v0.0.4-0.20221110181420-84bca6216fe4/go.mod h1:XhI2/CMng+xcH3mAs+1aPz29PSNu1079XMJ8V+xxihw= +gitlab.com/elixxir/comms v0.0.4-0.20221215214627-7807bfdde33a h1:DuqDqWc5cWjZ3qk98K1Bf9y1dYlyCeIigFmkHWDKc1Q= +gitlab.com/elixxir/comms v0.0.4-0.20221215214627-7807bfdde33a/go.mod h1:B2Yek4mCbtN2aXZkyZcUffd3sTEZ5WgKD0mRBSVYtF8= gitlab.com/elixxir/crypto v0.0.7-0.20221208214832-13e2a751db1a h1:d514iJOaPmH2qjqUyI1N93UyEPTWvZ40LJiRPvQ89jw= gitlab.com/elixxir/crypto v0.0.7-0.20221208214832-13e2a751db1a/go.mod h1:fb6UMdmr0hVnzOU67hOZzTeS+wcQZ4pUtTO82039wGg= gitlab.com/elixxir/crypto v0.0.7-0.20221209195912-492ced8f7c92 h1:76JsxCk9Qwe1SFzYkrP42YVffhDcHXjwDBH/B3YS40U= gitlab.com/elixxir/crypto v0.0.7-0.20221209195912-492ced8f7c92/go.mod h1:fb6UMdmr0hVnzOU67hOZzTeS+wcQZ4pUtTO82039wGg= +gitlab.com/elixxir/crypto v0.0.7-0.20221214192244-6783272c04a0 h1:dwCf7wKv2DCuYZZ394bSQWdUOXiABLsEyDvXZUOo83o= +gitlab.com/elixxir/crypto v0.0.7-0.20221214192244-6783272c04a0/go.mod h1:oRh3AwveOEvpk9E3kRcMGK8fImcEnN0PY4jr9HDgQE8= +gitlab.com/elixxir/crypto v0.0.7-0.20221219161351-7c3751afd8f2 h1:QPTL5+jHqfauN8WrmHMwvJ/d0Rbm/EY20Iggeu+NPvs= +gitlab.com/elixxir/crypto v0.0.7-0.20221219161351-7c3751afd8f2/go.mod h1:7whUm4bnEdEoiVfMnu3TbHgvlrz0Ywp/Tekqg2Wl7vw= gitlab.com/elixxir/ekv v0.2.1 h1:dtwbt6KmAXG2Tik5d60iDz2fLhoFBgWwST03p7T+9Is= gitlab.com/elixxir/ekv v0.2.1/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU= gitlab.com/elixxir/primitives v0.0.3-0.20221114231218-cc461261a6af h1:xcPqknK1ehNb9xwcutTdoR0YgD7DC/ySh9z49tIpSxQ= gitlab.com/elixxir/primitives v0.0.3-0.20221114231218-cc461261a6af/go.mod h1:DUnCTXYKgjpro5+6ITySKIf+qzW2vhW40IVHMimdsqw= +gitlab.com/elixxir/primitives v0.0.3-0.20221214192222-988b44a6958a h1:F17FfEjS+/uDI/TTYQD21S5JvNZ9+p9bieau2nyLCzo= +gitlab.com/elixxir/primitives v0.0.3-0.20221214192222-988b44a6958a/go.mod h1:DUnCTXYKgjpro5+6ITySKIf+qzW2vhW40IVHMimdsqw= gitlab.com/xx_network/comms v0.0.4-0.20221207203143-462f82d6ec01 h1:0jkud7OWqneH9xEjDARnLspspxA3SDsNrbg8heY+wMQ= gitlab.com/xx_network/comms v0.0.4-0.20221207203143-462f82d6ec01/go.mod h1:+RfHgk75ywMvmucOpPS7rSUlsnbPyBuLsr13tsthUTE= +gitlab.com/xx_network/comms v0.0.4-0.20221215214252-1275cef8760e h1:l+FiCBP2Lc1+cR6xwWDVDvSHnuzOaZFIRUEYGUwKGBA= +gitlab.com/xx_network/comms v0.0.4-0.20221215214252-1275cef8760e/go.mod h1:FR/OyruSuob6+xzSZtk+rXlncbRr6nDKFypX3vwtkFc= gitlab.com/xx_network/crypto v0.0.5-0.20221121220724-8eefdbb0eb46 h1:6AHgUpWdJ72RVTTdJSvfThZiYTQNUnrPaTCl/EkRLpg= gitlab.com/xx_network/crypto v0.0.5-0.20221121220724-8eefdbb0eb46/go.mod h1:acWUBKCpae/XVaQF7J9RnLAlBT13i5r7gnON+mrIxBk= gitlab.com/xx_network/primitives v0.0.4-0.20221110180011-fd6ea3058225 h1:TAn87e6Zt9KwcSnWKyIul5eu8T0RHY9FDubCGs3G0dw= @@ -939,3 +975,5 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +src.agwa.name/tlshacks v0.0.0-20220518131152-d2c6f4e2b780 h1:iMW3HbLV3/OuK02FDW8qNC13i5o1uK079MGLH404rnQ= +src.agwa.name/tlshacks v0.0.0-20220518131152-d2c6f4e2b780/go.mod h1:NT4HI59yJusF5Il4/DlC8F5+mfylE4CbRVwdoEi6MF8= diff --git a/indexedDb/channels/implementation.go b/indexedDb/channels/implementation.go index bcd957f13863d47e2ce493f6c00b5e5bfd4775b4..cf64d77f6dc3a492990110061e5d76b0ac8e4cf4 100644 --- a/indexedDb/channels/implementation.go +++ b/indexedDb/channels/implementation.go @@ -13,11 +13,12 @@ import ( "crypto/ed25519" "encoding/base64" "encoding/json" - "gitlab.com/elixxir/xxdk-wasm/indexedDb" "sync" "syscall/js" "time" + "gitlab.com/elixxir/xxdk-wasm/indexedDb" + "github.com/hack-pad/go-indexeddb/idb" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" @@ -26,6 +27,7 @@ import ( "gitlab.com/elixxir/client/v4/cmix/rounds" cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast" cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/elixxir/crypto/message" "gitlab.com/elixxir/xxdk-wasm/utils" "gitlab.com/xx_network/primitives/id" ) @@ -166,7 +168,7 @@ func (w *wasmModel) deleteMsgByChannel(channelID *id.ID) error { // It may be called multiple times on the same message; it is incumbent on the // user of the API to filter such called by message ID. func (w *wasmModel) ReceiveMessage(channelID *id.ID, - messageID cryptoChannel.MessageID, nickname, text string, + messageID message.ID, nickname, text string, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time, lease time.Duration, round rounds.Round, mType channels.MessageType, status channels.SentStatus) uint64 { @@ -202,7 +204,7 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, // Messages may arrive our of order, so a reply, in theory, can arrive before // the initial message. As a result, it may be important to buffer replies. func (w *wasmModel) ReceiveReply(channelID *id.ID, - messageID cryptoChannel.MessageID, replyTo cryptoChannel.MessageID, + messageID message.ID, replyTo message.ID, nickname, text string, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time, lease time.Duration, round rounds.Round, mType channels.MessageType, status channels.SentStatus) uint64 { @@ -238,7 +240,7 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, // Messages may arrive our of order, so a reply, in theory, can arrive before // the initial message. As a result, it may be important to buffer reactions. func (w *wasmModel) ReceiveReaction(channelID *id.ID, - messageID cryptoChannel.MessageID, reactionTo cryptoChannel.MessageID, + messageID message.ID, reactionTo message.ID, nickname, reaction string, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time, lease time.Duration, round rounds.Round, mType channels.MessageType, status channels.SentStatus) uint64 { @@ -272,7 +274,7 @@ func (w *wasmModel) ReceiveReaction(channelID *id.ID, // // TODO: Potential race condition due to separate get/update operations. func (w *wasmModel) UpdateSentStatus(uuid uint64, - messageID cryptoChannel.MessageID, timestamp time.Time, round rounds.Round, + messageID message.ID, timestamp time.Time, round rounds.Round, status channels.SentStatus) { parentErr := errors.New("failed to UpdateSentStatus") @@ -303,7 +305,7 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, } newMessage.Status = uint8(status) - if !messageID.Equals(cryptoChannel.MessageID{}) { + if !messageID.Equals(message.ID{}) { newMessage.MessageID = messageID.Bytes() } @@ -387,7 +389,7 @@ func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64, // NOTE: Sometimes the insert fails to return an error but hits a duplicate // insert, so this fallthrough returns the UUID entry in that case. if res.IsUndefined() { - msgID := cryptoChannel.MessageID{} + msgID := message.ID{} copy(msgID[:], newMessage.MessageID) uuid, errLookup := w.msgIDLookup(msgID) if uuid != 0 && errLookup == nil { @@ -402,7 +404,7 @@ func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64, } // msgIDLookup gets the UUID of the Message with the given messageID. -func (w *wasmModel) msgIDLookup(messageID cryptoChannel.MessageID) (uint64, +func (w *wasmModel) msgIDLookup(messageID message.ID) (uint64, error) { msgIDStr := js.ValueOf(base64.StdEncoding.EncodeToString(messageID.Bytes())) resultObj, err := indexedDb.GetIndex(w.db, messageStoreName, diff --git a/indexedDb/channels/implementation_test.go b/indexedDb/channels/implementation_test.go index 9f17457013d26125f05d411d29b09afff4cd16c3..8f240d9f5f5f949f9501a7c8c05e3249f78f5b5f 100644 --- a/indexedDb/channels/implementation_test.go +++ b/indexedDb/channels/implementation_test.go @@ -12,22 +12,23 @@ package channels import ( "encoding/json" "fmt" + "os" + "strconv" + "testing" + "time" + "github.com/hack-pad/go-indexeddb/idb" "gitlab.com/elixxir/xxdk-wasm/indexedDb" "gitlab.com/elixxir/xxdk-wasm/storage" "gitlab.com/xx_network/crypto/csprng" "gitlab.com/xx_network/primitives/netTime" - "os" - "strconv" - "testing" - "time" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/v4/channels" "gitlab.com/elixxir/client/v4/cmix/rounds" cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast" - "gitlab.com/elixxir/crypto/channel" cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/elixxir/crypto/message" "gitlab.com/xx_network/primitives/id" ) @@ -53,7 +54,8 @@ func TestWasmModel_msgIDLookup(t *testing.T) { storage.GetLocalStorage().Clear() testString := "test" - testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1}) + testMsgId := message.DeriveChannelMessageID(&id.ID{1}, + 0, []byte(testString)) eventModel, err := newWASMModel(testString, c, dummyCallback) if err != nil { t.Fatalf("%+v", err) @@ -92,7 +94,8 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) { t.Run(fmt.Sprintf("Test_wasmModel_UpdateSentStatus%s", cs), func(t *testing.T) { storage.GetLocalStorage().Clear() testString := "test" - testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1}) + testMsgId := message.DeriveChannelMessageID(&id.ID{1}, + 0, []byte(testString)) eventModel, err := newWASMModel(testString, c, dummyCallback) if err != nil { t.Fatalf("%+v", err) @@ -221,7 +224,7 @@ func Test_wasmModel_UUIDTest(t *testing.T) { for i := 0; i < 10; i++ { // Store a test message channelID := id.NewIdFromBytes([]byte(testString), t) - msgID := channel.MessageID{} + msgID := message.ID{} copy(msgID[:], testString+fmt.Sprintf("%d", i)) rnd := rounds.Round{ID: id.Round(42)} uuid := eventModel.ReceiveMessage(channelID, msgID, "test", @@ -263,7 +266,7 @@ func Test_wasmModel_DuplicateReceives(t *testing.T) { uuids := make([]uint64, 10) - msgID := channel.MessageID{} + msgID := message.ID{} copy(msgID[:], testString) for i := 0; i < 10; i++ { // Store a test message @@ -324,7 +327,8 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) { thisChannel = keepChannel } - testMsgId := channel.MakeMessageID([]byte(testStr), &id.ID{1}) + testMsgId := message.DeriveChannelMessageID( + &id.ID{1}, 0, []byte(testStr)) eventModel.ReceiveMessage(thisChannel, testMsgId, testStr, testStr, []byte{8, 6, 7, 5}, 0, 0, netTime.Now(), time.Second, rounds.Round{ID: id.Round(0)}, 0, channels.Sent) @@ -398,7 +402,8 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) { } // First message insert should succeed - testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1}) + testMsgId := message.DeriveChannelMessageID(&id.ID{1}, + 0, []byte(testString)) testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, netTime.Now(), time.Second, 0, 0, channels.Sent) @@ -421,7 +426,8 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) { } // Now insert a message with a different message ID from the first - testMsgId2 := channel.MakeMessageID([]byte(testString), &id.ID{2}) + testMsgId2 := message.DeriveChannelMessageID(&id.ID{1}, + 0, []byte(testString)) testMsg = buildMessage([]byte(testString), testMsgId2.Bytes(), nil, testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, netTime.Now(), time.Second, 0, 0, channels.Sent) diff --git a/indexedDb/dm/implementation.go b/indexedDb/dm/implementation.go index 5f2361ac1363714d3f8d899231ab7f746a8ad1b9..6324aecfa8d38e463496e95213b5dba89b9e18ab 100644 --- a/indexedDb/dm/implementation.go +++ b/indexedDb/dm/implementation.go @@ -12,6 +12,11 @@ package channelEventModel import ( "crypto/ed25519" "encoding/json" + "strings" + "sync" + "syscall/js" + "time" + "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/v4/cmix/rounds" @@ -19,13 +24,10 @@ import ( "gitlab.com/elixxir/xxdk-wasm/indexedDb" "gitlab.com/elixxir/xxdk-wasm/utils" "gitlab.com/xx_network/primitives/id" - "strings" - "sync" - "syscall/js" - "time" "github.com/hack-pad/go-indexeddb/idb" cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/elixxir/crypto/message" ) // wasmModel implements [dm.Receiver] interface, which uses the channels @@ -93,7 +95,7 @@ func buildMessage(messageID, parentID []byte, text []byte, } } -func (w *wasmModel) Receive(messageID dm.MessageID, nickname string, text []byte, +func (w *wasmModel) Receive(messageID message.ID, nickname string, text []byte, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time, round rounds.Round, mType dm.MessageType, status dm.Status) uint64 { parentErr := errors.New("failed to Receive") @@ -135,7 +137,7 @@ func (w *wasmModel) Receive(messageID dm.MessageID, nickname string, text []byte return uuid } -func (w *wasmModel) ReceiveText(messageID dm.MessageID, nickname, text string, +func (w *wasmModel) ReceiveText(messageID message.ID, nickname, text string, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time, round rounds.Round, status dm.Status) uint64 { parentErr := errors.New("failed to ReceiveText") @@ -179,7 +181,7 @@ func (w *wasmModel) ReceiveText(messageID dm.MessageID, nickname, text string, return uuid } -func (w *wasmModel) ReceiveReply(messageID dm.MessageID, reactionTo dm.MessageID, +func (w *wasmModel) ReceiveReply(messageID message.ID, reactionTo message.ID, nickname, text string, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time, round rounds.Round, status dm.Status) uint64 { parentErr := errors.New("failed to ReceiveReply") @@ -223,7 +225,7 @@ func (w *wasmModel) ReceiveReply(messageID dm.MessageID, reactionTo dm.MessageID return uuid } -func (w *wasmModel) ReceiveReaction(messageID dm.MessageID, reactionTo dm.MessageID, +func (w *wasmModel) ReceiveReaction(messageID message.ID, reactionTo message.ID, nickname, reaction string, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time, round rounds.Round, status dm.Status) uint64 { parentErr := errors.New("failed to ReceiveText") @@ -268,7 +270,7 @@ func (w *wasmModel) ReceiveReaction(messageID dm.MessageID, reactionTo dm.Messag } func (w *wasmModel) UpdateSentStatus(uuid uint64, - messageID dm.MessageID, timestamp time.Time, round rounds.Round, + messageID message.ID, timestamp time.Time, round rounds.Round, status dm.Status) { parentErr := errors.New("failed to UpdateSentStatus") @@ -299,7 +301,7 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, } newMessage.Status = uint8(status) - if !messageID.Equals(dm.MessageID{}) { + if !messageID.Equals(message.ID{}) { newMessage.MessageID = messageID.Bytes() } @@ -351,7 +353,7 @@ func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64, // NOTE: Sometimes the insert fails to return an error but hits a duplicate // insert, so this fallthrough returns the UUID entry in that case. if res.IsUndefined() { - msgID := cryptoChannel.MessageID{} + msgID := message.ID{} copy(msgID[:], newMessage.MessageID) uuid, errLookup := w.msgIDLookup(msgID) if uuid != 0 && errLookup == nil { @@ -366,7 +368,7 @@ func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64, } // msgIDLookup gets the UUID of the Message with the given messageID. -func (w *wasmModel) msgIDLookup(messageID cryptoChannel.MessageID) (uint64, +func (w *wasmModel) msgIDLookup(messageID message.ID) (uint64, error) { resultObj, err := indexedDb.GetIndex(w.db, messageStoreName, messageStoreMessageIndex, utils.CopyBytesToJS(messageID.Marshal())) diff --git a/indexedDb/dm/init.go b/indexedDb/dm/init.go index d2c3ba1dbb2d6306a39ffc8f5f0b4e6f0159dc73..380df647c810d7fb6239cee9c4e7c1fc4181e4ca 100644 --- a/indexedDb/dm/init.go +++ b/indexedDb/dm/init.go @@ -11,6 +11,8 @@ package channelEventModel import ( "crypto/ed25519" + "syscall/js" + "github.com/hack-pad/go-indexeddb/idb" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" @@ -18,7 +20,6 @@ import ( cryptoChannel "gitlab.com/elixxir/crypto/channel" "gitlab.com/elixxir/xxdk-wasm/indexedDb" "gitlab.com/elixxir/xxdk-wasm/storage" - "syscall/js" ) const ( @@ -39,7 +40,7 @@ type MessageReceivedCallback func(uuid uint64, pubKey ed25519.PublicKey, update // NewWASMEventModel returns a [channels.EventModel] backed by a wasmModel. // The name should be a base64 encoding of the users public key. func NewWASMEventModel(path string, encryption cryptoChannel.Cipher, - cb MessageReceivedCallback) (dm.Receiver, error) { + cb MessageReceivedCallback) (dm.EventModel, error) { databaseName := path + databaseSuffix return newWASMModel(databaseName, encryption, cb) } diff --git a/main.go b/main.go index d06333fb8dd96ea545ad3d00fb182e8849ae1dad..d18364cc74455d2b2c68b1b97e423d8c68d4025e 100644 --- a/main.go +++ b/main.go @@ -11,13 +11,14 @@ package main import ( "fmt" + "os" + "syscall/js" + jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/v4/bindings" "gitlab.com/elixxir/xxdk-wasm/storage" "gitlab.com/elixxir/xxdk-wasm/utils" "gitlab.com/elixxir/xxdk-wasm/wasm" - "os" - "syscall/js" ) func init() { @@ -89,6 +90,15 @@ func main() { js.Global().Set("NewChannelsDatabaseCipher", js.FuncOf(wasm.NewChannelsDatabaseCipher)) + // wasm/dm.go + js.Global().Set("NewDMClient", js.FuncOf(wasm.NewDMClient)) + js.Global().Set("NewDMClientWithIndexedDb", + js.FuncOf(wasm.NewDMClientWithIndexedDb)) + js.Global().Set("NewDMClientWithIndexedDbUnsafe", + js.FuncOf(wasm.NewDMClientWithIndexedDbUnsafe)) + js.Global().Set("NewDMsDatabaseCipher", + js.FuncOf(wasm.NewDMsDatabaseCipher)) + // wasm/cmix.go js.Global().Set("NewCmix", js.FuncOf(wasm.NewCmix)) js.Global().Set("LoadCmix", js.FuncOf(wasm.LoadCmix)) diff --git a/wasm/dm.go b/wasm/dm.go new file mode 100644 index 0000000000000000000000000000000000000000..678ca74f529f1ccce1a0a8cac63242b5073ed835 --- /dev/null +++ b/wasm/dm.go @@ -0,0 +1,872 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 xx foundation // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file. // +//////////////////////////////////////////////////////////////////////////////// + +//go:build js && wasm + +package wasm + +import ( + "crypto/ed25519" + "syscall/js" + + indexDB "gitlab.com/elixxir/xxdk-wasm/indexedDb/dm" + + "encoding/base64" + + "gitlab.com/elixxir/client/v4/bindings" + "gitlab.com/elixxir/crypto/codename" + "gitlab.com/elixxir/xxdk-wasm/utils" +) + +//////////////////////////////////////////////////////////////////////////////// +// Basic Channel API // +//////////////////////////////////////////////////////////////////////////////// + +// DMClient wraps the [bindings.DMClient] object so its methods +// can be wrapped to be Javascript compatible. +type DMClient struct { + api *bindings.DMClient +} + +// newDMClientJS creates a new Javascript compatible object +// (map[string]any) that matches the [DMClient] structure. +func newDMClientJS(api *bindings.DMClient) map[string]any { + cm := DMClient{api} + dmClientMap := map[string]any{ + // Basic Channel API + "GetID": js.FuncOf(cm.GetID), + + // Identity and Nickname Controls + "GetPublicKey": js.FuncOf(cm.GetPublicKey), + "GetToken": js.FuncOf(cm.GetToken), + "GetIdentity": js.FuncOf(cm.GetIdentity), + "ExportPrivateIdentity": js.FuncOf(cm.ExportPrivateIdentity), + "SetNickname": js.FuncOf(cm.SetNickname), + "GetNickname": js.FuncOf(cm.GetNickname), + + // DM Sending Methods and Reports + "SendText": js.FuncOf(cm.SendText), + "SendReply": js.FuncOf(cm.SendReply), + "SendReaction": js.FuncOf(cm.SendReaction), + "Send": js.FuncOf(cm.Send), + } + + return dmClientMap +} + +// GetPublicKey returns the ecdh Public Key for this [DMClient] in the +// [DMClient] tracker. +// +// Returns: +// - Tracker ID (int). +func (ch *DMClient) GetID(js.Value, []js.Value) any { + return ch.api.GetID() +} + +func (ch *DMClient) GetPublicKey(js.Value, []js.Value) any { + return ch.api.GetPublicKey() +} + +func (ch *DMClient) GetToken(js.Value, []js.Value) any { + return ch.api.GetToken() +} + +// dmReceiverBuilder adheres to the [bindings.DMReceiverBuilder] interface. +type dmReceiverBuilder struct { + build func(args ...any) js.Value +} + +// Build initializes and returns the event model. It wraps a Javascript object +// that has all the methods in [bindings.EventModel] to make it adhere to the Go +// interface [bindings.EventModel]. +func (emb *dmReceiverBuilder) Build(path string) bindings.DMReceiver { + emJs := emb.build(path) + return &dmReceiver{ + receive: utils.WrapCB(emJs, "ReceiveText"), + receiveText: utils.WrapCB(emJs, "ReceiveText"), + receiveReply: utils.WrapCB(emJs, "ReceiveReply"), + receiveReaction: utils.WrapCB(emJs, "ReceiveReaction"), + updateSentStatus: utils.WrapCB(emJs, "UpdateSentStatus"), + } +} + +// NewDMClient creates a new [DMClient] from a new private +// identity ([channel.PrivateIdentity]). +// +// This is for creating a manager for an identity for the first time. For +// generating a new one channel identity, use [GenerateChannelIdentity]. To +// reload this channel manager, use [LoadDMClient], passing in the +// storage tag retrieved by [DMClient.GetStorageTag]. +// +// Parameters: +// - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved +// using [Cmix.GetID]. +// - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is +// generated by [GenerateChannelIdentity] (Uint8Array). +// - args[2] - A function that initialises and returns a Javascript object +// that matches the [bindings.EventModel] interface. The function must match +// the Build function in [bindings.EventModelBuilder]. +// +// Returns: +// - Javascript representation of the [DMClient] object. +// - Throws a TypeError if creating the manager fails. +func NewDMClient(_ js.Value, args []js.Value) any { + privateIdentity := utils.CopyBytesToGo(args[1]) + + em := &dmReceiverBuilder{args[2].Invoke} + + cm, err := bindings.NewDMClient(args[0].Int(), privateIdentity, em) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + return newDMClientJS(cm) +} + +// NewDMClientWithIndexedDb creates a new [DMClient] from a new +// private identity ([channel.PrivateIdentity]) and using indexedDb as a backend +// to manage the event model. +// +// This is for creating a manager for an identity for the first time. For +// generating a new one channel identity, use [GenerateChannelIdentity]. To +// reload this channel manager, use [LoadDMClientWithIndexedDb], passing +// in the storage tag retrieved by [DMClient.GetStorageTag]. +// +// This function initialises an indexedDb database. +// +// Parameters: +// - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved +// using [Cmix.GetID]. +// - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is +// generated by [GenerateChannelIdentity] (Uint8Array). +// - args[2] - Function that takes in the same parameters as +// [indexedDb.MessageReceivedCallback]. On the Javascript side, the UUID is +// returned as an int and the channelID as a Uint8Array. The row in the +// database that was updated can be found using the UUID. The channel ID is +// provided so that the recipient can filter if they want to the processes +// the update now or not. An "update" bool is present which tells you if the +// row is new or if it is an edited old row. +// - args[3] - ID of [ChannelDbCipher] object in tracker (int). Create this +// object with [NewChannelsDatabaseCipher] and get its id with +// [ChannelDbCipher.GetID]. +// +// Returns a promise: +// - Resolves to a Javascript representation of the [DMClient] object. +// - Rejected with an error if loading indexedDb or the manager fails. +// - Throws a TypeError if the cipher ID does not correspond to a cipher. +func NewDMClientWithIndexedDb(_ js.Value, args []js.Value) any { + cmixID := args[0].Int() + privateIdentity := utils.CopyBytesToGo(args[1]) + messageReceivedCB := args[2] + cipherID := args[3].Int() + + cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID) + if err != nil { + utils.Throw(utils.TypeError, err) + } + + return newDMClientWithIndexedDb( + cmixID, privateIdentity, messageReceivedCB, cipher) +} + +// NewDMClientWithIndexedDbUnsafe creates a new [DMClient] from a +// new private identity ([channel.PrivateIdentity]) and using indexedDb as a +// backend to manage the event model. However, the data is written in plain text +// and not encrypted. It is recommended that you do not use this in production. +// +// This is for creating a manager for an identity for the first time. For +// generating a new one channel identity, use [GenerateChannelIdentity]. To +// reload this channel manager, use [LoadDMClientWithIndexedDbUnsafe], +// passing in the storage tag retrieved by [DMClient.GetStorageTag]. +// +// This function initialises an indexedDb database. +// +// Parameters: +// - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved +// using [Cmix.GetID]. +// - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is +// generated by [GenerateChannelIdentity] (Uint8Array). +// - args[2] - Function that takes in the same parameters as +// [indexedDb.MessageReceivedCallback]. On the Javascript side, the UUID is +// returned as an int and the channelID as a Uint8Array. The row in the +// database that was updated can be found using the UUID. The channel ID is +// provided so that the recipient can filter if they want to the processes +// the update now or not. An "update" bool is present which tells you if +// the row is new or if it is an edited old row +// +// Returns a promise: +// - Resolves to a Javascript representation of the [DMClient] object. +// - Rejected with an error if loading indexedDb or the manager fails. +func NewDMClientWithIndexedDbUnsafe(_ js.Value, args []js.Value) any { + cmixID := args[0].Int() + privateIdentity := utils.CopyBytesToGo(args[1]) + messageReceivedCB := args[2] + + return newDMClientWithIndexedDb( + cmixID, privateIdentity, messageReceivedCB, nil) +} + +func newDMClientWithIndexedDb(cmixID int, privateIdentity []byte, + cb js.Value, cipher *bindings.ChannelDbCipher) any { + + messageReceivedCB := func(uuid uint64, pubKey ed25519.PublicKey, + update bool) { + cb.Invoke(uuid, utils.CopyBytesToJS(pubKey[:]), update) + } + + promiseFn := func(resolve, reject func(args ...any) js.Value) { + + pi, err := codename.UnmarshalPrivateIdentity(privateIdentity) + if err != nil { + reject(utils.JsTrace(err)) + } + dmPath := base64.RawStdEncoding.EncodeToString(pi.PubKey[:]) + model, err := indexDB.NewWASMEventModel(dmPath, cipher, + messageReceivedCB) + if err != nil { + reject(utils.JsTrace(err)) + } + + cm, err := bindings.NewDMClientWithGoEventModel( + cmixID, privateIdentity, model) + if err != nil { + reject(utils.JsTrace(err)) + } else { + resolve(newDMClientJS(cm)) + } + } + + return utils.CreatePromise(promiseFn) +} + +//////////////////////////////////////////////////////////////////////////////// +// Channel Sending Methods and Reports // +//////////////////////////////////////////////////////////////////////////////// + +// SendGeneric is used to send a raw message over a channel. In general, it +// should be wrapped in a function which defines the wire protocol. If the final +// message, before being sent over the wire, is too long, this will return an +// error. Due to the underlying encoding using compression, it isn't possible to +// define the largest payload that can be sent, but it will always be possible +// to send a payload of 802 bytes at minimum. The meaning of validUntil depends +// on the use case. +// +// Parameters: +// - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). +// - args[1] - The message type of the message. This will be a valid +// [channels.MessageType] (int). +// - args[2] - The contents of the message (Uint8Array). +// - args[3] - The lease of the message. This will be how long the message is +// valid until, in milliseconds. As per the [channels.Manager] +// documentation, this has different meanings depending on the use case. +// These use cases may be generic enough that they will not be enumerated +// here (int). +// - args[4] - JSON of [xxdk.CMIXParams]. If left empty +// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). +// +// Returns a promise: +// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array). +// - Rejected with an error if sending fails. +func (ch *DMClient) Send(_ js.Value, args []js.Value) any { + messageType := args[0].Int() + partnerPubKeyBytes := utils.CopyBytesToGo(args[1]) + partnerToken := args[2].Int() + message := utils.CopyBytesToGo(args[3]) + leaseTimeMS := int64(args[4].Int()) + cmixParamsJSON := utils.CopyBytesToGo(args[5]) + + promiseFn := func(resolve, reject func(args ...any) js.Value) { + sendReport, err := ch.api.Send(messageType, partnerPubKeyBytes, + uint32(partnerToken), message, leaseTimeMS, + cmixParamsJSON) + if err != nil { + reject(utils.JsTrace(err)) + } else { + resolve(utils.CopyBytesToJS(sendReport)) + } + } + + return utils.CreatePromise(promiseFn) +} + +// SendMessage is used to send a formatted message over a channel. +// Due to the underlying encoding using compression, it isn't possible to define +// the largest payload that can be sent, but it will always be possible to send +// a payload of 798 bytes at minimum. +// +// The message will auto delete validUntil after the round it is sent in, +// lasting forever if [channels.ValidForever] is used. +// +// Parameters: +// - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). +// - args[1] - The contents of the message (string). +// - args[2] - The lease of the message. This will be how long the message is +// valid until, in milliseconds. As per the [channels.Manager] +// documentation, this has different meanings depending on the use case. +// These use cases may be generic enough that they will not be enumerated +// here (int). +// - args[3] - JSON of [xxdk.CMIXParams]. If left empty +// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). +// +// Returns a promise: +// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array). +// - Rejected with an error if sending fails. +func (ch *DMClient) SendText(_ js.Value, args []js.Value) any { + partnerPubKeyBytes := utils.CopyBytesToGo(args[0]) + partnerToken := args[1].Int() + message := args[2].String() + leaseTimeMS := int64(args[3].Int()) + cmixParamsJSON := utils.CopyBytesToGo(args[4]) + + promiseFn := func(resolve, reject func(args ...any) js.Value) { + sendReport, err := ch.api.SendText(partnerPubKeyBytes, + uint32(partnerToken), message, leaseTimeMS, + cmixParamsJSON) + if err != nil { + reject(utils.JsTrace(err)) + } else { + resolve(utils.CopyBytesToJS(sendReport)) + } + } + + return utils.CreatePromise(promiseFn) +} + +// SendReply is used to send a formatted message over a channel. Due to the +// underlying encoding using compression, it isn't possible to define the +// largest payload that can be sent, but it will always be possible to send a +// payload of 766 bytes at minimum. +// +// If the message ID the reply is sent to is nonexistent, the other side will +// post the message as a normal message and not a reply. The message will auto +// delete validUntil after the round it is sent in, lasting forever if +// [channels.ValidForever] is used. +// +// Parameters: +// - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). +// - args[1] - The contents of the message. The message should be at most 510 +// bytes. This is expected to be Unicode, and thus a string data type is +// expected (string). +// - args[2] - JSON of [channel.MessageID] of the message you wish to reply +// to. This may be found in the [bindings.ChannelSendReport] if replying to +// your own. Alternatively, if reacting to another user's message, you may +// retrieve it via the [bindings.ChannelMessageReceptionCallback] registered +// using RegisterReceiveHandler (Uint8Array). +// - args[3] - The lease of the message. This will be how long the message is +// valid until, in milliseconds. As per the [channels.Manager] +// documentation, this has different meanings depending on the use case. +// These use cases may be generic enough that they will not be enumerated +// here (int). +// - args[4] - JSON of [xxdk.CMIXParams]. If left empty +// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). +// +// Returns a promise: +// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array). +// - Rejected with an error if sending fails. +func (ch *DMClient) SendReply(_ js.Value, args []js.Value) any { + partnerPubKeyBytes := utils.CopyBytesToGo(args[0]) + partnerToken := args[1].Int() + replyID := utils.CopyBytesToGo(args[2]) + message := args[3].String() + leaseTimeMS := int64(args[4].Int()) + cmixParamsJSON := utils.CopyBytesToGo(args[5]) + + promiseFn := func(resolve, reject func(args ...any) js.Value) { + sendReport, err := ch.api.SendReply(partnerPubKeyBytes, + uint32(partnerToken), message, replyID, leaseTimeMS, + cmixParamsJSON) + if err != nil { + reject(utils.JsTrace(err)) + } else { + resolve(utils.CopyBytesToJS(sendReport)) + } + } + + return utils.CreatePromise(promiseFn) +} + +// SendReaction is used to send a reaction to a message over a channel. +// The reaction must be a single emoji with no other characters, and will +// be rejected otherwise. +// Users will drop the reaction if they do not recognize the reactTo message. +// +// Parameters: +// - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). +// - args[1] - The user's reaction. This should be a single emoji with no +// other characters. As such, a Unicode string is expected (string). +// - args[2] - JSON of [channel.MessageID] of the message you wish to reply +// to. This may be found in the [bindings.ChannelSendReport] if replying to +// your own. Alternatively, if reacting to another user's message, you may +// retrieve it via the ChannelMessageReceptionCallback registered using +// RegisterReceiveHandler (Uint8Array). +// - args[3] - JSON of [xxdk.CMIXParams]. If left empty +// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). +// +// Returns a promise: +// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array). +// - Rejected with an error if sending fails. +func (ch *DMClient) SendReaction(_ js.Value, args []js.Value) any { + partnerPubKeyBytes := utils.CopyBytesToGo(args[0]) + partnerToken := args[1].Int() + replyID := utils.CopyBytesToGo(args[2]) + message := args[3].String() + cmixParamsJSON := utils.CopyBytesToGo(args[4]) + + promiseFn := func(resolve, reject func(args ...any) js.Value) { + sendReport, err := ch.api.SendReaction(partnerPubKeyBytes, + uint32(partnerToken), message, replyID, + cmixParamsJSON) + if err != nil { + reject(utils.JsTrace(err)) + } else { + resolve(utils.CopyBytesToJS(sendReport)) + } + } + + return utils.CreatePromise(promiseFn) +} + +// GetIdentity returns the marshaled public identity ([codename.Identity]) that +// the client is using. +// +// Returns: +// - JSON of the [channel.Identity] (Uint8Array). +// - Throws TypeError if marshalling the identity fails. +func (ch *DMClient) GetIdentity(js.Value, []js.Value) any { + i := ch.api.GetIdentity() + + return utils.CopyBytesToJS(i) +} + +// ExportPrivateIdentity encrypts and exports the private identity to a portable +// string. +// +// Parameters: +// - args[0] - Password to encrypt the identity with (string). +// +// Returns: +// - JSON of the encrypted private identity (Uint8Array). +// - Throws TypeError if exporting the identity fails. +func (ch *DMClient) ExportPrivateIdentity(_ js.Value, args []js.Value) any { + i, err := ch.api.ExportPrivateIdentity(args[0].String()) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + return utils.CopyBytesToJS(i) +} + +// SetNickname sets the nickname for a given channel. The nickname must be valid +// according to [IsNicknameValid]. +// +// Parameters: +// - args[0] - The nickname to set (string). +// - args[1] - Marshalled bytes if the channel's [id.ID] (Uint8Array). +// +// Returns: +// - Throws TypeError if unmarshalling the ID fails or the nickname is +// invalid. +func (ch *DMClient) SetNickname(_ js.Value, args []js.Value) any { + ch.api.SetNickname(args[0].String()) + return nil +} + +// GetNickname returns the nickname set for a given channel. Returns an error if +// there is no nickname set. +// +// Parameters: +// - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array). +// +// Returns: +// - The nickname (string). +// - Throws TypeError if the channel has no nickname set. +func (ch *DMClient) GetNickname(_ js.Value, args []js.Value) any { + nickname, err := ch.api.GetNickname(utils.CopyBytesToGo(args[0])) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + return nickname +} + +//////////////////////////////////////////////////////////////////////////////// +// Channel Receiving Logic and Callback Registration // +//////////////////////////////////////////////////////////////////////////////// + +// channelMessageReceptionCallback wraps Javascript callbacks to adhere to the +// [bindings.ChannelMessageReceptionCallback] interface. +type dmReceptionCallback struct { + callback func(args ...any) js.Value +} + +// Callback returns the context for a channel message. +// +// Parameters: +// - receivedChannelMessageReport - Returns the JSON of +// [bindings.ReceivedChannelMessageReport] (Uint8Array). +// - err - Returns an error on failure (Error). +// +// Returns: +// - It must return a unique UUID for the message that it can be referenced by +// later (int). +func (cmrCB *dmReceptionCallback) Callback( + receivedChannelMessageReport []byte, err error) int { + uuid := cmrCB.callback( + utils.CopyBytesToJS(receivedChannelMessageReport), + utils.JsTrace(err)) + + return uuid.Int() +} + +//////////////////////////////////////////////////////////////////////////////// +// Event Model Logic // +//////////////////////////////////////////////////////////////////////////////// + +// dmReceiver wraps Javascript callbacks to adhere to the [bindings.EventModel] +// interface. +type dmReceiver struct { + receive func(args ...any) js.Value + receiveText func(args ...any) js.Value + receiveReply func(args ...any) js.Value + receiveReaction func(args ...any) js.Value + updateSentStatus func(args ...any) js.Value +} + +// ReceiveMessage is called whenever a message is received on a given channel. +// It may be called multiple times on the same message. It is incumbent on the +// user of the API to filter such called by message ID. +// +// Parameters: +// - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array). +// - messageID - The bytes of the [channel.MessageID] of the received message +// (Uint8Array). +// - nickname - The nickname of the sender of the message (string). +// - text - The content of the message (string). +// - pubKey - The sender's Ed25519 public key (Uint8Array). +// - dmToken - The dmToken (int32). +// - codeset - The codeset version (int). +// - timestamp - Time the message was received; represented as nanoseconds +// since unix epoch (int). +// - lease - The number of nanoseconds that the message is valid for (int). +// - roundId - The ID of the round that the message was received on (int). +// - msgType - The type of message ([channels.MessageType]) to send (int). +// - status - The [channels.SentStatus] of the message (int). +// +// Statuses will be enumerated as such: +// +// Sent = 0 +// Delivered = 1 +// Failed = 2 +// +// Returns: +// - A non-negative unique UUID for the message that it can be referenced by +// later with [dmReceiver.UpdateSentStatus]. +func (em *dmReceiver) Receive(messageID []byte, nickname string, + text []byte, pubKey []byte, dmToken int32, codeset int, timestamp, + roundId, mType, status int64) int64 { + uuid := em.receive(messageID, nickname, text, pubKey, dmToken, + codeset, timestamp, roundId, mType, status) + + return int64(uuid.Int()) +} + +// ReceiveText is called whenever a message is received that is a reply on a +// given channel. It may be called multiple times on the same message. It is +// incumbent on the user of the API to filter such called by message ID. +// +// Messages may arrive our of order, so a reply in theory can arrive before the +// initial message. As a result, it may be important to buffer replies. +// +// Parameters: +// - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array). +// - messageID - The bytes of the [channel.MessageID] of the received message +// (Uint8Array). +// - reactionTo - The [channel.MessageID] for the message that received a +// reply (Uint8Array). +// - senderUsername - The username of the sender of the message (string). +// - text - The content of the message (string). +// - pubKey - The sender's Ed25519 public key (Uint8Array). +// - dmToken - The dmToken (int32). +// - codeset - The codeset version (int). +// - timestamp - Time the message was received; represented as nanoseconds +// since unix epoch (int). +// - lease - The number of nanoseconds that the message is valid for (int). +// - roundId - The ID of the round that the message was received on (int). +// - msgType - The type of message ([channels.MessageType]) to send (int). +// - status - The [channels.SentStatus] of the message (int). +// +// Statuses will be enumerated as such: +// +// Sent = 0 +// Delivered = 1 +// Failed = 2 +// +// Returns: +// - A non-negative unique UUID for the message that it can be referenced by +// later with [dmReceiver.UpdateSentStatus]. +func (em *dmReceiver) ReceiveText(messageID []byte, nickname, text string, + pubKey []byte, dmToken int32, codeset int, timestamp, + roundId, status int64) int64 { + + uuid := em.receiveText(messageID, nickname, text, pubKey, dmToken, + codeset, timestamp, roundId, status) + + return int64(uuid.Int()) +} + +// ReceiveReply is called whenever a message is received that is a reply on a +// given channel. It may be called multiple times on the same message. It is +// incumbent on the user of the API to filter such called by message ID. +// +// Messages may arrive our of order, so a reply in theory can arrive before the +// initial message. As a result, it may be important to buffer replies. +// +// Parameters: +// - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array). +// - messageID - The bytes of the [channel.MessageID] of the received message +// (Uint8Array). +// - reactionTo - The [channel.MessageID] for the message that received a +// reply (Uint8Array). +// - senderUsername - The username of the sender of the message (string). +// - text - The content of the message (string). +// - pubKey - The sender's Ed25519 public key (Uint8Array). +// - dmToken - The dmToken (int32). +// - codeset - The codeset version (int). +// - timestamp - Time the message was received; represented as nanoseconds +// since unix epoch (int). +// - lease - The number of nanoseconds that the message is valid for (int). +// - roundId - The ID of the round that the message was received on (int). +// - msgType - The type of message ([channels.MessageType]) to send (int). +// - status - The [channels.SentStatus] of the message (int). +// +// Statuses will be enumerated as such: +// +// Sent = 0 +// Delivered = 1 +// Failed = 2 +// +// Returns: +// - A non-negative unique UUID for the message that it can be referenced by +// later with [dmReceiver.UpdateSentStatus]. +func (em *dmReceiver) ReceiveReply(messageID, replyTo []byte, nickname, + text string, pubKey []byte, dmToken int32, codeset int, + timestamp, roundId, status int64) int64 { + uuid := em.receiveReply(messageID, replyTo, nickname, text, pubKey, + dmToken, codeset, timestamp, roundId, status) + + return int64(uuid.Int()) +} + +// ReceiveReaction is called whenever a reaction to a message is received on a +// given channel. It may be called multiple times on the same reaction. It is +// incumbent on the user of the API to filter such called by message ID. +// +// Messages may arrive our of order, so a reply in theory can arrive before the +// initial message. As a result, it may be important to buffer reactions. +// +// Parameters: +// - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array). +// - messageID - The bytes of the [channel.MessageID] of the received message +// (Uint8Array). +// - reactionTo - The [channel.MessageID] for the message that received a +// reply (Uint8Array). +// - senderUsername - The username of the sender of the message (string). +// - reaction - The contents of the reaction message (string). +// - pubKey - The sender's Ed25519 public key (Uint8Array). +// - dmToken - The dmToken (int32). +// - codeset - The codeset version (int). +// - timestamp - Time the message was received; represented as nanoseconds +// since unix epoch (int). +// - lease - The number of nanoseconds that the message is valid for (int). +// - roundId - The ID of the round that the message was received on (int). +// - msgType - The type of message ([channels.MessageType]) to send (int). +// - status - The [channels.SentStatus] of the message (int). +// +// Statuses will be enumerated as such: +// +// Sent = 0 +// Delivered = 1 +// Failed = 2 +// +// Returns: +// - A non-negative unique UUID for the message that it can be referenced by +// later with [dmReceiver.UpdateSentStatus]. +func (em *dmReceiver) ReceiveReaction(messageID, reactionTo []byte, + nickname, reaction string, pubKey []byte, dmToken int32, + codeset int, timestamp, roundId, + status int64) int64 { + uuid := em.receiveReaction(messageID, reactionTo, nickname, reaction, + pubKey, dmToken, codeset, timestamp, roundId, status) + + return int64(uuid.Int()) +} + +// UpdateSentStatus is called whenever the sent status of a message has +// changed. +// +// Parameters: +// - uuid - The unique identifier for the message (int). +// - messageID - The bytes of the [channel.MessageID] of the received message +// (Uint8Array). +// - timestamp - Time the message was received; represented as nanoseconds +// since unix epoch (int). +// - roundId - The ID of the round that the message was received on (int). +// - status - The [channels.SentStatus] of the message (int). +// +// Statuses will be enumerated as such: +// +// Sent = 0 +// Delivered = 1 +// Failed = 2 +func (em *dmReceiver) UpdateSentStatus(uuid int64, messageID []byte, + timestamp, roundID, status int64) { + em.updateSentStatus(uuid, utils.CopyBytesToJS(messageID), + timestamp, roundID, status) +} + +//////////////////////////////////////////////////////////////////////////////// +// DM DB Cipher // +//////////////////////////////////////////////////////////////////////////////// + +// DMDbCipher wraps the [bindings.DMDbCipher] object so its methods +// can be wrapped to be Javascript compatible. +type DMDbCipher struct { + api *bindings.DMDbCipher +} + +// newDMDbCipherJS creates a new Javascript compatible object +// (map[string]any) that matches the [DMDbCipher] structure. +func newDMDbCipherJS(api *bindings.DMDbCipher) map[string]any { + c := DMDbCipher{api} + channelDbCipherMap := map[string]any{ + "GetID": js.FuncOf(c.GetID), + "Encrypt": js.FuncOf(c.Encrypt), + "Decrypt": js.FuncOf(c.Decrypt), + "MarshalJSON": js.FuncOf(c.MarshalJSON), + "UnmarshalJSON": js.FuncOf(c.UnmarshalJSON), + } + + return channelDbCipherMap +} + +// NewDMsDatabaseCipher constructs a [DMDbCipher] object. +// +// Parameters: +// - args[0] - The tracked [Cmix] object ID (int). +// - args[1] - The password for storage. This should be the same password +// passed into [NewCmix] (Uint8Array). +// - args[2] - The maximum size of a payload to be encrypted. A payload passed +// into [DMDbCipher.Encrypt] that is larger than this value will result +// in an error (int). +// +// Returns: +// - JavaScript representation of the [DMDbCipher] object. +// - Throws a TypeError if creating the cipher fails. +func NewDMsDatabaseCipher(_ js.Value, args []js.Value) any { + cmixId := args[0].Int() + password := utils.CopyBytesToGo(args[1]) + plaintTextBlockSize := args[2].Int() + + cipher, err := bindings.NewDMsDatabaseCipher( + cmixId, password, plaintTextBlockSize) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + return newDMDbCipherJS(cipher) +} + +// GetID returns the ID for this [bindings.DMDbCipher] in the +// channelDbCipherTracker. +// +// Returns: +// - Tracker ID (int). +func (c *DMDbCipher) GetID(js.Value, []js.Value) any { + return c.api.GetID() +} + +// Encrypt will encrypt the raw data. It will return a ciphertext. Padding is +// done on the plaintext so all encrypted data looks uniform at rest. +// +// Parameters: +// - args[0] - The data to be encrypted (Uint8Array). This must be smaller +// than the block size passed into [NewDMsDatabaseCipher]. If it is +// larger, this will return an error. +// +// Returns: +// - The ciphertext of the plaintext passed in (Uint8Array). +// - Throws a TypeError if it fails to encrypt the plaintext. +func (c *DMDbCipher) Encrypt(_ js.Value, args []js.Value) any { + ciphertext, err := c.api.Encrypt(utils.CopyBytesToGo(args[0])) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + return utils.CopyBytesToJS(ciphertext) +} + +// Decrypt will decrypt the passed in encrypted value. The plaintext will be +// returned by this function. Any padding will be discarded within this +// function. +// +// Parameters: +// - args[0] - the encrypted data returned by [DMDbCipher.Encrypt] +// (Uint8Array). +// +// Returns: +// - The plaintext of the ciphertext passed in (Uint8Array). +// - Throws a TypeError if it fails to encrypt the plaintext. +func (c *DMDbCipher) Decrypt(_ js.Value, args []js.Value) any { + plaintext, err := c.api.Decrypt(utils.CopyBytesToGo(args[0])) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + return utils.CopyBytesToJS(plaintext) +} + +// MarshalJSON marshals the cipher into valid JSON. +// +// Returns: +// - JSON of the cipher (Uint8Array). +// - Throws a TypeError if marshalling fails. +func (c *DMDbCipher) MarshalJSON(js.Value, []js.Value) any { + data, err := c.api.MarshalJSON() + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + return utils.CopyBytesToJS(data) +} + +// UnmarshalJSON unmarshalls JSON into the cipher. This function adheres to the +// json.Unmarshaler interface. +// +// Note that this function does not transfer the internal RNG. Use +// [channel.NewCipherFromJSON] to properly reconstruct a cipher from JSON. +// +// Parameters: +// - args[0] - JSON data to unmarshal (Uint8Array). +// +// Returns: +// - JSON of the cipher (Uint8Array). +// - Throws a TypeError if marshalling fails. +func (c *DMDbCipher) UnmarshalJSON(_ js.Value, args []js.Value) any { + err := c.api.UnmarshalJSON(utils.CopyBytesToGo(args[0])) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + return nil +} diff --git a/wasm/docs.go b/wasm/docs.go index 33b0d56ed2951fa2e98c6217f09e77ebe4024fb4..2f783b6374afb19e8e7aede6e732274c7e17b854 100644 --- a/wasm/docs.go +++ b/wasm/docs.go @@ -23,11 +23,11 @@ import ( "gitlab.com/elixxir/client/v4/restlike" "gitlab.com/elixxir/client/v4/single" "gitlab.com/elixxir/crypto/broadcast" - "gitlab.com/elixxir/crypto/channel" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/fileTransfer" "gitlab.com/elixxir/crypto/group" + cryptoMessage "gitlab.com/elixxir/crypto/message" "gitlab.com/elixxir/primitives/fact" "gitlab.com/elixxir/primitives/format" "gitlab.com/xx_network/primitives/id" @@ -50,7 +50,7 @@ var ( _ = connect.Callback(nil) _ = partner.Manager(nil) _ = ndf.NetworkDefinition{} - _ = channel.MessageID{} + _ = cryptoMessage.ID{} _ = channels.SentStatus(0) _ = ftE2e.Params{} _ = fileTransfer.TransferID{} diff --git a/wasm_test.go b/wasm_test.go index b7afa43099adac67ae4d2136676fe37477a05db5..68cc545ba79c45b67cf1eefdb596e28c69b5eba3 100644 --- a/wasm_test.go +++ b/wasm_test.go @@ -49,10 +49,11 @@ func TestPublicFunctions(t *testing.T) { "GetGitVersion": {}, "GetDependencies": {}, - // DM Functions -- temporarily not implemented + // DM Functions these are used but not exported by + // WASM bindins, so are not exposed. "NewDMReceiver": {}, - "NewDMClient": {}, "NewDMClientWithGoEventModel": {}, + "GetDMDbCipherTrackerFromID": {}, } wasmFuncs := getPublicFunctions("wasm", t) bindingsFuncs := getPublicFunctions(