add gs
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
local Constants = {
|
||||
SIGNALR_NAMESPACE = "TimedEntertainmentAllowanceNotifications",
|
||||
SIGNALR_TYPE_NEW_INSTRUCTION = "NewInstruction",
|
||||
HEARTBEAT_NOTIFICATIONS_NAMESPACE = "ScreenTimeClientNotifications",
|
||||
HEARTBEAT_NOTIFICATION_TYPE_NEW_INSTRUCTION = "ScreentimeInstructionCheck",
|
||||
}
|
||||
|
||||
return Constants
|
||||
@@ -0,0 +1,5 @@
|
||||
game:DefineFastFlag("LuaEnableScreenTime", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("LuaEnableScreenTime")
|
||||
end
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
game:DefineFastFlag("LuaEnableScreenTimeSignalR", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("LuaEnableScreenTimeSignalR")
|
||||
end
|
||||
@@ -0,0 +1,183 @@
|
||||
--[[
|
||||
Provides HTTP request methods for ScreenTime feature.
|
||||
]]
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
local UrlBuilder = require(CorePackages.Packages.UrlBuilder).UrlBuilder
|
||||
local Logging = require(CorePackages.Logging)
|
||||
local ArgCheck = require(CorePackages.ArgCheck)
|
||||
|
||||
local TEA_END_POINTS = {
|
||||
GET_INSTRUCTIONS = "timed-entertainment-allowance/v1/instructions",
|
||||
REPORT_EXECUTION = "timed-entertainment-allowance/v1/reportExecute",
|
||||
}
|
||||
|
||||
local RESPONSE_FORMATS = {
|
||||
GET_INSTRUCTIONS = {
|
||||
errorCode = "number",
|
||||
instructions = "table"
|
||||
},
|
||||
INSTRUCTION = {
|
||||
type = "number",
|
||||
instructionName = "string",
|
||||
serialId = "string",
|
||||
title = "string",
|
||||
message = "string",
|
||||
url = "string",
|
||||
modalType = "number",
|
||||
data = "string"
|
||||
},
|
||||
REPORT_EXECUTION = {
|
||||
errorCode = "number",
|
||||
},
|
||||
}
|
||||
|
||||
local TAG = "HttpRequests"
|
||||
|
||||
local getInstructionsUrl = UrlBuilder.new({
|
||||
base = Url.APIS_URL,
|
||||
path = TEA_END_POINTS.GET_INSTRUCTIONS
|
||||
})()
|
||||
|
||||
local reportExecutionUrl = UrlBuilder.new({
|
||||
base = Url.APIS_URL,
|
||||
path = TEA_END_POINTS.REPORT_EXECUTION
|
||||
})()
|
||||
|
||||
--[[
|
||||
A helper function to check object table have the required fields and fields'
|
||||
type specified in format table.
|
||||
It will throw exceptions, so must be encapsulated by pcall.
|
||||
]]
|
||||
local function checkFormat(format, object)
|
||||
for key, typeString in pairs(format) do
|
||||
assert(object[key] ~= nil, "Missing key")
|
||||
assert(type(object[key]) == typeString, "Wrong type")
|
||||
end
|
||||
end
|
||||
|
||||
local HttpRequests = {
|
||||
httpService = nil,
|
||||
}
|
||||
|
||||
--[[
|
||||
Create a new HttpRequests object.
|
||||
|
||||
@param httpService: Pass in HttpService from game:GetService("HttpService")
|
||||
]]
|
||||
function HttpRequests:new(httpService)
|
||||
ArgCheck.isNotNil(httpService, "httpService")
|
||||
local obj = {
|
||||
httpService = httpService,
|
||||
}
|
||||
setmetatable(obj, self)
|
||||
self.__index = self
|
||||
return obj
|
||||
end
|
||||
|
||||
--[[
|
||||
Query TEA endpoint to get the instructions to execute.
|
||||
|
||||
@param callback: it should be non-nil with signature:
|
||||
callback(success, unauthorized, instructions)
|
||||
success (boolean): indicate whether the query is successful.
|
||||
unauthorized (boolean): if success is false, indicate whether the
|
||||
failure is due to authorization issue.
|
||||
instructions (table): if success is true, this is the result
|
||||
(associative array) of tables following
|
||||
RESPONSE_FORMATS.INSTRUCTION format.
|
||||
]]
|
||||
function HttpRequests:getInstructions(callback)
|
||||
ArgCheck.isNotNil(self.httpService, "httpService")
|
||||
ArgCheck.isNotNil(callback, "callback")
|
||||
local httpRequest = self.httpService:RequestInternal({
|
||||
Url = getInstructionsUrl,
|
||||
Method = "GET",
|
||||
})
|
||||
httpRequest:Start(function(reqSuccess, reqResponse)
|
||||
local success
|
||||
local err
|
||||
local unauthorized = false
|
||||
local instructions = {}
|
||||
if not reqSuccess then
|
||||
success = false
|
||||
err = "Connection error"
|
||||
elseif reqResponse.StatusCode == 401 then
|
||||
success = false
|
||||
unauthorized = true
|
||||
err = "Unauthorized"
|
||||
elseif reqResponse.StatusCode < 200 or reqResponse.StatusCode >= 400 then
|
||||
success = false
|
||||
err = "Status code: " .. reqResponse.StatusCode
|
||||
else
|
||||
-- reqSuccess == true and StatusCode >= 200 and StatusCode < 400
|
||||
success, err = pcall(function()
|
||||
local json = self.httpService:JSONDecode(reqResponse.Body)
|
||||
checkFormat(RESPONSE_FORMATS.GET_INSTRUCTIONS, json)
|
||||
assert(json.errorCode == 0, "Error code is not 0")
|
||||
for i, instruction in ipairs(json.instructions) do
|
||||
checkFormat(RESPONSE_FORMATS.INSTRUCTION, instruction)
|
||||
end
|
||||
instructions = json.instructions
|
||||
end)
|
||||
end
|
||||
if not success then
|
||||
Logging.warn(TAG .. " getInstructions failed: " .. getInstructionsUrl .. ", ".. err)
|
||||
end
|
||||
callback(success, unauthorized, instructions)
|
||||
end)
|
||||
end
|
||||
|
||||
--[[
|
||||
Tell TEA endpoint that an instruction has been executed.
|
||||
|
||||
@param instructionName: from RESPONSE_FORMATS.INSTRUCTION
|
||||
@param serialId: from RESPONSE_FORMATS.INSTRUCTION
|
||||
@param callback: can be nil, signature: callback(success)
|
||||
success (boolean): indicate whether the http request is successful.
|
||||
]]
|
||||
function HttpRequests:reportExecution(instructionName, serialId, callback)
|
||||
ArgCheck.isNotNil(self.httpService, "httpService")
|
||||
-- ISO 8601, Example: 2020-06-04T04:44:09Z
|
||||
local formattedTime = os.date("%Y-%m-%dT%H:%M:%SZ")
|
||||
local payload = self.httpService:JSONEncode({
|
||||
instructionName = instructionName,
|
||||
serialId = serialId,
|
||||
execTime = formattedTime,
|
||||
})
|
||||
local httpRequest = self.httpService:RequestInternal({
|
||||
Url = reportExecutionUrl,
|
||||
Method = "POST",
|
||||
Headers = {
|
||||
["Content-Type"] = "application/json",
|
||||
},
|
||||
Body = payload,
|
||||
})
|
||||
httpRequest:Start(function(reqSuccess, reqResponse)
|
||||
local success
|
||||
local err
|
||||
if not reqSuccess then
|
||||
success = false
|
||||
err = "Connection error"
|
||||
elseif reqResponse.StatusCode < 200 and reqResponse.StatusCode >= 400 then
|
||||
success = false
|
||||
err = "Status code: " .. reqResponse.StatusCode
|
||||
else
|
||||
-- reqSuccess == true and StatusCode >= 200 and StatusCode < 400
|
||||
success, err = pcall(function()
|
||||
local json = self.httpService:JSONDecode(reqResponse.Body)
|
||||
checkFormat(RESPONSE_FORMATS.REPORT_EXECUTION, json)
|
||||
assert(json.errorCode == 0, "Error code is not 0")
|
||||
end)
|
||||
end
|
||||
if not success then
|
||||
Logging.warn(TAG .. " reportExecution failed: " .. reportExecutionUrl .. ", ".. err)
|
||||
end
|
||||
if callback ~= nil then
|
||||
callback(success)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return HttpRequests
|
||||
@@ -0,0 +1,250 @@
|
||||
local HttpRequests = require(script.Parent.HttpRequests)
|
||||
|
||||
function createMockHttpService(success, statusCode, errorCode, instructions)
|
||||
local testBody = "test-body"
|
||||
return {
|
||||
RequestInternal = function(self, params)
|
||||
return {
|
||||
Start = function(self, callback)
|
||||
local response = {
|
||||
Body = testBody,
|
||||
StatusCode = statusCode,
|
||||
}
|
||||
callback(success, response)
|
||||
end
|
||||
}
|
||||
end,
|
||||
JSONDecode = function(self, body)
|
||||
assert(body == testBody)
|
||||
return {
|
||||
errorCode = errorCode,
|
||||
instructions = instructions,
|
||||
}
|
||||
end,
|
||||
JSONEncode = function(self, param)
|
||||
return testBody
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return function()
|
||||
local testInstructions = {{
|
||||
type = 3,
|
||||
instructionName = "name",
|
||||
serialId = "id",
|
||||
title = "title",
|
||||
message = "message",
|
||||
url = "url",
|
||||
modalType = 0,
|
||||
data = "",
|
||||
}}
|
||||
|
||||
describe("getInstructions()", function()
|
||||
it("should correctly callback when succeeded", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(true, 200, 0, testInstructions))
|
||||
local called = false
|
||||
function callback(success, unauthorized, instructions)
|
||||
expect(success).to.equal(true)
|
||||
expect(unauthorized).to.equal(false)
|
||||
expect(instructions).to.equal(testInstructions)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:getInstructions(callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should throw when callback is nil", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(false))
|
||||
success, err = pcall(function()
|
||||
httpRequests:getInstructions()
|
||||
end)
|
||||
expect(success).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should throw when not get from new", function()
|
||||
called = false
|
||||
function callback(success, unauthorized, instructions)
|
||||
called = true;
|
||||
end
|
||||
success, err = pcall(function()
|
||||
HttpRequests:getInstructions(callback)
|
||||
end)
|
||||
expect(success).to.equal(false)
|
||||
expect(called).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should correctly callback when connection error", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(false))
|
||||
local called = false
|
||||
function callback(success, unauthorized, instructions)
|
||||
expect(success).to.equal(false)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:getInstructions(callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should correctly callback when 401", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(true, 401))
|
||||
local called = false
|
||||
function callback(success, unauthorized, instructions)
|
||||
expect(success).to.equal(false)
|
||||
expect(unauthorized).to.equal(true)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:getInstructions(callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should correctly callback when 412", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(true, 412))
|
||||
local called = false
|
||||
function callback(success, unauthorized, instructions)
|
||||
expect(success).to.equal(false)
|
||||
expect(unauthorized).to.equal(false)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:getInstructions(callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should correctly callback when errorCode", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(true, 200, 1))
|
||||
local called = false
|
||||
function callback(success, unauthorized, instructions)
|
||||
expect(success).to.equal(false)
|
||||
expect(unauthorized).to.equal(false)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:getInstructions(callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should correctly callback when decoding failed", function()
|
||||
local httpService = createMockHttpService(true, 200, 0)
|
||||
httpService.JSONDecode = function(self, body)
|
||||
assert(false)
|
||||
end
|
||||
local httpRequests = HttpRequests:new(httpService)
|
||||
local called = false
|
||||
function callback(success, unauthorized, instructions)
|
||||
expect(success).to.equal(false)
|
||||
expect(unauthorized).to.equal(false)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:getInstructions(callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should correctly callback when wrong response json format", function()
|
||||
local httpService = createMockHttpService(true, 200, 0)
|
||||
httpService.JSONDecode = function(self, body)
|
||||
return {
|
||||
errorCode = 0,
|
||||
}
|
||||
end
|
||||
local httpRequests = HttpRequests:new(httpService)
|
||||
local called = false
|
||||
function callback(success, unauthorized, instructions)
|
||||
expect(success).to.equal(false)
|
||||
expect(unauthorized).to.equal(false)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:getInstructions(callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("reportExecution()", function()
|
||||
it("should correctly callback when succeeded", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(true, 200, 0))
|
||||
local called = false
|
||||
function callback(success)
|
||||
expect(success).to.equal(true)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:reportExecution("a", "b", callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should throw when not get from new", function()
|
||||
called = false
|
||||
function callback(success)
|
||||
called = true;
|
||||
end
|
||||
success, err = pcall(function()
|
||||
HttpRequests:reportExecution("a", "b", callback)
|
||||
end)
|
||||
expect(success).to.equal(false)
|
||||
expect(called).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should be ok with nil callback when succeeded", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(true, 200, 0))
|
||||
httpRequests:reportExecution("a", "b", nil)
|
||||
end)
|
||||
|
||||
it("should correctly callback when connection error", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(false))
|
||||
local called = false
|
||||
function callback(success)
|
||||
expect(success).to.equal(false)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:reportExecution("a", "b", callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should correctly callback when 401", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(true, 401))
|
||||
local called = false
|
||||
function callback(success)
|
||||
expect(success).to.equal(false)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:reportExecution("a", "b", callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should correctly callback when errorCode", function()
|
||||
local httpRequests = HttpRequests:new(createMockHttpService(true, 200, 1))
|
||||
local called = false
|
||||
function callback(success)
|
||||
expect(success).to.equal(false)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:reportExecution("a", "b", callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should correctly callback when decoding failed", function()
|
||||
local httpService = createMockHttpService(true, 200, 0)
|
||||
httpService.JSONDecode = function(self, body)
|
||||
assert(false)
|
||||
end
|
||||
local httpRequests = HttpRequests:new(httpService)
|
||||
local called = false
|
||||
function callback(success)
|
||||
expect(success).to.equal(false)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:reportExecution("a", "b", callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should correctly callback when wrong response json format", function()
|
||||
local httpService = createMockHttpService(true, 200, 0)
|
||||
httpService.JSONDecode = function(self, body)
|
||||
return { }
|
||||
end
|
||||
local httpRequests = HttpRequests:new(httpService)
|
||||
local called = false
|
||||
function callback(success)
|
||||
expect(success).to.equal(false)
|
||||
called = true;
|
||||
end
|
||||
httpRequests:reportExecution("a", "b", callback)
|
||||
expect(called).to.equal(true)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,59 @@
|
||||
--[[
|
||||
Provides utility methods for ScreenTime feature.
|
||||
]]
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local ArgCheck = require(CorePackages.ArgCheck)
|
||||
local Logging = require(CorePackages.Logging)
|
||||
|
||||
local TAG = "Utils"
|
||||
-- This global key will be accessed by native code
|
||||
local GLOBAL_KEY_LOCKED_OUT = "ScreenTime.lockedOut"
|
||||
-- This string will be checked against in native code (Java, etc.)
|
||||
local TRUE_VALUE = "true"
|
||||
|
||||
local Utils = {
|
||||
globalGetter = nil,
|
||||
globalSetter = nil,
|
||||
}
|
||||
|
||||
--[[
|
||||
Create a new Utils object.
|
||||
|
||||
@param dependencies injected dependencies to get and set global key values.
|
||||
The value should be accessible from native code.
|
||||
members & interfaces:
|
||||
globalGetter(string key) -> string
|
||||
globalSetter(string key, string value)
|
||||
]]
|
||||
function Utils:new(dependencies)
|
||||
ArgCheck.isType(dependencies.globalGetter, "function", "globalGetter")
|
||||
ArgCheck.isType(dependencies.globalSetter, "function", "globalSetter")
|
||||
local obj = {
|
||||
globalGetter = dependencies.globalGetter,
|
||||
globalSetter = dependencies.globalSetter,
|
||||
}
|
||||
setmetatable(obj, self)
|
||||
self.__index = self
|
||||
return obj
|
||||
end
|
||||
|
||||
--[[
|
||||
Get the flag whether current user is locked out.
|
||||
]]
|
||||
function Utils:isLockedOut()
|
||||
ArgCheck.isType(self.globalGetter, "function", "globalGetter")
|
||||
return (self.globalGetter(GLOBAL_KEY_LOCKED_OUT) == TRUE_VALUE)
|
||||
end
|
||||
|
||||
--[[
|
||||
Set the flag that current user is locked out.
|
||||
It will be reset after successful MSDK login.
|
||||
]]
|
||||
function Utils:setLockedOut()
|
||||
Logging.warn(TAG .. " setLockedOut")
|
||||
ArgCheck.isType(self.globalSetter, "function", "globalSetter")
|
||||
self.globalSetter(GLOBAL_KEY_LOCKED_OUT, TRUE_VALUE)
|
||||
end
|
||||
|
||||
return Utils
|
||||
@@ -0,0 +1,52 @@
|
||||
local Utils = require(script.Parent.Utils)
|
||||
|
||||
function createMockDependencies()
|
||||
local dict = {}
|
||||
return {
|
||||
globalGetter = function(key)
|
||||
return dict[key]
|
||||
end,
|
||||
globalSetter = function(key, val)
|
||||
dict[key] = val
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return function()
|
||||
describe("new()", function()
|
||||
it("should throw when getter is nil", function()
|
||||
local mock = createMockDependencies()
|
||||
mock.globalGetter = nil
|
||||
success, err = pcall(function()
|
||||
local utils = Utils:new(mock)
|
||||
end)
|
||||
expect(success).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should throw when setter is nil", function()
|
||||
local mock = createMockDependencies()
|
||||
mock.globalSetter = nil
|
||||
success, err = pcall(function()
|
||||
local utils = Utils:new(mock)
|
||||
end)
|
||||
expect(success).to.equal(false)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("isLockedOut()", function()
|
||||
it("should return false before set", function()
|
||||
local mock = createMockDependencies()
|
||||
local utils = Utils:new(mock)
|
||||
local flag = utils:isLockedOut()
|
||||
expect(flag).to.equal(false)
|
||||
end)
|
||||
|
||||
it("should return true after set", function()
|
||||
local mock = createMockDependencies()
|
||||
local utils = Utils:new(mock)
|
||||
utils:setLockedOut()
|
||||
local flag = utils:isLockedOut()
|
||||
expect(flag).to.equal(true)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
Reference in New Issue
Block a user