Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Headless API #639

Open
wants to merge 91 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
4832035
Add headless API
boatbomber Sep 25, 2022
be89259
Add Address and ProjectName when connected
boatbomber Sep 25, 2022
f3de0c3
Nicer notification formatting and info
boatbomber Sep 25, 2022
2a25193
Add CreateApiContext and more caller info
boatbomber Sep 27, 2022
17b2752
Add Changed event
boatbomber Sep 28, 2022
6284d83
Use full caller as key to avoid conflicts
boatbomber Sep 28, 2022
d7cee5c
Add api permissions
boatbomber Sep 28, 2022
e3a8152
Handle plugin state prior to headless
boatbomber Sep 30, 2022
e0c7b51
Support cloud plugins that have no folder
boatbomber Sep 30, 2022
ea08d28
Add assertions
boatbomber Sep 30, 2022
471215f
Add rate limiting
boatbomber Sep 30, 2022
6a5876c
Include source in Test output for debugging
boatbomber Sep 30, 2022
2a8cb11
Clearer names and titles
boatbomber Sep 30, 2022
7e7583b
Merge branch 'master' into headless-api
boatbomber Oct 1, 2022
7c0a67b
Merge branch 'master' of boatbomber/rojo into headless-api
boatbomber Oct 8, 2022
ebdffb0
Sanitiza requested APIs
boatbomber Oct 8, 2022
4275783
Add permissions page
boatbomber Jan 4, 2023
7c1afd5
Add permission management listings
boatbomber Jan 4, 2023
de33cf0
Genericize popup state
boatbomber Jan 4, 2023
d158ccb
Simplify popup handling
boatbomber Jan 4, 2023
6fddcc5
Generic popup titles
boatbomber Jan 5, 2023
cf5e13f
Add api conflict popup
boatbomber Jan 5, 2023
3b1aca2
Clearer button UX
boatbomber Jan 5, 2023
e24657c
Better light theme support
boatbomber Jan 5, 2023
9cf1956
Don't scroll the navbar
boatbomber Jan 5, 2023
d83543e
Handle falsy API values
boatbomber Jan 5, 2023
9d412a3
Merge branch 'master' of https://github.com/rojo-rbx/rojo into headle…
boatbomber Jan 5, 2023
5cf9a7d
Handle nil APIs
boatbomber Jan 9, 2023
8a8ee80
Move api descs to top
boatbomber Jan 9, 2023
36ec55d
Merge branch 'master' into headless-api
boatbomber Apr 2, 2023
ba634d4
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber May 31, 2023
b68b89b
Update changelog
boatbomber Jun 4, 2023
1ff018a
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Jul 4, 2023
a795e23
Fix typo in log
boatbomber Jul 4, 2023
7d1ee6e
Support actions on third party notifs
boatbomber Jul 4, 2023
2f78d94
Undo old notif changes
boatbomber Jul 4, 2023
dd30b16
Improve sanitization error messages
boatbomber Jul 4, 2023
5c0e1d0
Adjust the UI for third party notifications to include source
boatbomber Jul 4, 2023
91cba1b
Return dismiss function from Notify
boatbomber Jul 4, 2023
dcfdf7f
Prevent misuse of RequestAccess
boatbomber Jul 5, 2023
a0b9931
Fix editing permissions to use new request api
boatbomber Jul 8, 2023
1220b5c
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Jul 17, 2023
478cfba
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Jul 23, 2023
dda01ae
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Aug 15, 2023
4ab51e1
Merge branch 'master' into headless-api
boatbomber Aug 21, 2023
b401b6d
Use new setting input prop
boatbomber Aug 21, 2023
955b143
Change to a game.Rojo module instead of _G
boatbomber Sep 4, 2023
d1d7052
Properly handle changing and removing permissions
boatbomber Sep 4, 2023
c9ca6be
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Sep 18, 2023
5884247
Use latest plugingui component
boatbomber Sep 18, 2023
1042b36
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Sep 19, 2023
b8e5fda
Stylua formatting
boatbomber Sep 19, 2023
e7cf733
Remove unneeded frame
boatbomber Sep 19, 2023
3e0d850
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Oct 3, 2023
15f5a89
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Oct 30, 2023
7d8ba31
Merge branch 'headless-api' of https://github.com/boatbomber/rojo int…
boatbomber Oct 30, 2023
e066846
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Jan 2, 2024
1bb3c81
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Jan 6, 2024
34f920a
Merge branch 'master' into headless-api
boatbomber Jan 17, 2024
3a28b03
Merge branch 'master' into headless-api
boatbomber Jan 30, 2024
9397dba
Merge branch 'master' of https://github.com/rojo-rbx/rojo into headle…
boatbomber Feb 1, 2024
528e2f0
Merge branch 'master' of https://github.com/boatbomber/rojo into head…
boatbomber Feb 1, 2024
09bcef8
Remove unused vars
boatbomber Feb 1, 2024
5609205
Fix shadowed and unused theme
boatbomber Feb 1, 2024
997a040
Fix changelog pr link
boatbomber Feb 13, 2024
e459ae9
Merge branch 'master' of https://github.com/rojo-rbx/rojo into headle…
boatbomber Feb 13, 2024
b98af57
Validate settings type before passing them to set
boatbomber Feb 13, 2024
57b37f1
Strip duplicate APIs in request
boatbomber Feb 13, 2024
1cd2153
Move permissions above experimental section
boatbomber Feb 13, 2024
ab75e2b
Switch to userdata with locked metatable
boatbomber Feb 13, 2024
0de0610
Spawn action click callbacks in notifs
boatbomber Feb 13, 2024
9444c84
Improve UX/DX for requesting permissions
boatbomber Feb 13, 2024
a381479
Fix type signature
boatbomber Feb 13, 2024
fd2e309
Note that Test is for development
boatbomber Feb 13, 2024
d1078a1
Clearer text for users
boatbomber Feb 13, 2024
a7e15a0
Remove setsetting
boatbomber Feb 14, 2024
1af32ad
Ensure permissions are always listed in the same order
boatbomber Feb 14, 2024
177d480
Merge branch 'master' of https://github.com/rojo-rbx/rojo into headle…
boatbomber Nov 6, 2024
6f9fd5c
add images to assets
boatbomber Nov 6, 2024
eeb6f00
Move some image assets into icons dir
boatbomber Nov 6, 2024
cfea77e
Merge branch 'master' of https://github.com/rojo-rbx/rojo into headle…
boatbomber Dec 22, 2024
03fa1b3
Use new theme font/textsize
boatbomber Dec 22, 2024
a330c12
Fix theme usage
boatbomber Dec 22, 2024
12e3d2b
Add verified icon
boatbomber Dec 22, 2024
139b283
Improve displayed info about third party plugins
boatbomber Dec 22, 2024
37d893e
Remove unused service
boatbomber Dec 22, 2024
83a4fce
Simplify creator info and differentiate user/group
boatbomber Dec 22, 2024
742c2a3
Improved creator text style
boatbomber Dec 22, 2024
92a3044
Include creator info in notifs
boatbomber Dec 23, 2024
c45271b
Improve notification layout
boatbomber Dec 24, 2024
1ac8b9d
Merge branch 'fix-notif-widths' of https://github.com/boatbomber/rojo…
boatbomber Dec 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/thirdParty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions plugin/src/App/Components/TextButton.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ end
function TextButton:render()
return Theme.with(function(theme)
local textSize = TextService:GetTextSize(
self.props.text, 18, Enum.Font.GothamSemibold,
self.props.text, 18, Enum.Font.GothamMedium,
Vector2.new(math.huge, math.huge)
)

Expand Down Expand Up @@ -85,7 +85,7 @@ function TextButton:render()

Text = e("TextLabel", {
Text = self.props.text,
Font = Enum.Font.GothamSemibold,
Font = Enum.Font.GothamMedium,
TextSize = 18,
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.TextColor, theme.Disabled.TextColor),
TextTransparency = self.props.transparency,
Expand Down
38 changes: 20 additions & 18 deletions plugin/src/App/Notifications.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function Notification:render()
local textBounds = TextService:GetTextSize(
self.props.text,
15,
Enum.Font.GothamSemibold,
Enum.Font.GothamMedium,
Vector2.new(350, 700)
)

Expand All @@ -102,7 +102,7 @@ function Notification:render()
local size = self.binding:map(function(value)
return UDim2.fromOffset(
(35+40+textBounds.X)*value,
math.max(14+20+textBounds.Y, 32+20)
math.max(16+20+textBounds.Y, 54)
)
end)

Expand All @@ -129,46 +129,46 @@ function Notification:render()
}, {
Logo = e("ImageLabel", {
ImageTransparency = transparency,
Image = Assets.Images.PluginButton,
Image = if self.props.thirdParty then Assets.Images.ThirdPartyPlugin else Assets.Images.PluginButton,
BackgroundTransparency = 1,
Size = UDim2.new(0, 32, 0, 32),
Position = UDim2.new(0, 0, 0.5, 0),
AnchorPoint = Vector2.new(0, 0.5),
}),
Info = e("TextLabel", {
Message = e("TextLabel", {
Text = self.props.text,
Font = Enum.Font.GothamSemibold,
Font = Enum.Font.GothamMedium,
TextSize = 15,
TextColor3 = theme.Notification.InfoColor,
TextTransparency = transparency,
TextXAlignment = Enum.TextXAlignment.Left,
TextWrapped = true,

Size = UDim2.new(0, textBounds.X, 0, textBounds.Y),
Position = UDim2.fromOffset(35, 0),
Position = UDim2.fromOffset(38, 0),

LayoutOrder = 1,
BackgroundTransparency = 1,
}),
Time = e("TextLabel", {
Text = time:FormatLocalTime("LTS", "en-us"),
Font = Enum.Font.Code,
Info = e("TextLabel", {
Text = if self.props.source then "From: " .. self.props.source else time:FormatLocalTime("LTS", "en-us"),
boatbomber marked this conversation as resolved.
Show resolved Hide resolved
Font = Enum.Font.Gotham,
TextSize = 12,
TextColor3 = theme.Notification.InfoColor,
TextTransparency = transparency,
TextXAlignment = Enum.TextXAlignment.Left,

Size = UDim2.new(1, -35, 0, 14),
Position = UDim2.new(0, 35, 1, -14),
Size = UDim2.new(1, -38, 0, 14),
Position = UDim2.new(0, 38, 1, -14),

LayoutOrder = 1,
BackgroundTransparency = 1,
}),
}),

Padding = e("UIPadding", {
PaddingLeft = UDim.new(0, 17),
PaddingRight = UDim.new(0, 15),
PaddingLeft = UDim.new(0, 12),
PaddingRight = UDim.new(0, 12),
}),
})
})
Expand All @@ -181,16 +181,18 @@ function Notifications:render()
local notifs = {}

for index, notif in ipairs(self.props.notifications) do
notifs[notif] = e(Notification, {
local props = {
soundPlayer = self.props.soundPlayer,
text = notif.text,
timestamp = notif.timestamp,
timeout = notif.timeout,
layoutOrder = (notif.timestamp - baseClock),
onClose = function()
self.props.onClose(index)
end,
})
}
for key, value in notif do
props[key] = value
end

notifs[notif] = e(Notification, props)
end

return Roact.createFragment(notifs)
Expand Down
47 changes: 42 additions & 5 deletions plugin/src/App/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ local strict = require(Plugin.strict)
local Dictionary = require(Plugin.Dictionary)
local ServeSession = require(Plugin.ServeSession)
local ApiContext = require(Plugin.ApiContext)
local HeadlessAPI = require(Plugin.HeadlessAPI)
local preloadAssets = require(Plugin.preloadAssets)
local soundPlayer = require(Plugin.soundPlayer)
local Theme = require(script.Theme)
Expand Down Expand Up @@ -48,6 +49,11 @@ function App:init()
self.host, self.setHost = Roact.createBinding(priorHost or "")
self.port, self.setPort = Roact.createBinding(priorPort or "")

self.headlessAPI, self.readOnlyHeadlessAPI = HeadlessAPI.new(self, Config, Settings)

-- selene: allow(global_usage)
_G.Rojo = self.readOnlyHeadlessAPI -- Expose headless to other plugins and command bar

self.patchInfo, self.setPatchInfo = Roact.createBinding({
changes = 0,
timestamp = os.time(),
Expand Down Expand Up @@ -78,6 +84,25 @@ function App:addNotification(text: string, timeout: number?)
})
end

function App:addThirdPartyNotification(source: string, text: string, timeout: number?)
if not Settings:get("showNotifications") then
return
end

local notifications = table.clone(self.state.notifications)
table.insert(notifications, {
text = text,
timestamp = DateTime.now().UnixTimestampMillis,
timeout = timeout or 3,
thirdParty = true,
source = source,
})

self:setState({
notifications = notifications,
})
end

function App:closeNotification(index: number)
local notifications = table.clone(self.state.notifications)
table.remove(notifications, index)
Expand Down Expand Up @@ -126,7 +151,7 @@ function App:setPriorEndpoint(host: string, port: string)
Settings:set("priorEndpoints", priorEndpoints)
end

function App:getHostAndPort()
function App:getHostAndPort(): (string, string)
local host = self.host:getValue()
local port = self.port:getValue()

Expand Down Expand Up @@ -179,7 +204,7 @@ function App:releaseSyncLock()
Log.trace("Could not relase sync lock because it is owned by {}", lock.Value)
end

function App:startSession()
function App:startSession(host: string?, port: string?)
local claimedLock, priorOwner = self:claimSyncLock()
if not claimedLock then
local msg = string.format("Could not sync because user '%s' is already syncing", tostring(priorOwner))
Expand All @@ -195,14 +220,16 @@ function App:startSession()
return
end

local host, port = self:getHostAndPort()
if host == nil or port == nil then
host, port = self:getHostAndPort()
end

local sessionOptions = {
openScriptsExternally = Settings:get("openScriptsExternally"),
twoWaySync = Settings:get("twoWaySync"),
}

local baseUrl = ("http://%s:%s"):format(host, port)
local baseUrl = string.format("http://%s:%s", host :: string, port :: string)
local apiContext = ApiContext.new(baseUrl)

local serveSession = ServeSession.new({
Expand Down Expand Up @@ -240,6 +267,12 @@ function App:startSession()
end)

serveSession:onStatusChanged(function(status, details)
self.headlessAPI.Connected = status == ServeSession.Status.Connected
if not self.headlessAPI.Connected then
self.headlessAPI.Address = nil
self.headlessAPI.ProjectName = nil
end

if status == ServeSession.Status.Connecting then
self:setPriorEndpoint(host, port)

Expand All @@ -249,7 +282,11 @@ function App:startSession()
})
self:addNotification("Connecting to session...")
elseif status == ServeSession.Status.Connected then
local address = ("%s:%s"):format(host, port)
local address = string.format("%s:%s", host :: string, port :: string)

self.headlessAPI.Address = address
self.headlessAPI.ProjectName = details

self:setState({
appStatus = AppStatus.Connected,
projectName = details,
Expand Down
1 change: 1 addition & 0 deletions plugin/src/Assets.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ local Assets = {
PluginButton = "rbxassetid://3405341609",
PluginButtonConnected = "rbxassetid://9529783993",
PluginButtonWarning = "rbxassetid://9529784530",
ThirdPartyPlugin = "rbxassetid://11064843298",
Icons = {
Close = "rbxassetid://6012985953",
Back = "rbxassetid://6017213752",
Expand Down
94 changes: 94 additions & 0 deletions plugin/src/HeadlessAPI.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
local API = {}

function API.new(app, config, settings)
local Rojo = {}

Rojo.Connected = if app.serveSession then app.serveSession:getStatus() == "Connected" else false
Rojo.Address = nil
Rojo.ProjectName = nil
Rojo.Version = table.clone(config.version)
Rojo.ProtocolVersion = config.protocolVersion

Rojo._notifRateLimit = {}

function Rojo:Test(...)
print("Rojo:Test called by", Rojo:_getCaller(), "with args", ...)
end

function Rojo:_getCaller()
local traceback = string.split(debug.traceback(), "\n")
local topLevel = traceback[#traceback - 1]

local debugPlugin = string.match(topLevel, "^PluginDebugService%.user_(.-)%.")
if debugPlugin then
return debugPlugin
end

local localPlugin = string.match(topLevel, "^user_(.-)%.")
if localPlugin then
return localPlugin
end

local cloudPlugin = string.match(topLevel, "cloud_%d-%.(.-)%.")
if cloudPlugin then
return cloudPlugin
end

return "Command Bar"
end

function Rojo:ConnectAsync(host: string?, port: number?)
app:startSession(host, port)
end

function Rojo:DisconnectAsync()
app:endSession()
end

function Rojo:GetSetting(setting: string): any
return settings:get(setting)
end

function Rojo:SetSetting(setting: string, value: any)
boatbomber marked this conversation as resolved.
Show resolved Hide resolved
return settings:set(setting, value)
end

function Rojo:Notify(msg: string, timeout: number?)
local source = Rojo:_getCaller()

if Rojo._notifRateLimit[source] == nil then
Rojo._notifRateLimit[source] = 0
elseif Rojo._notifRateLimit[source] > 45 then
return -- Rate limited
end

Rojo._notifRateLimit[source] += 1
task.delay(30, function()
Rojo._notifRateLimit[source] -= 1
end)

app:addThirdPartyNotification(source, msg, timeout)
return
end

function Rojo:GetHostAndPort(): (string, string)
return app:getHostAndPort()
end

local ReadOnly = setmetatable({}, {
__index = function(_, key)
if string.find(key, "^_") then
return nil -- Don't expose private members
end
return Rojo[key]
end,
__newindex = function(_, key, value)
error(string.format("Attempted to set Rojo.%s to %q but it's a read-only value", key, value), 2)
return
end,
boatbomber marked this conversation as resolved.
Show resolved Hide resolved
})

return Rojo, ReadOnly
end

return API