340 lines
12 KiB
Lua
340 lines
12 KiB
Lua
local CoreGui = game:GetService("CoreGui")
|
|
local HttpService = game:GetService("HttpService")
|
|
local NotificationService = game:GetService("NotificationService")
|
|
local GuiService = game:GetService("GuiService")
|
|
local UserInputService = game:GetService("UserInputService")
|
|
local Players = game:GetService("Players")
|
|
|
|
local Modules = CoreGui.RobloxGui.Modules
|
|
local LuaChat = Modules.LuaChat
|
|
local LuaApp = Modules.LuaApp
|
|
|
|
local Constants = require(LuaChat.Constants)
|
|
local WebApi = require(LuaChat.WebApi)
|
|
local ConversationActions = require(LuaChat.Actions.ConversationActions)
|
|
local FetchChatEnabled = require(LuaChat.Actions.FetchChatEnabled)
|
|
local ReceivedUserTyping = require(LuaChat.Actions.ReceivedUserTyping)
|
|
local PlayTogetherActions = require(LuaChat.Actions.PlayTogetherActions)
|
|
local DialogInfo = require(LuaChat.DialogInfo)
|
|
local Config = require(LuaApp.Config)
|
|
local ToastModel = require(LuaChat.Models.ToastModel)
|
|
local NotificationType = require(LuaApp.Enum.NotificationType)
|
|
|
|
local ChangedParticipants = require(LuaChat.Actions.ChangedParticipants)
|
|
local PopRoute = require(LuaChat.Actions.PopRoute)
|
|
local RemovedConversation = require(LuaChat.Actions.RemovedConversation)
|
|
local RenamedGroupConversation = require(LuaChat.Actions.RenamedGroupConversation)
|
|
local SetChatEnabled = require(LuaChat.Actions.SetChatEnabled)
|
|
local SetConnectionState = require(LuaChat.Actions.SetConnectionState)
|
|
local SetRoute = require(LuaChat.Actions.SetRoute)
|
|
local ShowToast = require(LuaChat.Actions.ShowToast)
|
|
local SetPreloading = require(LuaApp.Actions.SetPreloading)
|
|
|
|
local FlagSettings = require(LuaApp.FlagSettings)
|
|
|
|
local isLuaAppFriendshipCreatedSignalREnabled = FlagSettings.IsLuaAppFriendshipCreatedSignalREnabled()
|
|
local luaChatDisconnectBackButtonWhenOffScreen = settings():GetFFlag("LuaChatDisconnectBackButtonWhenOffScreen")
|
|
|
|
local Intent = DialogInfo.Intent
|
|
|
|
local function jsonDecode(data)
|
|
return HttpService:JSONDecode(data)
|
|
end
|
|
|
|
local function getNewestWithNilPreviousMessageId(messages)
|
|
for id, message, _ in messages:CreateReverseIterator() do
|
|
if message.previousMessageId == nil then
|
|
return id
|
|
end
|
|
end
|
|
return messages.keys[1]
|
|
end
|
|
|
|
local RobloxEventReceiver = {}
|
|
function RobloxEventReceiver:init(store)
|
|
local function onChatNotifications(eventData)
|
|
local detail = jsonDecode(eventData.detail)
|
|
local detailType = detail.Type or eventData.detailType
|
|
if detailType == "RemovedFromConversation" then
|
|
local conversationId = tostring(detail.ConversationId)
|
|
store:dispatch(RemovedConversation(conversationId))
|
|
|
|
local chatReducer = store:getState().ChatAppReducer
|
|
if chatReducer and chatReducer.Location then
|
|
local currentLocation = chatReducer.Location.current
|
|
if currentLocation and currentLocation.parameters then
|
|
if currentLocation.parameters.conversationId == conversationId then
|
|
local messageKey = "Feature.Chat.Message.RemovedFromConversation"
|
|
local toastModel = ToastModel.new(Constants.ToastIDs.REMOVED_FROM_CONVERSATION, messageKey, {})
|
|
store:dispatch(ShowToast(toastModel))
|
|
end
|
|
end
|
|
end
|
|
elseif detailType == "ConversationRemoved" then
|
|
local conversationId = tostring(detail.ConversationId)
|
|
store:dispatch(RemovedConversation(conversationId))
|
|
elseif detailType == "ConversationTitleChanged" then
|
|
local conversationId = tostring(detail.ConversationId)
|
|
spawn(function()
|
|
local status, result = WebApi.GetConversations({conversationId})
|
|
|
|
if status ~= WebApi.Status.OK then
|
|
warn("WebApi failure in RobloxEventReceiver->ConversationTitleChanged")
|
|
return
|
|
end
|
|
|
|
local conversations = result.conversations
|
|
|
|
if #conversations > 0 then
|
|
local conversation = conversations[1]
|
|
local title = conversation.title
|
|
local isDefaultTitle = conversation.isDefaultTitle
|
|
store:dispatch(
|
|
RenamedGroupConversation(conversationId, title, isDefaultTitle, conversation.lastUpdated)
|
|
)
|
|
end
|
|
end)
|
|
elseif detailType == "ParticipantAdded" then
|
|
local convoId = tostring(detail.ConversationId)
|
|
spawn(function()
|
|
local status, result = WebApi.GetConversations({convoId})
|
|
|
|
if status ~= WebApi.Status.OK then
|
|
warn("WebApi failure in RobloxEventReceiver->ParticipantAdded")
|
|
return
|
|
end
|
|
|
|
local conversations = result.conversations
|
|
|
|
if #conversations > 0 then
|
|
local conversation = conversations[1]
|
|
local participants = conversation.participants
|
|
local title = conversation.title
|
|
store:dispatch(ChangedParticipants(convoId, participants, title, conversation.lastUpdated))
|
|
end
|
|
end)
|
|
elseif detailType == "ParticipantLeft" then
|
|
local convoId = tostring(detail.ConversationId)
|
|
spawn(function()
|
|
local status, result = WebApi.GetConversations({convoId})
|
|
|
|
if status ~= WebApi.Status.OK then
|
|
warn("WebApi failure in RobloxEventReceiver->ParticipantLeft", status)
|
|
return
|
|
end
|
|
|
|
local conversations = result.conversations
|
|
|
|
if #conversations > 0 then
|
|
local conversation = conversations[1]
|
|
local participants = conversation.participants
|
|
local title = conversation.title
|
|
store:dispatch(ChangedParticipants(convoId, participants, title, conversation.lastUpdated))
|
|
end
|
|
end)
|
|
elseif detailType == "AddedToConversation" then
|
|
local conversationId = tostring(detail.ConversationId)
|
|
spawn(function()
|
|
local status = store:dispatch(ConversationActions.GetConversations(conversationId))
|
|
|
|
if status ~= WebApi.Status.OK then
|
|
warn("WebApi failure in RobloxEventReceiver->AddedToConversation")
|
|
return
|
|
end
|
|
end)
|
|
elseif detailType == "NewConversation" then
|
|
local conversationId = tostring(detail.ConversationId)
|
|
spawn(function()
|
|
local status = store:dispatch(ConversationActions.GetConversations(conversationId))
|
|
|
|
if status ~= WebApi.Status.OK then
|
|
warn("WebApi failure in RobloxEventReceiver->NewConversation")
|
|
return
|
|
end
|
|
end)
|
|
elseif detailType == "NewMessage" or detailType == "NewMessageBySelf" then
|
|
local newMessageNotificationReceivedLocalTime = tick()
|
|
local conversationId = tostring(detail.ConversationId)
|
|
store:dispatch(
|
|
ConversationActions.GetNewMessages(
|
|
conversationId,
|
|
detailType == "NewMessageBySelf",
|
|
newMessageNotificationReceivedLocalTime
|
|
)
|
|
)
|
|
elseif detailType == "ParticipantTyping" then
|
|
local conversationId = tostring(detail.ConversationId)
|
|
local userId = tostring(detail.UserId)
|
|
store:dispatch(ReceivedUserTyping(conversationId, userId))
|
|
elseif detailType == "ConversationUniverseChanged" then
|
|
local conversationId = tostring(detail.ConversationId)
|
|
local universeId = detail.UniverseId and tostring(detail.UniverseId)
|
|
local rootPlaceId = detail.RootPlaceId and tostring(detail.RootPlaceId)
|
|
store:dispatch(PlayTogetherActions.SetPinnedGameForConversation(universeId, rootPlaceId, conversationId))
|
|
end
|
|
end
|
|
|
|
local function onPresenceNotifications(eventData)
|
|
local detail = jsonDecode(eventData.detail)
|
|
local userId = tostring(detail.UserId)
|
|
store:dispatch(ConversationActions.GetUserPresences({userId}))
|
|
end
|
|
|
|
local function onPresenceBulkNotifications(eventData)
|
|
local detail = jsonDecode(eventData.detail)
|
|
local userIds = {}
|
|
for _, update in ipairs(detail) do
|
|
table.insert(userIds, tostring(update.UserId))
|
|
end
|
|
store:dispatch(ConversationActions.GetUserPresences(userIds))
|
|
end
|
|
|
|
local function onAppShellNotifications(eventData)
|
|
-- Note: AppShellNotifications are local and don't come from the
|
|
-- network. eventData.detail is not a structure, unlike other messages.
|
|
local detailType = eventData.detailType
|
|
if detailType == "StartConversationWithUserId" then
|
|
local userId = eventData.detail
|
|
spawn(function()
|
|
local status, result = WebApi.StartOneToOneConversation(userId)
|
|
|
|
if status ~= WebApi.Status.OK then
|
|
warn("WebApi failure in RobloxEventReceiver->AppShellNotifications, Status: "..tostring(status) )
|
|
return
|
|
end
|
|
|
|
if store:getState().ChatAppReducer.Conversations[result.id] == nil then
|
|
--Call GetConversations to make sure we hit the user and presence
|
|
--endpoints if need be. Being a bit lazy I suppose
|
|
local status = store:dispatch(
|
|
ConversationActions.GetConversations({result.id})
|
|
)
|
|
|
|
if status ~= WebApi.Status.OK then
|
|
warn("WebApi failure in RobloxEventReceiver->StartConversationWithUserId, Status: "..tostring(status))
|
|
return
|
|
end
|
|
end
|
|
|
|
store:dispatch(SetRoute(Intent.Conversation, {conversationId = result.id}, Intent.ConversationHub))
|
|
end)
|
|
elseif detailType == "StartConversationWithId" then
|
|
local convoId = eventData.detail
|
|
if store:getState().ChatAppReducer.Conversations[convoId] == nil then
|
|
local status = store:dispatch(
|
|
ConversationActions.GetConversations({convoId})
|
|
)
|
|
|
|
if status ~= WebApi.Status.OK then
|
|
warn("WebApi failure in RobloxEventReceiver->StartConversationWithId, Status: "..tostring(status))
|
|
return
|
|
end
|
|
end
|
|
|
|
store:dispatch(SetRoute(Intent.Conversation, {conversationId = convoId}, Intent.ConversationHub))
|
|
elseif detailType == "Preloading" then
|
|
local isPreloading = eventData.detail == "true"
|
|
store:dispatch(SetPreloading(isPreloading))
|
|
end
|
|
end
|
|
|
|
local function onPrivacyNotifications(eventData)
|
|
local detail = jsonDecode(eventData.detail)
|
|
local detailType = detail.Type
|
|
|
|
if detailType == "ChatDisabled" then
|
|
store:dispatch(SetChatEnabled(false))
|
|
elseif detailType == "ChatEnabled" then
|
|
store:dispatch(SetChatEnabled(true))
|
|
end
|
|
end
|
|
|
|
local function onFriendshipNotifications(eventData)
|
|
local detail = jsonDecode(eventData.detail)
|
|
local detailType = detail.Type
|
|
|
|
if detailType == "FriendshipCreated" then
|
|
-- LuaApp's RobloxEventReceiver will create the new user and mock conversation if this flag is on
|
|
if not isLuaAppFriendshipCreatedSignalREnabled then
|
|
local userId = tostring(Players.LocalPlayer.UserId) == tostring(detail.EventArgs.UserId1)
|
|
and detail.EventArgs.UserId2 or detail.EventArgs.UserId1
|
|
store:dispatch(ConversationActions.FriendshipCreated(tostring(userId)))
|
|
end
|
|
end
|
|
end
|
|
|
|
local function onRobloxEventReceived(eventData)
|
|
if eventData.namespace == "ChatNotifications" then
|
|
onChatNotifications(eventData)
|
|
elseif eventData.namespace == "PresenceNotifications" then
|
|
onPresenceNotifications(eventData)
|
|
elseif eventData.namespace == "PresenceBulkNotifications" then
|
|
onPresenceBulkNotifications(eventData)
|
|
elseif eventData.namespace == "ChatPrivacySettingNotifications" then
|
|
onPrivacyNotifications(eventData)
|
|
elseif eventData.namespace == "AppShellNotifications" then
|
|
onAppShellNotifications(eventData)
|
|
elseif eventData.namespace == "FriendshipNotifications" then
|
|
onFriendshipNotifications(eventData)
|
|
end
|
|
end
|
|
|
|
local lastSeqNum = nil
|
|
local function onRobloxConnectionChanged(connectionHubName, connectionState, seqNum)
|
|
if connectionHubName == "signalR" then
|
|
store:dispatch(SetConnectionState(connectionState))
|
|
if connectionState == Enum.ConnectionState.Connected then
|
|
if seqNum ~= lastSeqNum then
|
|
store:dispatch(FetchChatEnabled(function(chatEnabled)
|
|
if chatEnabled then
|
|
store:dispatch(ConversationActions.RefreshConversations())
|
|
spawn(function()
|
|
store:dispatch(ConversationActions.GetAllFriendsAsync())
|
|
end)
|
|
store:dispatch(ConversationActions.GetAllUserPresences())
|
|
end
|
|
end))
|
|
lastSeqNum = seqNum
|
|
end
|
|
local conversations = store:getState().ChatAppReducer.Conversations
|
|
for conversationId, conversation in pairs(conversations) do
|
|
if conversation.fetchingOlderMessages then
|
|
local messages = conversation.messages
|
|
local exclusiveMessageStartId = getNewestWithNilPreviousMessageId(messages)
|
|
store:dispatch(ConversationActions.GetOlderMessages(conversationId, exclusiveMessageStartId))
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
local function onBackButtonPressed()
|
|
if #store:getState().ChatAppReducer.Location.history > 1 then
|
|
store:dispatch(PopRoute())
|
|
else
|
|
GuiService:BroadcastNotification("", NotificationType.BACK_BUTTON_NOT_CONSUMED)
|
|
end
|
|
end
|
|
|
|
--Protect this call because Tests run in a downgraded security context
|
|
pcall(function()
|
|
NotificationService.RobloxEventReceived:Connect(onRobloxEventReceived)
|
|
NotificationService.RobloxConnectionChanged:Connect(onRobloxConnectionChanged)
|
|
if not luaChatDisconnectBackButtonWhenOffScreen then
|
|
GuiService.ShowLeaveConfirmation:Connect(onBackButtonPressed)
|
|
end
|
|
end)
|
|
|
|
if Config.LuaChat.Debug then
|
|
UserInputService.InputEnded:Connect(function(input, gameProcessed)
|
|
if input.UserInputType == Enum.UserInputType.Keyboard then
|
|
if input.KeyCode == Enum.KeyCode.Left then
|
|
onBackButtonPressed()
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
return RobloxEventReceiver |