mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Implement one-shot mode for explicit confirmation in ServeSession
- Introduced one-shot mode to block automatic outgoing writes and require explicit user confirmation for all changes. - Updated confirmation logic in App:startSession to ensure no auto-acceptance occurs during one-shot mode. - Enhanced logging to provide clarity on actions taken during one-shot mode, improving user awareness and control over sync operations.
This commit is contained in:
parent
d002c4e821
commit
78aab0eca4
2 changed files with 80 additions and 68 deletions
|
|
@ -709,87 +709,76 @@ function App:startSession()
|
|||
end)
|
||||
|
||||
serveSession:setConfirmCallback(function(instanceMap, patch, serverInfo)
|
||||
-- Helper to schedule one-shot disconnect after auto-accept
|
||||
local function scheduleOneShotDisconnect()
|
||||
if Settings:get("oneShotSync") then
|
||||
task.delay(0.5, function()
|
||||
if self.serveSession then
|
||||
Log.info("One-shot sync: disconnecting after sync")
|
||||
self:endSession()
|
||||
end
|
||||
end)
|
||||
local isOneShotMode = Settings:get("oneShotSync")
|
||||
|
||||
-- ONE-SHOT MODE: Never auto-accept, always require explicit confirmation
|
||||
-- This ensures nothing can "sneak through" without user review
|
||||
if not isOneShotMode then
|
||||
if PatchSet.isEmpty(patch) then
|
||||
Log.trace("Accepting patch without confirmation because it is empty")
|
||||
return "Accept"
|
||||
end
|
||||
end
|
||||
|
||||
if PatchSet.isEmpty(patch) then
|
||||
Log.trace("Accepting patch without confirmation because it is empty")
|
||||
scheduleOneShotDisconnect()
|
||||
return "Accept"
|
||||
end
|
||||
-- Play solo auto-connect does not require confirmation
|
||||
if self:isAutoConnectPlaytestServerAvailable() then
|
||||
Log.trace("Accepting patch without confirmation because play solo auto-connect is enabled")
|
||||
return "Accept"
|
||||
end
|
||||
|
||||
-- Play solo auto-connect does not require confirmation
|
||||
if self:isAutoConnectPlaytestServerAvailable() then
|
||||
Log.trace("Accepting patch without confirmation because play solo auto-connect is enabled")
|
||||
scheduleOneShotDisconnect()
|
||||
return "Accept"
|
||||
end
|
||||
|
||||
local confirmationBehavior = Settings:get("confirmationBehavior")
|
||||
if confirmationBehavior ~= "Always" then
|
||||
if confirmationBehavior == "Initial" then
|
||||
-- Only confirm if we haven't synced this project yet this session
|
||||
if self.knownProjects[serverInfo.projectName] then
|
||||
Log.trace(
|
||||
"Accepting patch without confirmation because project has already been connected and behavior is set to Initial"
|
||||
)
|
||||
scheduleOneShotDisconnect()
|
||||
return "Accept"
|
||||
end
|
||||
elseif confirmationBehavior == "Large Changes" then
|
||||
-- Only confirm if the patch impacts many instances
|
||||
if PatchSet.countInstances(patch) < Settings:get("largeChangesConfirmationThreshold") then
|
||||
Log.trace(
|
||||
"Accepting patch without confirmation because patch is small and behavior is set to Large Changes"
|
||||
)
|
||||
scheduleOneShotDisconnect()
|
||||
return "Accept"
|
||||
end
|
||||
elseif confirmationBehavior == "Unlisted PlaceId" then
|
||||
-- Only confirm if the current placeId is not in the servePlaceIds allowlist
|
||||
if serverInfo.expectedPlaceIds then
|
||||
local isListed = table.find(serverInfo.expectedPlaceIds, game.PlaceId) ~= nil
|
||||
if isListed then
|
||||
local confirmationBehavior = Settings:get("confirmationBehavior")
|
||||
if confirmationBehavior ~= "Always" then
|
||||
if confirmationBehavior == "Initial" then
|
||||
-- Only confirm if we haven't synced this project yet this session
|
||||
if self.knownProjects[serverInfo.projectName] then
|
||||
Log.trace(
|
||||
"Accepting patch without confirmation because placeId is listed and behavior is set to Unlisted PlaceId"
|
||||
"Accepting patch without confirmation because project has already been connected and behavior is set to Initial"
|
||||
)
|
||||
scheduleOneShotDisconnect()
|
||||
return "Accept"
|
||||
end
|
||||
elseif confirmationBehavior == "Large Changes" then
|
||||
-- Only confirm if the patch impacts many instances
|
||||
if PatchSet.countInstances(patch) < Settings:get("largeChangesConfirmationThreshold") then
|
||||
Log.trace(
|
||||
"Accepting patch without confirmation because patch is small and behavior is set to Large Changes"
|
||||
)
|
||||
return "Accept"
|
||||
end
|
||||
elseif confirmationBehavior == "Unlisted PlaceId" then
|
||||
-- Only confirm if the current placeId is not in the servePlaceIds allowlist
|
||||
if serverInfo.expectedPlaceIds then
|
||||
local isListed = table.find(serverInfo.expectedPlaceIds, game.PlaceId) ~= nil
|
||||
if isListed then
|
||||
Log.trace(
|
||||
"Accepting patch without confirmation because placeId is listed and behavior is set to Unlisted PlaceId"
|
||||
)
|
||||
return "Accept"
|
||||
end
|
||||
end
|
||||
elseif confirmationBehavior == "Never" then
|
||||
Log.trace("Accepting patch without confirmation because behavior is set to Never")
|
||||
return "Accept"
|
||||
end
|
||||
elseif confirmationBehavior == "Never" then
|
||||
Log.trace("Accepting patch without confirmation because behavior is set to Never")
|
||||
scheduleOneShotDisconnect()
|
||||
return "Accept"
|
||||
end
|
||||
end
|
||||
|
||||
-- The datamodel name gets overwritten by Studio, making confirmation of it intrusive
|
||||
-- and unnecessary. This special case allows it to be accepted without confirmation.
|
||||
if
|
||||
PatchSet.hasAdditions(patch) == false
|
||||
and PatchSet.hasRemoves(patch) == false
|
||||
and PatchSet.containsOnlyInstance(patch, instanceMap, game)
|
||||
then
|
||||
local datamodelUpdates = PatchSet.getUpdateForInstance(patch, instanceMap, game)
|
||||
-- The datamodel name gets overwritten by Studio, making confirmation of it intrusive
|
||||
-- and unnecessary. This special case allows it to be accepted without confirmation.
|
||||
if
|
||||
datamodelUpdates ~= nil
|
||||
and next(datamodelUpdates.changedProperties) == nil
|
||||
and datamodelUpdates.changedClassName == nil
|
||||
PatchSet.hasAdditions(patch) == false
|
||||
and PatchSet.hasRemoves(patch) == false
|
||||
and PatchSet.containsOnlyInstance(patch, instanceMap, game)
|
||||
then
|
||||
Log.trace("Accepting patch without confirmation because it only contains a datamodel name change")
|
||||
scheduleOneShotDisconnect()
|
||||
return "Accept"
|
||||
local datamodelUpdates = PatchSet.getUpdateForInstance(patch, instanceMap, game)
|
||||
if
|
||||
datamodelUpdates ~= nil
|
||||
and next(datamodelUpdates.changedProperties) == nil
|
||||
and datamodelUpdates.changedClassName == nil
|
||||
then
|
||||
Log.trace("Accepting patch without confirmation because it only contains a datamodel name change")
|
||||
return "Accept"
|
||||
end
|
||||
end
|
||||
else
|
||||
Log.info("One-shot mode: skipping all auto-accept paths, requiring explicit confirmation")
|
||||
end
|
||||
|
||||
self:setState({
|
||||
|
|
@ -845,6 +834,13 @@ function App:startSession()
|
|||
end)
|
||||
|
||||
serveSession:setPatchUpdateCallback(function(instanceMap, patch)
|
||||
-- ONE-SHOT MODE: Ignore all further changes during confirmation
|
||||
-- They will be picked up on the next connection attempt
|
||||
if Settings:get("oneShotSync") then
|
||||
Log.info("One-shot mode: ignoring incoming changes during confirmation")
|
||||
return
|
||||
end
|
||||
|
||||
-- If all changes have been reverted, auto-accept the empty patch
|
||||
if PatchSet.isEmpty(patch) then
|
||||
Log.trace("Patch became empty after merging, auto-accepting")
|
||||
|
|
|
|||
|
|
@ -78,6 +78,15 @@ function ServeSession.new(options)
|
|||
end
|
||||
|
||||
local function onChangesFlushed(patch)
|
||||
-- ONE-SHOT MODE: Block ALL automatic outgoing writes from the ChangeBatcher.
|
||||
-- The only writes allowed in One-shot mode are explicit "pull" selections
|
||||
-- from the confirmation dialog (handled directly in __confirmAndApplyInitialPatch).
|
||||
-- This ensures nothing can be pushed to the server without explicit user action.
|
||||
if Settings:get("oneShotSync") then
|
||||
Log.info("One-shot mode: blocking automatic outgoing write from ChangeBatcher")
|
||||
return
|
||||
end
|
||||
|
||||
self.__apiContext:write(patch)
|
||||
end
|
||||
|
||||
|
|
@ -211,7 +220,14 @@ function ServeSession:__onWebSocketMessage(messagesPacket)
|
|||
end
|
||||
|
||||
-- If we're already waiting for confirmation, merge into the patch being confirmed
|
||||
-- UNLESS we're in One-shot mode, where we ignore all further changes
|
||||
if self.__confirmingPatch ~= nil then
|
||||
if Settings:get("oneShotSync") then
|
||||
Log.info("One-shot mode: ignoring incoming changes while confirming (will be picked up on next connection)")
|
||||
self.__apiContext:setMessageCursor(messagesPacket.messageCursor)
|
||||
return
|
||||
end
|
||||
|
||||
Log.trace("Already confirming, merging into current patch")
|
||||
PatchSet.merge(self.__confirmingPatch, combinedPatch, self.__instanceMap)
|
||||
-- Notify the UI to update the displayed patch
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue