2023-04-27 09:03:40 +08:00
|
|
|
package reader
|
|
|
|
|
|
|
|
import (
|
2024-01-24 02:28:32 +08:00
|
|
|
"context"
|
2024-02-02 23:36:27 +08:00
|
|
|
"encoding/json"
|
2024-01-24 02:28:32 +08:00
|
|
|
"errors"
|
2024-04-06 22:43:36 +08:00
|
|
|
"fmt"
|
2023-04-27 09:03:40 +08:00
|
|
|
"github.com/iMDT/bbb-graphql-middleware/internal/common"
|
2024-05-02 21:45:32 +08:00
|
|
|
"github.com/iMDT/bbb-graphql-middleware/internal/hasura/retransmiter"
|
2023-05-10 02:37:58 +08:00
|
|
|
"github.com/iMDT/bbb-graphql-middleware/internal/msgpatch"
|
2023-04-27 09:03:40 +08:00
|
|
|
log "github.com/sirupsen/logrus"
|
2024-02-03 01:37:32 +08:00
|
|
|
"hash/crc32"
|
2023-04-27 09:03:40 +08:00
|
|
|
"nhooyr.io/websocket/wsjson"
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
// HasuraConnectionReader consumes messages from Hasura connection and add send to the browser channel
|
2023-12-15 01:01:47 +08:00
|
|
|
func HasuraConnectionReader(hc *common.HasuraConnection, fromHasuraToBrowserChannel *common.SafeChannel, fromBrowserToHasuraChannel *common.SafeChannel, wg *sync.WaitGroup) {
|
2024-03-13 21:35:51 +08:00
|
|
|
log := log.WithField("_routine", "HasuraConnectionReader").WithField("browserConnectionId", hc.BrowserConn.Id).WithField("hasuraConnectionId", hc.Id)
|
2023-09-30 07:05:23 +08:00
|
|
|
defer log.Debugf("finished")
|
|
|
|
log.Debugf("starting")
|
2023-04-27 09:03:40 +08:00
|
|
|
|
|
|
|
defer wg.Done()
|
|
|
|
defer hc.ContextCancelFunc()
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Read a message from hasura
|
|
|
|
var message interface{}
|
|
|
|
err := wsjson.Read(hc.Context, hc.Websocket, &message)
|
|
|
|
if err != nil {
|
2024-01-24 02:28:32 +08:00
|
|
|
if errors.Is(err, context.Canceled) {
|
2024-03-13 21:35:51 +08:00
|
|
|
log.Debugf("Closing Hasura ws connection as Context was cancelled!")
|
2024-01-24 02:28:32 +08:00
|
|
|
} else {
|
2024-03-13 21:35:51 +08:00
|
|
|
log.Debugf("Error reading message from Hasura: %v", err)
|
2024-01-24 02:28:32 +08:00
|
|
|
}
|
2023-04-27 09:03:40 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Tracef("received from hasura: %v", message)
|
|
|
|
|
2024-02-03 02:09:56 +08:00
|
|
|
handleMessageReceivedFromHasura(hc, fromHasuraToBrowserChannel, fromBrowserToHasuraChannel, message)
|
2024-02-02 23:36:27 +08:00
|
|
|
}
|
|
|
|
}
|
2023-07-06 20:34:48 +08:00
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
func handleMessageReceivedFromHasura(hc *common.HasuraConnection, fromHasuraToBrowserChannel *common.SafeChannel, fromBrowserToHasuraChannel *common.SafeChannel, message interface{}) {
|
|
|
|
var messageMap = message.(map[string]interface{})
|
|
|
|
|
|
|
|
if messageMap != nil {
|
|
|
|
var messageType = messageMap["type"]
|
|
|
|
var queryId, _ = messageMap["id"].(string)
|
|
|
|
|
|
|
|
//Check if subscription is still active!
|
|
|
|
if queryId != "" {
|
2024-03-13 21:35:51 +08:00
|
|
|
hc.BrowserConn.ActiveSubscriptionsMutex.RLock()
|
|
|
|
subscription, ok := hc.BrowserConn.ActiveSubscriptions[queryId]
|
|
|
|
hc.BrowserConn.ActiveSubscriptionsMutex.RUnlock()
|
2024-02-02 23:36:27 +08:00
|
|
|
if !ok {
|
2024-03-18 21:58:53 +08:00
|
|
|
log.Debugf("Subscription with Id %s doesn't exist anymore, skipping response.", queryId)
|
2024-02-02 23:36:27 +08:00
|
|
|
return
|
|
|
|
}
|
2023-04-27 09:03:40 +08:00
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
//When Hasura send msg type "complete", this query is finished
|
|
|
|
if messageType == "complete" {
|
|
|
|
handleCompleteMessage(hc, queryId)
|
2024-03-29 04:17:13 +08:00
|
|
|
common.ActivitiesOverviewCompleted(string(subscription.Type) + "-" + subscription.OperationName)
|
|
|
|
common.ActivitiesOverviewCompleted("_Sum-" + string(subscription.Type))
|
2024-02-02 23:36:27 +08:00
|
|
|
}
|
2023-05-10 02:37:58 +08:00
|
|
|
|
2024-04-01 08:00:28 +08:00
|
|
|
if messageType == "data" {
|
|
|
|
common.ActivitiesOverviewDataReceived(string(subscription.Type) + "-" + subscription.OperationName)
|
|
|
|
}
|
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
if messageType == "data" &&
|
|
|
|
subscription.Type == common.Subscription {
|
|
|
|
hasNoPreviousOccurrence := handleSubscriptionMessage(hc, messageMap, subscription, queryId)
|
2023-04-27 09:03:40 +08:00
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
if !hasNoPreviousOccurrence {
|
|
|
|
return
|
2023-05-10 02:37:58 +08:00
|
|
|
}
|
2024-02-02 23:36:27 +08:00
|
|
|
}
|
2024-01-24 02:28:32 +08:00
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
//Set last cursor value for stream
|
|
|
|
if subscription.Type == common.Streaming {
|
|
|
|
handleStreamingMessage(hc, messageMap, subscription, queryId)
|
|
|
|
}
|
|
|
|
}
|
2024-01-24 02:28:32 +08:00
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
// Retransmit the subscription start commands when hasura confirms the connection
|
|
|
|
// this is useful in case of a connection invalidation
|
|
|
|
if messageType == "connection_ack" {
|
|
|
|
handleConnectionAckMessage(hc, messageMap, fromHasuraToBrowserChannel, fromBrowserToHasuraChannel)
|
|
|
|
} else {
|
|
|
|
// Forward the message to browser
|
|
|
|
fromHasuraToBrowserChannel.Send(messageMap)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleSubscriptionMessage(hc *common.HasuraConnection, messageMap map[string]interface{}, subscription common.GraphQlSubscription, queryId string) bool {
|
|
|
|
if payload, okPayload := messageMap["payload"].(map[string]interface{}); okPayload {
|
|
|
|
if data, okData := payload["data"].(map[string]interface{}); okData {
|
|
|
|
for dataKey, dataItem := range data {
|
|
|
|
if currentDataProp, okCurrentDataProp := dataItem.([]interface{}); okCurrentDataProp {
|
|
|
|
if dataAsJson, err := json.Marshal(currentDataProp); err == nil {
|
2024-04-02 21:07:44 +08:00
|
|
|
if common.ActivitiesOverviewEnabled {
|
|
|
|
dataSize := len(string(dataAsJson))
|
|
|
|
dataCount := len(currentDataProp)
|
|
|
|
common.ActivitiesOverviewDataSize(string(subscription.Type)+"-"+subscription.OperationName, int64(dataSize), int64(dataCount))
|
|
|
|
}
|
2024-04-02 21:04:38 +08:00
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
//Check whether ReceivedData is different from the LastReceivedData
|
|
|
|
//Otherwise stop forwarding this message
|
2024-02-03 01:37:32 +08:00
|
|
|
dataChecksum := crc32.ChecksumIEEE(dataAsJson)
|
|
|
|
if subscription.LastReceivedDataChecksum == dataChecksum {
|
2024-02-02 23:36:27 +08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-04-06 22:43:36 +08:00
|
|
|
lastDataChecksumWas := subscription.LastReceivedDataChecksum
|
|
|
|
cacheKey := fmt.Sprintf("%s-%s-%v-%v", string(subscription.Type), subscription.OperationName, subscription.LastReceivedDataChecksum, dataChecksum)
|
|
|
|
|
2024-02-03 01:37:32 +08:00
|
|
|
//Store LastReceivedData Checksum
|
|
|
|
subscription.LastReceivedDataChecksum = dataChecksum
|
2024-03-13 21:35:51 +08:00
|
|
|
hc.BrowserConn.ActiveSubscriptionsMutex.Lock()
|
|
|
|
hc.BrowserConn.ActiveSubscriptions[queryId] = subscription
|
|
|
|
hc.BrowserConn.ActiveSubscriptionsMutex.Unlock()
|
2024-01-24 02:28:32 +08:00
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
//Apply msg patch when it supports it
|
|
|
|
if subscription.JsonPatchSupported {
|
2024-04-06 22:43:36 +08:00
|
|
|
msgpatch.PatchMessage(&messageMap, queryId, dataKey, dataAsJson, hc.BrowserConn, cacheKey, lastDataChecksumWas, dataChecksum)
|
2024-02-02 23:36:27 +08:00
|
|
|
}
|
|
|
|
}
|
2024-01-24 02:28:32 +08:00
|
|
|
}
|
2023-05-10 02:37:58 +08:00
|
|
|
}
|
2024-02-02 23:36:27 +08:00
|
|
|
}
|
|
|
|
}
|
2023-05-10 02:37:58 +08:00
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
return true
|
|
|
|
}
|
2024-01-24 07:20:16 +08:00
|
|
|
|
2024-02-02 23:36:27 +08:00
|
|
|
func handleStreamingMessage(hc *common.HasuraConnection, messageMap map[string]interface{}, subscription common.GraphQlSubscription, queryId string) {
|
|
|
|
lastCursor := common.GetLastStreamCursorValueFromReceivedMessage(messageMap, subscription.StreamCursorField)
|
|
|
|
if lastCursor != nil && subscription.StreamCursorCurrValue != lastCursor {
|
|
|
|
subscription.StreamCursorCurrValue = lastCursor
|
|
|
|
|
2024-03-13 21:35:51 +08:00
|
|
|
hc.BrowserConn.ActiveSubscriptionsMutex.Lock()
|
|
|
|
hc.BrowserConn.ActiveSubscriptions[queryId] = subscription
|
|
|
|
hc.BrowserConn.ActiveSubscriptionsMutex.Unlock()
|
2023-04-27 09:03:40 +08:00
|
|
|
}
|
|
|
|
}
|
2024-02-02 23:36:27 +08:00
|
|
|
|
|
|
|
func handleCompleteMessage(hc *common.HasuraConnection, queryId string) {
|
2024-03-13 21:35:51 +08:00
|
|
|
hc.BrowserConn.ActiveSubscriptionsMutex.Lock()
|
|
|
|
queryType := hc.BrowserConn.ActiveSubscriptions[queryId].Type
|
|
|
|
operationName := hc.BrowserConn.ActiveSubscriptions[queryId].OperationName
|
|
|
|
delete(hc.BrowserConn.ActiveSubscriptions, queryId)
|
|
|
|
hc.BrowserConn.ActiveSubscriptionsMutex.Unlock()
|
2024-03-13 07:12:55 +08:00
|
|
|
log.Debugf("%s (%s) with Id %s finished by Hasura.", queryType, operationName, queryId)
|
2024-02-02 23:36:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func handleConnectionAckMessage(hc *common.HasuraConnection, messageMap map[string]interface{}, fromHasuraToBrowserChannel *common.SafeChannel, fromBrowserToHasuraChannel *common.SafeChannel) {
|
|
|
|
log.Debugf("Received connection_ack")
|
|
|
|
//Hasura connection was initialized, now it's able to send new messages to Hasura
|
|
|
|
fromBrowserToHasuraChannel.UnfreezeChannel()
|
|
|
|
|
|
|
|
//Avoid to send `connection_ack` to the browser when it's a reconnection
|
2024-03-13 21:35:51 +08:00
|
|
|
if hc.BrowserConn.ConnAckSentToBrowser == false {
|
2024-02-02 23:36:27 +08:00
|
|
|
fromHasuraToBrowserChannel.Send(messageMap)
|
2024-03-13 21:35:51 +08:00
|
|
|
hc.BrowserConn.ConnAckSentToBrowser = true
|
2024-02-02 23:36:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
go retransmiter.RetransmitSubscriptionStartMessages(hc, fromBrowserToHasuraChannel)
|
|
|
|
}
|