local RunService = game:GetService("RunService") local validation = require(script.Parent.Validation) local version = require(script.Parent.Version) local HashLib = require(script.HashLib) local http_api = { protocol = "https", hostName = "api.gameanalytics.com", version = "v2", remoteConfigsVersion = "v1", initializeUrlPath = "init", eventsUrlPath = "events", EGAHTTPApiResponse = { NoResponse = 0, BadResponse = 1, RequestTimeout = 2, JsonEncodeFailed = 3, JsonDecodeFailed = 4, InternalServerError = 5, BadRequest = 6, Unauthorized = 7, UnknownResponseCode = 8, Ok = 9, Created = 10, }, } local HTTP = game:GetService("HttpService") local logger = require(script.Parent.Logger) local baseUrl = (RunService:IsStudio() and "https" or http_api.protocol) .. "://" .. (RunService:IsStudio() and "sandbox-" or "") .. http_api.hostName .. "/" .. http_api.version local remoteConfigsBaseUrl = (RunService:IsStudio() and "https" or http_api.protocol) .. "://" .. (RunService:IsStudio() and "sandbox-" or "") .. http_api.hostName .. "/remote_configs/" .. http_api.remoteConfigsVersion local function getInitAnnotations(build, playerData, playerId) local initAnnotations = { ["user_id"] = tostring(playerId) .. playerData.CustomUserId, ["sdk_version"] = "roblox " .. version.SdkVersion, ["os_version"] = playerData.OS, ["platform"] = playerData.Platform, ["build"] = build, ["session_num"] = playerData.Sessions, ["random_salt"] = playerData.Sessions, } return initAnnotations end local function encode(payload, secretKey) --Validate if not secretKey then logger:w("Error encoding, invalid SecretKey") return end --Encode local payloadHmac = HashLib.hmac( HashLib.sha256, RunService:IsStudio() and "16813a12f718bc5c620f56944e1abc3ea13ccbac" or secretKey, payload, true ) return HashLib.base64_encode(payloadHmac) end local function processRequestResponse(response, requestId) local statusCode = response.StatusCode local body = response.Body if not body or #body == 0 then logger:d(requestId .. " request. failed. Might be no connection. Status code: " .. tostring(statusCode)) return http_api.EGAHTTPApiResponse.NoResponse end if statusCode == 200 then return http_api.EGAHTTPApiResponse.Ok elseif statusCode == 201 then return http_api.EGAHTTPApiResponse.Created elseif statusCode == 0 or statusCode == 401 then logger:d(requestId .. " request. 401 - Unauthorized.") return http_api.EGAHTTPApiResponse.Unauthorized elseif statusCode == 400 then logger:d(requestId .. " request. 400 - Bad Request.") return http_api.EGAHTTPApiResponse.BadRequest elseif statusCode == 500 then logger:d(requestId .. " request. 500 - Internal Server Error.") return http_api.EGAHTTPApiResponse.InternalServerError else return http_api.EGAHTTPApiResponse.UnknownResponseCode end end function http_api:initRequest(gameKey, secretKey, build, playerData, playerId) local url = remoteConfigsBaseUrl .. "/" .. http_api.initializeUrlPath .. "?game_key=" .. gameKey .. "&interval_seconds=0&configs_hash=" .. (playerData.ConfigsHash or "") if RunService:IsStudio() then url = baseUrl .. "/5c6bcb5402204249437fb5a7a80a4959/" .. self.initializeUrlPath end logger:d("Sending 'init' URL: " .. url) local payload = HTTP:JSONEncode(getInitAnnotations(build, playerData, playerId)) payload = payload:gsub('"country_code":"unknown"', '"country_code":null') local authorization = encode(payload, secretKey) logger:d("init payload: " .. payload) local res local success, err = pcall(function() res = HTTP:RequestAsync({ Url = url, Method = "POST", Headers = { ["Authorization"] = authorization, ["Content-Type"] = "application/json", }, Body = payload, }) end) if not success then logger:d("Failed Init Call. error: " .. err) return { statusCode = http_api.EGAHTTPApiResponse.UnknownResponseCode, body = nil, } end logger:d("init request content: " .. res.Body) local requestResponseEnum = processRequestResponse(res, "Init") -- if not 200 result if requestResponseEnum ~= http_api.EGAHTTPApiResponse.Ok and requestResponseEnum ~= http_api.EGAHTTPApiResponse.Created and requestResponseEnum ~= http_api.EGAHTTPApiResponse.BadRequest then logger:d( "Failed Init Call. URL: " .. url .. ", JSONString: " .. payload .. ", Authorization: " .. authorization ) return { statusCode = requestResponseEnum, body = nil, } end --Response local responseBody success = pcall(function() responseBody = HTTP:JSONDecode(res.Body) end) if not success then logger:d("Failed Init Call. Json decoding failed: " .. err) return { statusCode = http_api.EGAHTTPApiResponse.JsonDecodeFailed, body = nil, } end -- print reason if bad request if requestResponseEnum == http_api.EGAHTTPApiResponse.BadRequest then logger:d("Failed Init Call. Bad request. Response: " .. res.Body) return { statusCode = requestResponseEnum, body = nil, } end -- validate Init call values local validatedInitValues = validation:validateAndCleanInitRequestResponse( responseBody, requestResponseEnum == http_api.EGAHTTPApiResponse.Created ) if not validatedInitValues then return { statusCode = http_api.EGAHTTPApiResponse.BadResponse, body = nil, } end -- all ok return { statusCode = requestResponseEnum, body = responseBody, } end function http_api:sendEventsInArray(gameKey, secretKey, eventArray) if not eventArray or #eventArray == 0 then logger:d("sendEventsInArray called with missing eventArray") return end -- Generate URL local url = baseUrl .. "/" .. gameKey .. "/" .. self.eventsUrlPath if RunService:IsStudio() then url = baseUrl .. "/5c6bcb5402204249437fb5a7a80a4959/" .. self.eventsUrlPath end logger:d("Sending 'events' URL: " .. url) -- make JSON string from data local payload = HTTP:JSONEncode(eventArray) payload = payload:gsub('"country_code":"unknown"', '"country_code":null') local authorization = encode(payload, secretKey) local res local success, err = pcall(function() res = HTTP:RequestAsync({ Url = url, Method = "POST", Headers = { ["Authorization"] = authorization, ["Content-Type"] = "application/json", }, Body = payload, }) end) if not success then logger:d("Failed Events Call. error: " .. err) return { statusCode = http_api.EGAHTTPApiResponse.UnknownResponseCode, body = nil, } end logger:d("body: " .. res.Body) local requestResponseEnum = processRequestResponse(res, "Events") -- if not 200 result if requestResponseEnum ~= http_api.EGAHTTPApiResponse.Ok and requestResponseEnum ~= http_api.EGAHTTPApiResponse.Created and requestResponseEnum ~= http_api.EGAHTTPApiResponse.BadRequest then logger:d( "Failed Events Call. URL: " .. url .. ", JSONString: " .. payload .. ", Authorization: " .. authorization ) return { statusCode = requestResponseEnum, body = nil, } end local responseBody pcall(function() responseBody = HTTP:JSONDecode(res.Body) end) if not responseBody then logger:d("Failed Events Call. Json decoding failed") return { statusCode = http_api.EGAHTTPApiResponse.JsonDecodeFailed, body = nil, } end -- print reason if bad request if requestResponseEnum == http_api.EGAHTTPApiResponse.BadRequest then logger:d("Failed Events Call. Bad request. Response: " .. res.Body) return { statusCode = requestResponseEnum, body = nil, } end -- all ok return { statusCode = http_api.EGAHTTPApiResponse.Ok, body = responseBody, } end return http_api