Improve UX/DX for requesting permissions

This commit is contained in:
boatbomber 2024-02-13 01:00:20 -08:00
parent 0de0610bda
commit 9444c84d73
6 changed files with 387 additions and 281 deletions

View file

@ -1,137 +0,0 @@
local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
local Theme = require(Plugin.App.Theme)
local Checkbox = require(Plugin.App.Components.Checkbox)
local e = Roact.createElement
local DIVIDER_FADE_SIZE = 0.1
local function getTextBounds(text, textSize, font, lineHeight, bounds)
local textBounds = TextService:GetTextSize(text, textSize, font, bounds)
local lineCount = textBounds.Y / textSize
local lineHeightAbsolute = textSize * lineHeight
return Vector2.new(textBounds.X, lineHeightAbsolute * lineCount - (lineHeightAbsolute - textSize))
end
local Toggle = Roact.Component:extend("Toggle")
function Toggle:init()
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
end
function Toggle:render()
return Theme.with(function(theme)
theme = theme.Settings
return e("Frame", {
Size = self.contentSize:map(function(value)
return UDim2.new(1, 0, 0, 20 + value.Y + 20)
end),
LayoutOrder = self.props.layoutOrder,
ZIndex = -self.props.layoutOrder,
BackgroundTransparency = 1,
[Roact.Change.AbsoluteSize] = function(object)
self.setContainerSize(object.AbsoluteSize)
end,
}, {
Input = e(Checkbox, {
active = self.props.active,
transparency = self.props.transparency,
position = UDim2.new(1, 0, 0.5, 0),
anchorPoint = Vector2.new(1, 0.5),
onClick = self.props.onClick,
}),
Text = e("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
}, {
Name = e("TextLabel", {
Text = self.props.name,
Font = Enum.Font.GothamBold,
TextSize = 17,
TextColor3 = theme.Setting.NameColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
Size = UDim2.new(1, 0, 0, 17),
LayoutOrder = 1,
BackgroundTransparency = 1,
}),
Description = e("TextLabel", {
Text = self.props.description,
Font = Enum.Font.Gotham,
LineHeight = 1.2,
TextSize = 14,
TextColor3 = theme.Setting.DescriptionColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
TextWrapped = true,
Size = self.containerSize:map(function(value)
local offset = self.props.options ~= nil and 120 or 40
local textBounds = getTextBounds(
self.props.description,
14,
Enum.Font.Gotham,
1.2,
Vector2.new(value.X - offset, math.huge)
)
return UDim2.new(1, -offset, 0, textBounds.Y)
end),
LayoutOrder = 2,
BackgroundTransparency = 1,
}),
Layout = e("UIListLayout", {
VerticalAlignment = Enum.VerticalAlignment.Center,
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 6),
[Roact.Change.AbsoluteContentSize] = function(object)
self.setContentSize(object.AbsoluteContentSize)
end,
}),
Padding = e("UIPadding", {
PaddingTop = UDim.new(0, 20),
PaddingBottom = UDim.new(0, 20),
}),
}),
Divider = e("Frame", {
BackgroundColor3 = theme.DividerColor,
BackgroundTransparency = self.props.transparency,
Size = UDim2.new(1, 0, 0, 1),
BorderSizePixel = 0,
}, {
Gradient = e("UIGradient", {
Transparency = NumberSequence.new({
NumberSequenceKeypoint.new(0, 1),
NumberSequenceKeypoint.new(DIVIDER_FADE_SIZE, 0),
NumberSequenceKeypoint.new(1 - DIVIDER_FADE_SIZE, 0),
NumberSequenceKeypoint.new(1, 1),
}),
}),
}),
})
end)
end
return Toggle

View file

@ -4,51 +4,139 @@ local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme)
local Toggle = require(Plugin.App.Components.Toggle)
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
local TextButton = require(Plugin.App.Components.TextButton)
local e = Roact.createElement
local DIVIDER_FADE_SIZE = 0.1
local PermissionPopup = Roact.Component:extend("PermissionPopup")
function PermissionPopup:init()
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
self.infoSize, self.setInfoSize = Roact.createBinding(Vector2.new(0, 0))
local response = {}
for _, api in self.props.apis do
response[api] = if self.props.initialState[api] == nil then true else self.props.initialState[api]
end
self:setState({
response = response,
})
end
function PermissionPopup:render()
return Theme.with(function(theme)
theme = theme.Settings
local apiToggles = {}
local thumbnail = Assets.Images.ThirdPartyPlugin
local thumbnailId = string.match(self.props.source, "cloud_(%d+)")
if thumbnailId then
thumbnail = string.format("rbxthumb://type=Asset&id=%s&w=150&h=150", thumbnailId)
end
local apiRequests = {
Event = {},
Property = {},
Method = {},
}
for index, api in self.props.apis do
apiToggles[api] = e(Toggle, {
active = self.state.response[api],
name = api,
description = self.props.apiDescriptions[api],
transparency = self.props.transparency,
layoutOrder = index,
onClick = function()
self:setState(function(state)
state.response[api] = not state.response[api]
return state
end)
end,
local apiDesc = self.props.apiDescriptions[api]
apiRequests[apiDesc.Type][api] = e("Frame", {
LayoutOrder = index,
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 17),
AutomaticSize = Enum.AutomaticSize.Y,
}, {
Divider = e("Frame", {
BackgroundColor3 = theme.DividerColor,
BackgroundTransparency = self.props.transparency,
Size = UDim2.new(1, 0, 0, 1),
Position = UDim2.new(0, 0, 0, -2),
BorderSizePixel = 0,
}, {
Gradient = e("UIGradient", {
Transparency = NumberSequence.new({
NumberSequenceKeypoint.new(0, 1),
NumberSequenceKeypoint.new(DIVIDER_FADE_SIZE, 0),
NumberSequenceKeypoint.new(1 - DIVIDER_FADE_SIZE, 0),
NumberSequenceKeypoint.new(1, 1),
}),
}),
}),
Name = e("TextLabel", {
BackgroundTransparency = 1,
Position = UDim2.new(0, 0, 0, 0),
Size = UDim2.new(0, 140, 0, 17),
TextWrapped = true,
AutomaticSize = Enum.AutomaticSize.Y,
Text = api,
Font = Enum.Font.Gotham,
TextSize = 17,
TextColor3 = theme.Setting.NameColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
}),
Desc = e("TextLabel", {
BackgroundTransparency = 1,
Position = UDim2.new(0, 145, 0, 0),
Size = UDim2.new(1, -145, 0, 17),
TextWrapped = true,
AutomaticSize = Enum.AutomaticSize.Y,
Text = apiDesc.Description,
Font = Enum.Font.Gotham,
TextSize = 15,
TextColor3 = theme.Setting.DescriptionColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
}),
})
end
-- Add labels to explain the api types
if next(apiRequests.Event) then
apiRequests.Event["_apiTypeInfo"] = e("TextLabel", {
LayoutOrder = -1,
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 18),
Text = string.format("%s will be able to listen to these events:", self.props.name),
TextWrapped = true,
AutomaticSize = Enum.AutomaticSize.Y,
Font = Enum.Font.GothamMedium,
TextSize = 17,
TextColor3 = theme.Setting.NameColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
}, e("UIPadding", { PaddingBottom = UDim.new(0, 8) }))
end
if next(apiRequests.Property) then
apiRequests.Property["_apiTypeInfo"] = e("TextLabel", {
LayoutOrder = -1,
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 18),
Text = string.format("%s will be able to read these properties:", self.props.name),
TextWrapped = true,
AutomaticSize = Enum.AutomaticSize.Y,
Font = Enum.Font.GothamMedium,
TextSize = 17,
TextColor3 = theme.Setting.NameColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
}, e("UIPadding", { PaddingBottom = UDim.new(0, 8) }))
end
if next(apiRequests.Method) then
apiRequests.Method["_apiTypeInfo"] = e("TextLabel", {
LayoutOrder = -1,
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 18),
Text = string.format("%s will be able to call these methods:", self.props.name),
TextWrapped = true,
AutomaticSize = Enum.AutomaticSize.Y,
Font = Enum.Font.GothamMedium,
TextSize = 17,
TextColor3 = theme.Setting.NameColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
}, e("UIPadding", { PaddingBottom = UDim.new(0, 8) }))
end
return e("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
@ -56,8 +144,8 @@ function PermissionPopup:render()
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 5),
HorizontalAlignment = Enum.HorizontalAlignment.Right,
Padding = UDim.new(0, 15),
HorizontalAlignment = Enum.HorizontalAlignment.Center,
}),
Padding = e("UIPadding", {
@ -67,59 +155,171 @@ function PermissionPopup:render()
PaddingBottom = UDim.new(0, 15),
}),
Icons = e("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 32),
LayoutOrder = 1,
}, {
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 5),
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),
ThirdPartyIcon = e("ImageLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(0, 32, 0, 32),
Image = thumbnail,
LayoutOrder = 1,
}),
TransactIcon = e("ImageLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(0, 24, 0, 24),
Image = Assets.Images.Icons.Transact,
ImageColor3 = theme.Setting.DescriptionColor,
LayoutOrder = 2,
}),
RojoIcon = e("ImageLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(0, 32, 0, 32),
Image = Assets.Images.PluginButton,
LayoutOrder = 3,
}),
}),
Info = e("TextLabel", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 0),
AutomaticSize = Enum.AutomaticSize.Y,
Text = string.format(
"A third-party plugin, %s, is asking to use the following parts of the Rojo API. Please grant/deny access.",
self.props.name or "[Unknown]"
),
Font = Enum.Font.GothamMedium,
Text = string.format("%s is asking to use the Rojo API", self.props.name or "[Unknown]"),
Font = Enum.Font.GothamBold,
TextSize = 17,
TextColor3 = theme.Setting.NameColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextXAlignment = Enum.TextXAlignment.Center,
TextWrapped = true,
TextTransparency = self.props.transparency,
LayoutOrder = 1,
LayoutOrder = 2,
[Roact.Change.AbsoluteSize] = function(rbx)
self.setInfoSize(rbx.AbsoluteSize)
end,
}),
Submit = e(TextButton, {
text = "Submit",
style = "Solid",
transparency = self.props.transparency,
layoutOrder = 3,
onClick = function()
self.props.responseEvent:Fire(self.state.response)
end,
Divider = e("Frame", {
LayoutOrder = 3,
BackgroundColor3 = theme.DividerColor,
BackgroundTransparency = self.props.transparency,
Size = UDim2.new(1, 0, 0, 1),
Position = UDim2.new(0, 0, 0, -2),
BorderSizePixel = 0,
}, {
Gradient = e("UIGradient", {
Transparency = NumberSequence.new({
NumberSequenceKeypoint.new(0, 1),
NumberSequenceKeypoint.new(DIVIDER_FADE_SIZE, 0),
NumberSequenceKeypoint.new(1 - DIVIDER_FADE_SIZE, 0),
NumberSequenceKeypoint.new(1, 1),
}),
}),
}),
ScrollingFrame = e(ScrollingFrame, {
size = self.infoSize:map(function(infoSize)
return UDim2.new(1, 0, 1, -infoSize.Y - 44)
return UDim2.new(0.9, 0, 1, -infoSize.Y - 140)
end),
layoutOrder = 2,
layoutOrder = 9,
contentSize = self.contentSize,
transparency = self.props.transparency,
}, {
APIToggles = Roact.createFragment(apiToggles),
Padding = e("UIPadding", {
PaddingRight = UDim.new(0, 15),
}),
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 18),
[Roact.Change.AbsoluteContentSize] = function(object)
self.setContentSize(object.AbsoluteContentSize)
end,
}),
PropertyRequests = e("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 0),
AutomaticSize = Enum.AutomaticSize.Y,
LayoutOrder = 1,
}, {
APIs = Roact.createFragment(apiRequests.Property),
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 4),
}),
}),
EventRequests = e("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 0),
AutomaticSize = Enum.AutomaticSize.Y,
LayoutOrder = 2,
}, {
APIs = Roact.createFragment(apiRequests.Event),
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 4),
}),
}),
MethodRequests = e("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 0),
AutomaticSize = Enum.AutomaticSize.Y,
LayoutOrder = 3,
}, {
APIs = Roact.createFragment(apiRequests.Method),
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 4),
}),
}),
}),
Actions = e("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 34),
LayoutOrder = 10,
}, {
Layout = e("UIListLayout", {
FillDirection = Enum.FillDirection.Horizontal,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 10),
}),
Deny = e(TextButton, {
text = "Deny",
style = "Bordered",
transparency = self.props.transparency,
layoutOrder = 1,
onClick = function()
self.props.responseEvent:Fire(false)
end,
}),
Allow = e(TextButton, {
text = "Allow",
style = "Solid",
transparency = self.props.transparency,
layoutOrder = 2,
onClick = function()
self.props.responseEvent:Fire(true)
end,
}),
}),
})
end)

View file

@ -84,7 +84,35 @@ function PermissionsPage:render()
theme = theme.Settings
local sources = {}
if next(self.state.permissions) == nil then
for source, permissions in self.state.permissions do
if next(permissions) == nil then
continue
end
local meta = self.props.headlessAPI:_getMetaFromSource(source)
sources[source] = e(Listing, {
layoutOrder = string.byte(source),
transparency = self.props.transparency,
name = meta.Name,
description = string.format(
"%s plugin%s",
meta.Type,
if meta.Creator then " by " .. meta.Creator else ""
),
onClick = function()
self.props.onEdit(
self.props.headlessAPI._sourceToPlugin[source],
source,
meta,
self.props.headlessAPI._permissions[source] or {}
)
end,
})
end
if next(sources) == nil then
sources.noSources = e("TextLabel", {
Text = "No third-party plugins have been granted permissions.",
Font = Enum.Font.Gotham,
@ -98,34 +126,6 @@ function PermissionsPage:render()
BackgroundTransparency = 1,
})
else
for source, permissions in self.state.permissions do
if next(permissions) == nil then
continue
end
local meta = self.props.headlessAPI:_getMetaFromSource(source)
sources[source] = e(Listing, {
layoutOrder = string.byte(source),
transparency = self.props.transparency,
name = meta.Name,
description = string.format(
"%s plugin%s",
meta.Type,
if meta.Creator then " by " .. meta.Creator else ""
),
onClick = function()
self.props.onEdit(
self.props.headlessAPI._sourceToPlugin[source],
source,
meta,
self.props.headlessAPI._permissions[source] or {}
)
end,
})
end
end
return e("Frame", {

View file

@ -459,7 +459,7 @@ function App:requestPermission(
source: string,
name: string,
apis: { string },
initialState: { [string]: boolean? }
initialState: boolean?
): { [string]: boolean }
local responseEvent = Instance.new("BindableEvent")
@ -471,7 +471,7 @@ function App:requestPermission(
"Cancelling API permission request for '{}' because the third-party plugin has been removed.",
name
)
responseEvent:Fire(initialState)
responseEvent:Fire(initialState or false)
end)
else nil
@ -480,7 +480,6 @@ function App:requestPermission(
name = name,
content = e(PermissionPopup, {
responseEvent = responseEvent,
initialState = initialState,
source = source,
name = name,
apis = apis,
@ -488,7 +487,7 @@ function App:requestPermission(
transparency = Roact.createBinding(0),
}),
onClose = function()
responseEvent:Fire(initialState)
responseEvent:Fire(initialState or false)
end,
}
return state
@ -833,7 +832,7 @@ function App:render()
initEnabled = true,
overridePreviousState = true,
floatingSize = Vector2.new(400, 300),
minimumSize = Vector2.new(390, 240),
minimumSize = Vector2.new(390, 240) or popup.minimumSize,
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
@ -961,8 +960,12 @@ function App:render()
for api in apiMap do
table.insert(apiList, api)
end
local response = self:requestPermission(plugin, source, name, apiList, apiMap)
self.headlessAPI:_setPermissions(source, name, response)
local granted = self:requestPermission(plugin, source, name, apiList, true)
if granted then
self.headlessAPI:_setPermissions(source, name, apiList)
else
self.headlessAPI:_removePermissions(source, name)
end
end,
}),

View file

@ -27,6 +27,7 @@ local Assets = {
Reset = "rbxassetid://10142422327",
Expand = "rbxassetid://12045401097",
Settings = "rbxassetid://12046309515",
Transact = "rbxassetid://16350762910",
},
Diff = {
Add = "rbxassetid://10434145835",

View file

@ -29,22 +29,40 @@ function API.new(app)
Rojo._changedEvent = Instance.new("BindableEvent")
Rojo._apiDescriptions = {}
Rojo._apiDescriptions.Changed = "An event that fires when a headless API property changes"
Rojo._apiDescriptions.Changed = {
Type = "Event",
Description = "An event that fires when a headless API property changes",
}
Rojo.Changed = Rojo._changedEvent.Event
Rojo._apiDescriptions.Connected = "Whether or not the plugin is connected to a Rojo server"
Rojo._apiDescriptions.Connected = {
Type = "Property",
Description = "Whether or not the plugin is connected to a Rojo server",
}
Rojo.Connected = if app.serveSession then app.serveSession:getStatus() == "Connected" else false
Rojo._apiDescriptions.Address = "The address (host:port) that the plugin is connected to"
Rojo._apiDescriptions.Address = {
Type = "Property",
Description = "The address (host:port) that the plugin is connected to",
}
Rojo.Address = nil
Rojo._apiDescriptions.ProjectName = "The name of the project that the plugin is connected to"
Rojo._apiDescriptions.ProjectName = {
Type = "Property",
Description = "The name of the project that the plugin is connected to",
}
Rojo.ProjectName = nil
Rojo._apiDescriptions.Version = "The version of the plugin"
Rojo._apiDescriptions.Version = {
Type = "Property",
Description = "The version of the plugin",
}
Rojo.Version = table.clone(Config.version)
Rojo._apiDescriptions.ProtocolVersion = "The protocol version that the plugin is using"
Rojo._apiDescriptions.ProtocolVersion = {
Type = "Property",
Description = "The protocol version that the plugin is using",
}
Rojo.ProtocolVersion = Config.protocolVersion
function Rojo:_updateProperty(property: string, value: any?)
@ -187,52 +205,49 @@ function API.new(app)
local source = Rojo:_getCallerSource()
if Rojo._permissions[source] == nil then
Rojo._permissions[source] = {}
return false
end
return not not Rojo._permissions[source][key]
end
function Rojo:_setPermissions(source, name, permissions)
-- Ensure permissions exist for this source
if Rojo._permissions[source] == nil then
Rojo._permissions[source] = {}
if next(permissions) == nil then
Rojo:_removePermissions(source, name)
return
end
-- Set permissions
for api, granted in permissions do
Log.warn(
string.format(
"%s Rojo.%s for '%s'",
granted and "Granting permission to" or "Denying permission to",
api,
name
)
)
Rojo._permissions[source][api] = granted
local sourcePermissions = {}
for _, api in permissions do
Log.info(string.format("Granting '%s' access to Rojo.%s", name, api))
sourcePermissions[api] = true
end
-- Clear out source if no permissions are granted
local hasAnyPermissions = false
for _, granted in Rojo._permissions[source] do
if granted then
hasAnyPermissions = true
break
end
end
if not hasAnyPermissions then
Rojo._permissions[source] = nil
end
-- Update stored permissions
Rojo._permissions[source] = sourcePermissions
Settings:set("apiPermissions", Rojo._permissions)
-- Share changes
Rojo._permissionsChangedEvent:Fire(source, sourcePermissions)
end
function Rojo:_removePermissions(source, name)
Rojo._permissions[source] = nil
Log.info(string.format("Denying access to Rojo APIs for '%s'", name))
-- Update stored permissions
Settings:set("apiPermissions", Rojo._permissions)
-- Share changes
Rojo._permissionsChangedEvent:Fire(source, Rojo._permissions[source])
Rojo._permissionsChangedEvent:Fire(source, nil)
end
Rojo._apiDescriptions.RequestAccess = "Used to gain access to Rojo API members"
function Rojo:RequestAccess(plugin: Plugin, apis: { string }): { [string]: boolean }
Rojo._apiDescriptions.RequestAccess = {
Type = "Method",
Description = "Used to gain access to Rojo API members",
}
function Rojo:RequestAccess(plugin: Plugin, apis: { string }): boolean
assert(type(apis) == "table", "Rojo:RequestAccess expects an array of valid API names as the second argument")
assert(
typeof(plugin) == "Instance" and plugin:IsA("Plugin"),
@ -244,7 +259,7 @@ function API.new(app)
if Rojo:_checkRateLimit("RequestAccess") then
-- Because this opens a popup, we dont want to let users get spammed by it
return {}
return false
end
if Rojo._activePermissionRequests[source] then
@ -256,10 +271,6 @@ function API.new(app)
end
Rojo._activePermissionRequests[source] = true
if Rojo._permissions[source] == nil then
Rojo._permissions[source] = {}
end
-- Sanitize request
local sanitizedApis = {}
for _, api in apis do
@ -270,33 +281,40 @@ function API.new(app)
end
end
assert(#sanitizedApis > 0, "Rojo:RequestAccess expects an array of valid API names")
table.sort(sanitizedApis)
local alreadyAllowed = true
for _, api in sanitizedApis do
if not Rojo._permissions[source][api] then
alreadyAllowed = false
break
if Rojo._permissions[source] == nil then
alreadyAllowed = false
else
for _, api in sanitizedApis do
if not Rojo._permissions[source][api] then
alreadyAllowed = false
break
end
end
end
if alreadyAllowed then
local response = {}
for _, api in sanitizedApis do
response[api] = true
end
Rojo._activePermissionRequests[source] = nil
return response
return true
end
local response = app:requestPermission(plugin, source, name, sanitizedApis, Rojo._permissions[source])
Rojo:_setPermissions(source, name, response)
local granted = app:requestPermission(plugin, source, name, sanitizedApis, false)
if granted then
Rojo:_setPermissions(source, name, sanitizedApis)
else
Rojo:_removePermissions(source, name)
end
Rojo._activePermissionRequests[source] = nil
return response
return granted
end
Rojo._apiDescriptions.Test = "Prints the given arguments to the console"
Rojo._apiDescriptions.Test = {
Type = "Method",
Description = "Prints the given arguments to the console",
}
function Rojo:Test(...)
local args = table.pack(...)
for i = 1, args.n do
@ -319,7 +337,10 @@ function API.new(app)
)
end
Rojo._apiDescriptions.ConnectAsync = "Connects to a Rojo server"
Rojo._apiDescriptions.ConnectAsync = {
Type = "Method",
Description = "Connects to a Rojo server",
}
function Rojo:ConnectAsync(host: string?, port: string?)
assert(type(host) == "string" or host == nil, "Host must be type `string?`")
assert(type(port) == "string" or port == nil, "Port must be type `string?`")
@ -331,7 +352,10 @@ function API.new(app)
app:startSession(host, port)
end
Rojo._apiDescriptions.DisconnectAsync = "Disconnects from the Rojo server"
Rojo._apiDescriptions.DisconnectAsync = {
Type = "Method",
Description = "Disconnects from the Rojo server",
}
function Rojo:DisconnectAsync()
if Rojo:_checkRateLimit("DisconnectAsync") then
return
@ -340,14 +364,20 @@ function API.new(app)
app:endSession()
end
Rojo._apiDescriptions.GetSetting = "Gets a Rojo setting"
Rojo._apiDescriptions.GetSetting = {
Type = "Method",
Description = "Gets a Rojo setting",
}
function Rojo:GetSetting(setting: string): any
assert(type(setting) == "string", "Setting must be type `string`")
return Settings:get(setting)
end
Rojo._apiDescriptions.SetSetting = "Sets a Rojo setting"
Rojo._apiDescriptions.SetSetting = {
Type = "Method",
Description = "Sets a Rojo setting",
}
function Rojo:SetSetting(setting: string, value: any)
assert(type(setting) == "string", "Setting must be type `string`")
assert(type(value) == type(Settings:get(setting)), "Value must be the same type as the setting")
@ -359,7 +389,10 @@ function API.new(app)
return Settings:set(setting, value)
end
Rojo._apiDescriptions.Notify = "Shows a notification in the Rojo UI"
Rojo._apiDescriptions.Notify = {
Type = "Method",
Description = "Shows a notification in the Rojo UI",
}
function Rojo:Notify(
msg: string,
timeout: number?,
@ -403,12 +436,18 @@ function API.new(app)
return app:addThirdPartyNotification(Rojo:_getCallerName(), msg, timeout, sanitizedActions)
end
Rojo._apiDescriptions.GetHostAndPort = "Gets the host and port that Rojo is set to"
Rojo._apiDescriptions.GetHostAndPort = {
Type = "Method",
Description = "Gets the host and port that Rojo is set to",
}
function Rojo:GetHostAndPort(): (string, string)
return app:getHostAndPort()
end
Rojo._apiDescriptions.CreateApiContext = "Creates a new API context"
Rojo._apiDescriptions.CreateApiContext = {
Type = "Method",
Description = "Creates a new API context",
}
function Rojo:CreateApiContext(baseUrl: string)
assert(type(baseUrl) == "string", "Base URL must be type `string`")