diff --git a/plugin/src/App/Components/Toggle.lua b/plugin/src/App/Components/Toggle.lua deleted file mode 100644 index 967c3ddb..00000000 --- a/plugin/src/App/Components/Toggle.lua +++ /dev/null @@ -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 diff --git a/plugin/src/App/PermissionPopup.lua b/plugin/src/App/PermissionPopup.lua index 814c7035..d63cf5d5 100644 --- a/plugin/src/App/PermissionPopup.lua +++ b/plugin/src/App/PermissionPopup.lua @@ -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) diff --git a/plugin/src/App/StatusPages/Permissions/init.lua b/plugin/src/App/StatusPages/Permissions/init.lua index 3762e474..c895e401 100644 --- a/plugin/src/App/StatusPages/Permissions/init.lua +++ b/plugin/src/App/StatusPages/Permissions/init.lua @@ -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", { diff --git a/plugin/src/App/init.lua b/plugin/src/App/init.lua index 88ef3f31..a040a5c4 100644 --- a/plugin/src/App/init.lua +++ b/plugin/src/App/init.lua @@ -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, }), diff --git a/plugin/src/Assets.lua b/plugin/src/Assets.lua index 247eec4b..a96a8d70 100644 --- a/plugin/src/Assets.lua +++ b/plugin/src/Assets.lua @@ -27,6 +27,7 @@ local Assets = { Reset = "rbxassetid://10142422327", Expand = "rbxassetid://12045401097", Settings = "rbxassetid://12046309515", + Transact = "rbxassetid://16350762910", }, Diff = { Add = "rbxassetid://10434145835", diff --git a/plugin/src/HeadlessAPI.lua b/plugin/src/HeadlessAPI.lua index 1ba2ee23..adc9cee0 100644 --- a/plugin/src/HeadlessAPI.lua +++ b/plugin/src/HeadlessAPI.lua @@ -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`")