123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411 |
- <roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
- <External>null</External>
- <External>nil</External>
- <Item class="ModuleScript" referent="RBXf309a3c5773d40eaae7007cb4478e182">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">GameAnalyticsSDK</string>
- <string name="ScriptGuid">{7431f9d9-7538-46e6-a8c6-45d71ee7e57a}</string>
- <ProtectedString name="Source"><![CDATA[local module = {}
- return module
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- <Item class="Script" referent="RBXa82cd927306847a28e2acd9083a050b2">
- <Properties>
- <bool name="Disabled">false</bool>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">GameAnalyticsServer</string>
- <string name="ScriptGuid">{4efacb9d-efec-45dc-892a-1ddedb9a9ae3}</string>
- <ProtectedString name="Source"><![CDATA[--[[
- NOTE: This script should be in game.ServerScriptService
- --]]
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local ServerStorage = game:GetService("ServerStorage")
- --Validate
- if not script:IsDescendantOf(game:GetService("ServerScriptService")) then
- error("GameAnalytics: Disabled server. GameAnalyticsServer has to be located in game.ServerScriptService.")
- return
- end
- -- if not ReplicatedStorage:FindFirstChild("GameAnalyticsSendMessage") then
- -- --Create
- -- local f = Instance.new("RemoteEvent")
- -- f.Name = "GameAnalyticsSendMessage"
- -- f.Parent = ReplicatedStorage
- -- end
- if not ReplicatedStorage:FindFirstChild("GameAnalyticsCommandCenter") then
- --Create
- local f = Instance.new("RemoteEvent")
- f.Name = "GameAnalyticsCommandCenter"
- f.Parent = ReplicatedStorage
- end
- if not ReplicatedStorage:FindFirstChild("OnPlayerReadyEvent") then
- --Create
- local f = Instance.new("BindableEvent")
- f.Name = "OnPlayerReadyEvent"
- f.Parent = ReplicatedStorage
- end
- --Modules
- local GameAnalytics = require(ServerStorage.GameAnalytics)
- local store = require(ServerStorage.GameAnalytics.Store)
- local state = require(ServerStorage.GameAnalytics.State)
- local LS = game:GetService("LogService")
- local MKT = game:GetService("MarketplaceService")
- local Players = game:GetService("Players")
- local ProductCache = {}
- local ONE_HOUR_IN_SECONDS = 3600
- local MaxErrorsPerHour = 10
- local ErrorDS = {}
- local errorCountCache = {}
- local errorCountCacheKeys = {}
- spawn(function()
- local currentHour = math.floor(os.time()/3600)
- ErrorDS = store:GetErrorDataStore(currentHour)
- while wait(ONE_HOUR_IN_SECONDS) do
- currentHour = math.floor(os.time()/3600)
- ErrorDS = store:GetErrorDataStore(currentHour)
- errorCountCache = {}
- errorCountCacheKeys = {}
- end
- end)
- spawn(function()
- while wait(store.AutoSaveData) do
- for _, key in pairs(errorCountCacheKeys) do
- local errorCount = errorCountCache[key]
- local step = errorCount.currentCount - errorCount.countInDS
- errorCountCache[key].countInDS = store:IncrementErrorCount(ErrorDS, key, step)
- errorCountCache[key].currentCount = errorCountCache[key].countInDS
- end
- end
- end)
- --Error Logging
- LS.MessageOut:Connect(function(message, messageType)
- --Validate
- if not state.ReportErrors then
- return
- end
- if messageType ~= Enum.MessageType.MessageError then
- return
- end
- local m = message
- if #m > 8192 then
- m = string.sub(m, 1, 8192)
- end
- local key = m
- if #key > 50 then
- key = string.sub(key, 1, 50)
- end
- if errorCountCache[key] == nil then
- errorCountCacheKeys[#errorCountCacheKeys + 1] = key
- errorCountCache[key] = {}
- errorCountCache[key].countInDS = 0
- errorCountCache[key].currentCount = 0
- end
- -- don't report error if limit has been exceeded
- if errorCountCache[key].currentCount > MaxErrorsPerHour then
- return
- end
- --Report (use nil for playerId as real player id is not available)
- GameAnalytics:addErrorEvent(nil, {
- severity = GameAnalytics.EGAErrorSeverity.error,
- message = m
- })
- -- increment error count
- errorCountCache[key].currentCount = errorCountCache[key].currentCount + 1
- end)
- --Record Gamepasses. NOTE: This doesn't record gamepass purchases if a player buys it from the website
- MKT.PromptGamePassPurchaseFinished:Connect(function(Player, ID, Purchased)
- --Validate
- if not state.AutomaticSendBusinessEvents then
- return
- end
- --Validate
- if not Purchased then return end
- --Variables
- local GamepassInfo = ProductCache[ID]
- --Cache
- if not GamepassInfo then
- --Get
- GamepassInfo = MKT:GetProductInfo(ID, Enum.InfoType.GamePass)
- ProductCache[ID] = GamepassInfo
- end
- GameAnalytics:addBusinessEvent(Player.UserId, {
- amount = GamepassInfo.PriceInRobux,
- itemType = "Gamepass",
- itemId = GameAnalytics:filterForBusinessEvent(GamepassInfo.Name)
- })
- end)
- -- Fire for players already in game
- for _, Player in pairs(Players:GetPlayers()) do
- GameAnalytics:PlayerJoined(Player)
- end
- -- New Players
- Players.PlayerAdded:Connect(function(Player)
- local joinData = Player:GetJoinData()
- local teleportData = joinData.TeleportData
- local gaData = nil
- if teleportData then
- gaData = teleportData.gameanalyticsData and teleportData.gameanalyticsData[tostring(Player.UserId)]
- end
- GameAnalytics:PlayerJoined(Player, gaData)
- end)
- -- Players leaving
- Players.PlayerRemoving:Connect(function(Player)
- GameAnalytics:PlayerRemoved(Player)
- end)
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBXa82cd927306847a28e2acd9083a990b2">
- <Properties>
- <bool name="Disabled">false</bool>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">Postie</string>
- <string name="ScriptGuid">{4efacb9d-efec-45dc-892a-1ddedb9a9ae9}</string>
- <ProtectedString name="Source"><![CDATA[--[[
- Postie - An elegant alternative to RemoteFunctions with a timeout
- By Dandystan
- INTERFACE:
- Function bool, Tuple Postie.InvokeClient(string id, Instance<Player> player, number timeout, Tuple args) [server_side] [yields]
- Invoke player with arguments args. Invocation identified by id. Yield until timeout (given in seconds) is reached
- and return false, or a signal is received back from the client and return true plus any values received from the
- client.
- Function bool, Tuple Postie.InvokeServer(string id, number timeout, Tuple args) [client_side] [yields]
- Invoke the server with arguments args. Invocation identified by id. Yield until timeout (given in seconds) is
- reached and return false, or a signal is received back from the server and return true plus any values received
- from the server.
- Function void Postie.SetCallback(string id, func callback)
- Set the callback that is invoked when an invocation identified by id is sent. Arguments passed with the invocation
- are passed to the callback. If on the server, the player who invoked is implicitly received as the first argument.
- Function func? Postie.GetCallback(string id)
- Return the callback associated with id.
- EXAMPLE 1 - server to client:
- Server:
- local postie = require(postieObj)
- -- arbritary func to be called whenever
- local function getTrampolinesOnScreen(player)
- -- get objects on screen from player
- local isSuccessful, trampolines = postie.InvokeClient("RequestObjectsOnScreen", player, 5, "Trampolines")
- -- check for timeout
- if isSuccessful then
- -- validate returned data type for security purposes
- if typeof(trampolines) == "number" then
- return true, trampolines
- end
- end
- return false
- end
- Client:
- local postie = require(postieObj)
- postie.SetCallback("RequestObjectsOnScreen", function(objectType)
- return objectsOnScreen[objectType]
- end)
- EXAMPLE 2 - client to server:
- Server:
- local postie = require(postieObj)
- postie.SetCallback("GetCoins", function(player)
- return playerCoins[player]
- end)
- Client:
- local postie = require(postieObj)
- local function getCoins()
- return postie.InvokeServer("GetCoins", 5)
- end
- --]]
- -- services:
- local runService = game:GetService("RunService")
- if not script:FindFirstChild("Sent") then
- --Create
- local f = Instance.new("RemoteEvent")
- f.Name = "Sent"
- f.Parent = script
- end
- if not script:FindFirstChild("Received") then
- --Create
- local f = Instance.new("RemoteEvent")
- f.Name = "Received"
- f.Parent = script
- end
- -- variables:
- local sent = script.Sent
- local received = script.Received
- local isServer = runService:IsServer()
- local idCallbacks = {}
- local listeners = {}
- local signalVersion = 1
- -- Postie:
- local postie = {}
- function postie.InvokeClient(id, player, timeout, ...)
- assert(isServer, "Postie.InvokeClient can only be called from the server")
- assert(typeof(id) == "string", "bad argument #1 to Postie.InvokeClient, expects string")
- assert(typeof(player) == "Instance" and player:IsA("Player"), "bad argument #2 to Postie.InvokeClient, expects Instance<Player>")
- assert(typeof(timeout) == "number", "bad argument #3 to Postie.InvokeClient, expects number")
- -- define variables
- local thread = coroutine.running()
- local isResumed = false
- local pos = #listeners + 1
- -- get signal version
- local version = signalVersion
- signalVersion = signalVersion + 1
- -- await signal from client
- listeners[pos] = function(playerWhoFired, versionOfSignal, ...)
- if not (playerWhoFired == player and versionOfSignal == version) then return end
- isResumed = true
- table.remove(listeners, pos)
- coroutine.resume(thread, true, ...)
- return true
- end
- -- await timeout
- coroutine.wrap(function()
- wait(timeout)
- if isResumed then return end
- table.remove(listeners, pos)
- coroutine.resume(thread, false)
- end)()
- -- send signal
- sent:FireClient(player, id, version, ...)
- return coroutine.yield()
- end
- function postie.InvokeServer(id, timeout, ...)
- assert(not isServer, "Postie.InvokeServer can only be called from the client")
- assert(typeof(id) == "string", "bad argument #1 to Postie.InvokeServer, expects string")
- assert(typeof(timeout) == "number", "bad argument #2 to Postie.InvokeServer, expects number")
- -- define variables
- local thread = coroutine.running()
- local isResumed = false
- local pos = #listeners + 1
- -- get signal version
- local version = signalVersion
- signalVersion = signalVersion + 1
- -- await signal from client
- listeners[pos] = function(versionOfSignal, ...)
- if versionOfSignal ~= id then return end
- isResumed = true
- table.remove(listeners, pos)
- coroutine.resume(thread, true, ...)
- return true
- end
- -- await timeout
- coroutine.wrap(function()
- wait(timeout)
- if isResumed then return end
- table.remove(listeners, pos)
- coroutine.resume(thread, false)
- end)()
- -- send signal
- sent:FireServer(id, version, ...)
- return coroutine.yield()
- end
- function postie.SetCallback(id, callback)
- assert(typeof(id) == "string", "bad argument #1 to Postie.SetCallback, expects string")
- assert(typeof(callback) == "function", "bad argument #2 to Postie.SetCallback, expects func")
- idCallbacks[id] = callback
- end
- function postie.GetCallback(id)
- assert(typeof(id) == "string", "bad argument #1 to Postie.GetCallback, expects string")
- return idCallbacks[id]
- end
- -- main:
- -- handle signals
- if isServer then
- -- handle received
- received.OnServerEvent:Connect(function(...)
- for _, listener in ipairs(listeners) do
- if listener(...) then return end
- end
- end)
- -- handle sent
- sent.OnServerEvent:Connect(function(player, id, version, ...)
- local callback = idCallbacks[id]
- received:FireClient(player, version, callback and callback(player, ...))
- end)
- else
- -- handle received
- received.OnClientEvent:Connect(function(...)
- for _, listener in ipairs(listeners) do
- if listener(...) then return end
- end
- end)
- -- handle sent
- sent.OnClientEvent:Connect(function(id, version, ...)
- local callback = idCallbacks[id]
- received:FireServer(version, callback and callback(...))
- end)
- end
- return postie
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="Script" referent="RBXa82cd927306847a28e2acd9083a150a5">
- <Properties>
- <bool name="Disabled">false</bool>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">GameAnalyticsServerInitUsingSettings</string>
- <string name="ScriptGuid">{4efacb9d-efec-45dc-892a-1ddedb9a9ae3}</string>
- <ProtectedString name="Source"><![CDATA[--[[
- NOTE: This script should be in game.ServerScriptService
- --]]
- local ServerStorage = game:GetService("ServerStorage")
- --Validate
- if not script:IsDescendantOf(game:GetService("ServerScriptService")) then
- error("GameAnalytics: GameAnalyticsServerInitUsingSettings has to be located in game.ServerScriptService.")
- return
- end
- --Modules
- local GameAnalytics = require(ServerStorage.GameAnalytics)
- local Settings = require(ServerStorage.GameAnalytics.Settings)
- local Players = game:GetService("Players")
- if Settings.EnableInfoLog then
- GameAnalytics:setEnabledInfoLog(Settings.EnableInfoLog)
- end
- if Settings.EnableVerboseLog then
- GameAnalytics:setEnabledVerboseLog(Settings.EnableVerboseLog)
- end
- if #Settings.AvailableCustomDimensions01 > 0 then
- GameAnalytics:configureAvailableCustomDimensions01(Settings.AvailableCustomDimensions01)
- end
- if #Settings.AvailableCustomDimensions02 > 0 then
- GameAnalytics:configureAvailableCustomDimensions02(Settings.AvailableCustomDimensions02)
- end
- if #Settings.AvailableCustomDimensions03 > 0 then
- GameAnalytics:configureAvailableCustomDimensions03(Settings.AvailableCustomDimensions03)
- end
- if #Settings.AvailableResourceCurrencies > 0 then
- GameAnalytics:configureAvailableResourceCurrencies(Settings.AvailableResourceCurrencies)
- end
- if #Settings.AvailableResourceItemTypes > 0 then
- GameAnalytics:configureAvailableResourceItemTypes(Settings.AvailableResourceItemTypes)
- end
- if #Settings.Build > 0 then
- GameAnalytics:configureBuild(Settings.Build)
- end
- GameAnalytics:initialize({
- gameKey = Settings.GameKey,
- secretKey = Settings.SecretKey
- })
- -- Fire for players already in game
- for _, Player in pairs(Players:GetPlayers()) do
- GameAnalytics:PlayerJoined(Player)
- end
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="Script" referent="RBXb7b6780e3b3b47999e926fea38fa454f">
- <Properties>
- <bool name="Disabled">false</bool>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">INSTALL</string>
- <string name="ScriptGuid">{4efacb9d-efec-45dc-892a-1ddedb9a2ae1}</string>
- <ProtectedString name="Source"><![CDATA[--[[
- Thanks for using the official GameAnalytics Roblox SDK module (based on gillern's GameAnalytics module)!
- To start using the plugin, follow these steps:
- 1. Create an account on gameanalytics.com and create a game
- 2. Get your game key and secret key
- 3. Add the keys to 'GameKey' and 'SecretKey' under GameAnalytics.Settings script
- 4. Move the 'GameAnalytics' script (together with its children) under 'ServerStorage'
- 5. Move the 'GameAnalyticsServer' script under 'ServerScriptService'
- 6. Move the 'GameAnalyticsServerInitUsingSettings' script under 'ServerScriptService' (optional, NOT needed if you want to programmatically initialize the SDK from your own script)
- 7. Move the 'GameAnalyticsClient' script under 'StarterPlayer/StarerPlayerScripts'
- 8. Move the 'Postie' script under 'ReplicatedStorage'
- 9. You're ready!
- NOTE: The provided rojo.json in the repository is set up to perform steps 4, 5 and 6 automatically through the Rojo workflow.
- For details on how to send events and more with the SDK go to this page: https://gameanalytics.com/docs/item/roblox-sdk
- Resources:
- Roblox SDK Docs: https://gameanalytics.com/docs/item/roblox-sdk
- Github: https://github.com/GameAnalytics/GA-SDK-ROBLOX
- Account sign up: https://go.gameanalytics.com/signup
- Support: https://gameanalytics.com/contact
- --]]
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="LocalScript" referent="RBX5ddcab423f9e430186a33c70b93fc965">
- <Properties>
- <bool name="Disabled">false</bool>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">GameAnalyticsClient</string>
- <string name="ScriptGuid">{7cd38da4-97aa-470d-a875-cace696c90ba}</string>
- <ProtectedString name="Source"><![CDATA[--Variables
- --local GameAnalyticsSendMessage = game:GetService("ReplicatedStorage"):WaitForChild("GameAnalyticsSendMessage")
- --Services
- local GS = game:GetService("GuiService")
- local UIS = game:GetService("UserInputService")
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local Postie = require(ReplicatedStorage.Postie)
- --Functions
- local function getPlatform()
- if (GS:IsTenFootInterface()) then
- return "Console"
- elseif (UIS.TouchEnabled and not UIS.MouseEnabled) then
- return "Mobile"
- else
- return "Desktop"
- end
- end
- --Filtering
- Postie.SetCallback("getPlatform", getPlatform);
- -- debug stuff
- --GameAnalyticsSendMessage.OnClientEvent:Connect(function(chatProperties)
- -- game:GetService("StarterGui"):SetCore("ChatMakeSystemMessage", chatProperties)
- --end)
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBXf8708688395b460f9941544945f03ab9">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">GameAnalytics</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local GAResourceFlowType = require(script.GAResourceFlowType)
- local GAProgressionStatus = require(script.GAProgressionStatus)
- local GAErrorSeverity = require(script.GAErrorSeverity)
- local ga = {
- EGAResourceFlowType = GAResourceFlowType,
- EGAProgressionStatus = GAProgressionStatus,
- EGAErrorSeverity = GAErrorSeverity
- }
- local logger = require(script.Logger)
- local threading = require(script.Threading)
- local state = require(script.State)
- local validation = require(script.Validation)
- local store = require(script.Store)
- local events = require(script.Events)
- local Players = game:GetService("Players")
- local MKT = game:GetService("MarketplaceService")
- local RunService = game:GetService("RunService")
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local Postie = require(ReplicatedStorage.Postie)
- local ProductCache = {}
- local OnPlayerReadyEvent
- -- local functions
- local function isSdkReady(options)
- local playerId = options["playerId"] or nil
- local needsInitialized = options["needsInitialized"] or true
- local shouldWarn = options["shouldWarn"] or false
- local message = options["message"] or ""
- -- Is SDK initialized
- if needsInitialized and not state.Initialized then
- if shouldWarn then
- logger:w(message .. " SDK is not initialized")
- end
- return false
- end
- -- Is SDK enabled
- if needsInitialized and playerId and not state:isEnabled(playerId) then
- if shouldWarn then
- logger:w(message .. " SDK is disabled")
- end
- return false
- end
- -- Is session started
- if needsInitialized and playerId and not state:sessionIsStarted(playerId) then
- if shouldWarn then
- logger:w(message .. " Session has not started yet")
- end
- return false
- end
- return true
- end
- function ga:configureAvailableCustomDimensions01(customDimensions)
- threading:performTaskOnGAThread(function()
- if isSdkReady({needsInitialized=true, shouldWarn=false}) then
- logger:w("Available custom dimensions must be set before SDK is initialized")
- return
- end
- state:setAvailableCustomDimensions01(customDimensions)
- end)
- end
- function ga:configureAvailableCustomDimensions02(customDimensions)
- threading:performTaskOnGAThread(function()
- if isSdkReady({needsInitialized=true, shouldWarn=false}) then
- logger:w("Available custom dimensions must be set before SDK is initialized")
- return
- end
- state:setAvailableCustomDimensions02(customDimensions)
- end)
- end
- function ga:configureAvailableCustomDimensions03(customDimensions)
- threading:performTaskOnGAThread(function()
- if isSdkReady({needsInitialized=true, shouldWarn=false}) then
- logger:w("Available custom dimensions must be set before SDK is initialized")
- return
- end
- state:setAvailableCustomDimensions03(customDimensions)
- end)
- end
- function ga:configureAvailableResourceCurrencies(resourceCurrencies)
- threading:performTaskOnGAThread(function()
- if isSdkReady({needsInitialized=true, shouldWarn=false}) then
- logger:w("Available resource currencies must be set before SDK is initialized")
- return
- end
- events:setAvailableResourceCurrencies(resourceCurrencies)
- end)
- end
- function ga:configureAvailableResourceItemTypes(resourceItemTypes)
- threading:performTaskOnGAThread(function()
- if isSdkReady({needsInitialized=true, shouldWarn=false}) then
- logger:w("Available resource item types must be set before SDK is initialized")
- return
- end
- events:setAvailableResourceItemTypes(resourceItemTypes)
- end)
- end
- function ga:configureBuild(build)
- threading:performTaskOnGAThread(function()
- if isSdkReady({needsInitialized=true, shouldWarn=false}) then
- logger:w("Build version must be set before SDK is initialized.")
- return
- end
- events:setBuild(build)
- end)
- end
- function ga:initialize(options)
- threading:performTaskOnGAThread(function()
- if isSdkReady({needsInitialized=true, shouldWarn=false}) then
- logger:w("SDK already initialized. Can only be called once.")
- return
- end
- local gameKey = options["gameKey"]
- local secretKey = options["secretKey"]
- if not validation:validateKeys(gameKey, secretKey) then
- logger:w("SDK failed initialize. Game key or secret key is invalid. Can only contain characters A-z 0-9, gameKey is 32 length, secretKey is 40 length. Failed keys - gameKey: " .. gameKey .. ", secretKey: " .. secretKey)
- return
- end
- events.GameKey = gameKey
- events.SecretKey = secretKey
- state.Initialized = true
- events:processEventQueue()
- end)
- end
- function ga:startNewSession(player, teleportData)
- threading:performTaskOnGAThread(function()
- if not state:isEventSubmissionEnabled() then
- return
- end
- if not state.Initialized then
- logger:w("Cannot start new session. SDK is not initialized yet.")
- return
- end
- state:startNewSession(player, teleportData)
- end)
- end
- function ga:endSession(playerId)
- threading:performTaskOnGAThread(function()
- if not state:isEventSubmissionEnabled() then
- return
- end
- state:endSession(playerId)
- end)
- end
- function ga:filterForBusinessEvent(text)
- return string.gsub(text, "[^A-Za-z0-9%s%-_%.%(%)!%?]", "")
- end
- function ga:addBusinessEvent(playerId, options)
- threading:performTaskOnGAThread(function()
- if not state:isEventSubmissionEnabled() then
- return
- end
- if not isSdkReady({playerId=playerId, needsInitialized=true, shouldWarn=true, message="Could not add business event"}) then
- return
- end
- -- Send to events
- local amount = options["amount"] or 0
- local itemType = options["itemType"] or ""
- local itemId = options["itemId"] or ""
- local cartType = options["cartType"] or ""
- local USDSpent = math.floor((amount * 0.7) * 0.35)
- events:addBusinessEvent(playerId, "USD", USDSpent, itemType, itemId, cartType)
- end)
- end
- function ga:addResourceEvent(playerId, options)
- threading:performTaskOnGAThread(function()
- if not state:isEventSubmissionEnabled() then
- return
- end
- if not isSdkReady({playerId=playerId, needsInitialized=true, shouldWarn=true, message="Could not add resource event"}) then
- return
- end
- -- Send to events
- local flowType = options["flowType"] or 0
- local currency = options["currency"] or ""
- local amount = options["amount"] or 0
- local itemType = options["itemType"] or ""
- local itemId = options["itemId"] or ""
- events:addResourceEvent(playerId, flowType, currency, amount, itemType, itemId)
- end)
- end
- function ga:addProgressionEvent(playerId, options)
- threading:performTaskOnGAThread(function()
- if not state:isEventSubmissionEnabled() then
- return
- end
- if not isSdkReady({playerId=playerId, needsInitialized=true, shouldWarn=true, message="Could not add progression event"}) then
- return
- end
- -- Send to events
- local progressionStatus = options["progressionStatus"] or 0
- local progression01 = options["progression01"] or ""
- local progression02 = options["progression02"] or nil
- local progression03 = options["progression03"] or nil
- local score = options["score"] or nil
- events:addProgressionEvent(playerId, progressionStatus, progression01, progression02, progression03, score)
- end)
- end
- function ga:addDesignEvent(playerId, options)
- threading:performTaskOnGAThread(function()
- if not state:isEventSubmissionEnabled() then
- return
- end
- if not isSdkReady({playerId=playerId, needsInitialized=true, shouldWarn=true, message="Could not add design event"}) then
- return
- end
- -- Send to events
- local eventId = options["eventId"] or ""
- local value = options["value"] or nil
- events:addDesignEvent(playerId, eventId, value)
- end)
- end
- function ga:addErrorEvent(playerId, options)
- threading:performTaskOnGAThread(function()
- if not state:isEventSubmissionEnabled() then
- return
- end
- if not isSdkReady({playerId=playerId, needsInitialized=true, shouldWarn=true, message="Could not add error event"}) then
- return
- end
- -- Send to events
- local severity = options["severity"] or 0
- local message = options["message"] or ""
- events:addErrorEvent(playerId, severity, message)
- end)
- end
- function ga:setEnabledDebugLog(flag)
- threading:performTaskOnGAThread(function()
- if RunService:IsStudio() then
- if flag then
- logger:setDebugLog(flag)
- logger:i("Debug logging enabled")
- else
- logger:i("Debug logging disabled")
- logger:setDebugLog(flag)
- end
- else
- logger:i("setEnabledDebugLog can only be used in studio")
- end
- end)
- end
- function ga:setEnabledInfoLog(flag)
- threading:performTaskOnGAThread(function()
- if flag then
- logger:setInfoLog(flag)
- logger:i("Info logging enabled")
- else
- logger:i("Info logging disabled")
- logger:setInfoLog(flag)
- end
- end)
- end
- function ga:setEnabledVerboseLog(flag)
- threading:performTaskOnGAThread(function()
- if flag then
- logger:setVerboseLog(flag)
- logger:ii("Verbose logging enabled")
- else
- logger:ii("Verbose logging disabled")
- logger:setVerboseLog(flag)
- end
- end)
- end
- function ga:setEnabledEventSubmission(flag)
- threading:performTaskOnGAThread(function()
- if flag then
- state:setEventSubmission(flag)
- logger:i("Event submission enabled")
- else
- logger:i("Event submission disabled")
- state:setEventSubmission(flag)
- end
- end)
- end
- function ga:setCustomDimension01(playerId, dimension)
- threading:performTaskOnGAThread(function()
- if not validation:validateDimension(state._availableCustomDimensions01, dimension) then
- logger:w("Could not set custom01 dimension value to '" .. dimension .. "'. Value not found in available custom01 dimension values")
- return
- end
- if not isSdkReady({playerId=playerId, needsInitialized=true, shouldWarn=true, message="Could not set custom01 dimension"}) then
- return
- end
- state:setCustomDimension01(playerId, dimension)
- end)
- end
- function ga:setCustomDimension02(playerId, dimension)
- threading:performTaskOnGAThread(function()
- if not validation:validateDimension(state._availableCustomDimensions02, dimension) then
- logger:w("Could not set custom02 dimension value to '" .. dimension .. "'. Value not found in available custom02 dimension values")
- return
- end
- if not isSdkReady({playerId=playerId, needsInitialized=true, shouldWarn=true, message="Could not set custom02 dimension"}) then
- return
- end
- state:setCustomDimension02(playerId, dimension)
- end)
- end
- function ga:setCustomDimension03(playerId, dimension)
- threading:performTaskOnGAThread(function()
- if not validation:validateDimension(state._availableCustomDimensions03, dimension) then
- logger:w("Could not set custom03 dimension value to '" .. dimension .. "'. Value not found in available custom03 dimension values")
- return
- end
- if not isSdkReady({playerId=playerId, needsInitialized=true, shouldWarn=true, message="Could not set custom03 dimension"}) then
- return
- end
- state:setCustomDimension03(playerId, dimension)
- end)
- end
- function ga:setEnabledReportErrors(flag)
- threading:performTaskOnGAThread(function()
- state.ReportErrors = flag
- end)
- end
- function ga:setEnabledAutomaticSendBusinessEvents(flag)
- threading:performTaskOnGAThread(function()
- state.AutomaticSendBusinessEvents = flag
- end)
- end
- function ga:addGameAnalyticsTeleportData(playerIds, teleportData)
- local gameAnalyticsTeleportData = {}
- for index = 1, #playerIds do
- local playerId = playerIds[index]
- local PlayerData = store.PlayerCache[playerId]
- PlayerData.PlayerTeleporting = true
- local data = {
- ["SessionID"] = PlayerData.SessionID,
- ["Sessions"] = PlayerData.Sessions,
- ["SessionStart"] = PlayerData.SessionStart
- }
- gameAnalyticsTeleportData[tostring(playerId)] = data
- end
- teleportData["gameanalyticsData"] = gameAnalyticsTeleportData
- return teleportData
- end
- function ga:getCommandCenterValueAsString(playerId, options)
- local key = options["key"] or ""
- local defaultValue = options["defaultValue"] or nil
- return state:getConfigurationStringValue(playerId, key, defaultValue)
- end
- function ga:isCommandCenterReady(playerId)
- return state:isCommandCenterReady(playerId)
- end
- function ga:getConfigurationsContentAsString(playerId)
- return state:getConfigurationsContentAsString(playerId)
- end
- function ga:PlayerJoined(Player, teleportData)
- if store.PlayerCache[Player.UserId] then
- return
- end
- --Variables
- local PlayerData = store:GetPlayerData(Player)
- local PlayerPlatform = "unknown"
- local isSuccessful, platform = Postie.InvokeClient("getPlatform", Player, 5)
- if isSuccessful then
- PlayerPlatform = platform
- end
- --Fill Data
- for key, value in pairs(store.BasePlayerData) do
- PlayerData[key] = PlayerData[key] or value
- end
- store.PlayerCache[Player.UserId] = PlayerData
- PlayerData.Platform = (PlayerPlatform == "Console" and "uwp_console") or (PlayerPlatform == "Mobile" and "uwp_mobile") or (PlayerPlatform == "Desktop" and "uwp_desktop") or ("uwp_desktop")
- PlayerData.OS = PlayerData.Platform .. " 0.0.0"
- ga:startNewSession(Player, teleportData)
- OnPlayerReadyEvent = OnPlayerReadyEvent or game:GetService("ReplicatedStorage"):WaitForChild("OnPlayerReadyEvent")
- OnPlayerReadyEvent:Fire(Player)
- --Autosave
- spawn(function()
- --Loop
- while true do
- --Delay
- wait(store.AutoSaveData)
- --Validate
- if (not Player) or (Player.Parent ~= Players) then return end
- --Save
- store:SavePlayerData(Player)
- end
- end)
- end
- function ga:PlayerRemoved(Player)
- --Save
- store:SavePlayerData(Player)
- local PlayerData = store.PlayerCache[Player.UserId]
- if PlayerData and not PlayerData.PlayerTeleporting then
- ga:endSession(Player.UserId)
- end
- end
- function ga:isPlayerReady(playerId)
- if store.PlayerCache[playerId] then
- return true
- else
- return false
- end
- end
- function ga:ProcessReceiptCallback(Info)
- --Variables
- local ProductInfo = ProductCache[Info.ProductId]
- --Cache
- if not ProductInfo then
- --Get
- ProductInfo = MKT:GetProductInfo(Info.ProductId, Enum.InfoType.Product)
- ProductCache[Info.ProductId] = ProductInfo
- end
- ga:addBusinessEvent(Info.PlayerId, {
- amount = Info.CurrencySpent,
- itemType = "DeveloperProduct",
- itemId = ga:filterForBusinessEvent(ProductInfo.Name)
- })
- end
- return ga
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- <Item class="ModuleScript" referent="RBX34831ce54bf7450a8e97b4ff9458caa6">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">Settings</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local settings = {
- EnableInfoLog = true,
- EnableVerboseLog = false,
- AutomaticSendBusinessEvents = true,
- ReportErrors = true,
- Build = "0.1",
- AvailableCustomDimensions01 = {},
- AvailableCustomDimensions02 = {},
- AvailableCustomDimensions03 = {},
- AvailableResourceCurrencies = {},
- AvailableResourceItemTypes = {},
- GameKey = "",
- SecretKey = ""
- }
- return settings
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBXd615e5db16034cae976316f3d4fea9e0">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">HttpApi</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local RunService = game:GetService("RunService")
- local validation = require(script.Parent.Validation)
- local version = require(script.Parent.Version)
- local sha256 = require(script.sha256)
- local hmac = require(script.hmac)
- local base64 = require(script.base64)
- local http_api = {
- protocol = "https",
- hostName = "api.gameanalytics.com",
- version = "v2",
- 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
- }
- }
- local HTTP = game:GetService("HttpService")
- local logger = require(script.Parent.Logger)
- local baseUrl = (RunService:IsStudio() and "http" or http_api.protocol) .. "://" .. (RunService:IsStudio() and "sandbox-" or "") .. http_api.hostName .. "/" .. http_api.version
- local Encoding = {}
- local function getInitAnnotations(playerData, playerId)
- local initAnnotations = {
- ["user_id"] = tostring(playerId),
- ["sdk_version"] = "roblox " .. version.SdkVersion,
- ["os_version"] = playerData.OS,
- ["platform"] = playerData.Platform
- }
- return initAnnotations
- end
- local function encode(payload, secretKey)
- --Validate
- if not secretKey then logger:w("Error encoding, invalid SecretKey") return end
- --Encode
- local payloadHmac = hmac(
- RunService:IsStudio() and "16813a12f718bc5c620f56944e1abc3ea13ccbac" or secretKey, -- key
- payload, -- message
- sha256, -- hashing function
- 64, -- block size
- nil, -- output size
- true -- output as binary data
- )
- return 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 == 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, playerData, playerId)
- local url = "https://rubick.gameanalytics.com/v2/command_center?game_key=" .. gameKey .. "&interval_seconds=1000000"
- if RunService:IsStudio() then
- url = baseUrl .. "/5c6bcb5402204249437fb5a7a80a4959/" .. self.initializeUrlPath
- end
- logger:d("Sending 'init' URL: " .. url)
- local payload = HTTP:JSONEncode(getInitAnnotations(playerData, playerId))
- local authorization = encode(payload, secretKey)
- local res
- local success, err = pcall(function()
- res = HTTP:RequestAsync({
- Url = url,
- Method = "POST",
- Headers = {
- ["Authorization"] = authorization
- },
- 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.BadRequest then
- logger:d("Failed Init Call. URL: " .. url .. ", JSONString: " .. payload .. ", Authorization: " .. authorization)
- return {
- statusCode = requestResponseEnum,
- body = nil
- }
- end
- --Response
- local responseBody
- success, _ = ypcall(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)
- if not validatedInitValues then
- return {
- statusCode = http_api.EGAHTTPApiResponse.BadResponse,
- body = nil
- }
- end
- -- all ok
- return {
- statusCode = http_api.EGAHTTPApiResponse.Ok,
- 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)
- local authorization = encode(payload, secretKey)
- local res
- local success, err = pcall(function()
- res = HTTP:RequestAsync({
- Url = url,
- Method = "POST",
- Headers = {
- ["Authorization"] = authorization
- },
- 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.BadRequest then
- logger:d("Failed Events Call. URL: " .. url .. ", JSONString: " .. payload .. ", Authorization: " .. authorization)
- return {
- statusCode = requestResponseEnum,
- body = nil
- }
- end
- local responseBody
- ypcall(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
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- <Item class="ModuleScript" referent="RBX4b14a91351d1404f89ac5d7683accce5">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">base64</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local ASSERTIONS_ENABLED = true -- Whether to run several checks when the module is first loaded
- local CHAR_SET = { [0] =
- "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
- "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
- "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
- "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
- "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/",
- }
- local REVERSE_CHAR_SET = {
- [65] = 0, [66] = 1, [67] = 2, [68] = 3, [69] = 4, [70] = 5, [71] = 6, [72] = 7, [73] = 8, [74] = 9, [75] = 10, [76] = 11, [77] = 12, [78] = 13,
- [79] = 14, [80] = 15, [81] = 16, [82] = 17, [83] = 18, [84] = 19, [85] = 20, [86] = 21, [87] = 22, [88] = 23, [89] = 24, [90] = 25, [97] = 26,
- [98] = 27, [99] = 28, [100] = 29, [101] = 30, [102] = 31, [103] = 32, [104] = 33, [105] = 34, [106] = 35, [107] = 36, [108] = 37, [109] = 38, [110] = 39,
- [111] = 40, [112] = 41, [113] = 42, [114] = 43, [115] = 44, [116] = 45, [117] = 46, [118] = 47, [119] = 48, [120] = 49, [121] = 50, [122] = 51, [48] = 52,
- [49] = 53, [50] = 54, [51] = 55, [52] = 56, [53] = 57, [54] = 58, [55] = 59, [56] = 60, [57] = 61, [43] = 62, [47] = 63,
- }
- --- Packs three 8-bit integers into one unsigned 24-bit integer.
- local function packUint24FromOctets(a, b, c)
- return bit32.lshift(a, 16)+bit32.lshift(b, 8)+c
- end
- --- Packs four 6-bit integers into one unsigned 24-bit integer
- local function packUint24FromSextets(a, b, c, d)
- return bit32.lshift(a, 18)+bit32.lshift(b, 12)+bit32.lshift(c, 6)+d
- end
- --- Encodes `input` from plaintext into base64, optionally omitting padding
- local function encodeBase64(input, omitPadding)
- local output = {}
- local padding = #input%3
- local c = 1
- for i = 1, #input, 3 do
- local b1, b2, b3 = string.byte(input, i, i+2)
- local packed = packUint24FromOctets(b1, b2 or 0, b3 or 0)
- output[c] = CHAR_SET[bit32.extract(packed, 18, 6)]
- output[c+1] = CHAR_SET[bit32.extract(packed, 12, 6)]
- if b2 then
- output[c+2] = CHAR_SET[bit32.extract(packed, 6, 6)]
- if b3 then
- output[c+3] = CHAR_SET[bit32.extract(packed, 0, 6)]
- end
- end
- c = c+4
- end
- if not omitPadding then
- if padding == 2 then
- output[c-1] = "="
- elseif padding == 1 then
- output[c-2] = "=="
- end
- end
- return table.concat(output)
- end
- --- Decodes `input` from base64 to plaintext.
- local function decodeBase64(input)
- assert(not (string.find(input, "[^%w+/=]")), "input contains invalid characters")
- local output = {}
- local c = 1
- for i = 1, #input, 4 do
- local b1, b2, b3, b4 = string.byte(input, i, i+3)
- b1 = REVERSE_CHAR_SET[b1]
- b2 = REVERSE_CHAR_SET[b2]
- b3 = REVERSE_CHAR_SET[b3]
- b4 = REVERSE_CHAR_SET[b4]
- local packed = packUint24FromSextets(b1, b2, b3 or 0, b4 or 0)
- output[c] = string.char(bit32.extract(packed, 16, 8))
- if not b3 then
- break
- end
- output[c+1] = string.char(bit32.extract(packed, 8, 8))
- if not b4 then
- break
- end
- output[c+2] = string.char(bit32.extract(packed, 0, 8))
- c = c+3
- end
- return table.concat(output)
- end
- if ASSERTIONS_ENABLED then
- assert(packUint24FromOctets(77, 97, 110) == 5071214, "(Base64) packUint24FromOctets check")
- assert(packUint24FromSextets(19, 22, 5, 46) == 5071214, "(Base64) packUint24FromSextets check")
- assert(encodeBase64("Man") == "TWFu", "(Base64) Man failed to encode into TWFu")
- assert(encodeBase64("Ma") == "TWE=", "(Base64) Ma failed to encode into TWE=")
- assert(encodeBase64("M") == "TQ==", "(Base64) M failed to encode into TQ==")
- assert(encodeBase64("Baby shark") == "QmFieSBzaGFyaw==", "(Base64) Baby shark failed to encode into QmFieSBzaGFyaw==")
- assert(encodeBase64("Almost heaven, West Virginia\nBlue Ridge Mountains, Shenandoah River\nLife is old there, older than the trees\nYounger than the mountains, blowing like a breeze\nCountry roads, take me home\nTo the place I belong\nWest Virginia, mountain mama\nTake me home, country roads\nAll my memories gather round her\nMiner's lady, stranger to blue water\nDark and dusty, painted on the sky\nMisty taste of moonshine, teardrop in my eye\nCountry roads, take me home\nTo the place I belong\nWest Virginia, mountain mama\nTake me home, country roads\nI hear her voice, in the morning hour she calls me\nThe radio reminds me of my home far away\nAnd driving down the road I get a feeling\nThat I should have been home yesterday, yesterday\nCountry roads, take me home\nTo the place I belong\nWest Virginia, mountain mama\nTake me home, country roads\nCountry roads, take me home\nTo the place I belong\nWest Virginia, mountain mama\nTake me home, country roads\nTake me home, down country roads\nTake me home, down country roads") == "QWxtb3N0IGhlYXZlbiwgV2VzdCBWaXJnaW5pYQpCbHVlIFJpZGdlIE1vdW50YWlucywgU2hlbmFuZG9haCBSaXZlcgpMaWZlIGlzIG9sZCB0aGVyZSwgb2xkZXIgdGhhbiB0aGUgdHJlZXMKWW91bmdlciB0aGFuIHRoZSBtb3VudGFpbnMsIGJsb3dpbmcgbGlrZSBhIGJyZWV6ZQpDb3VudHJ5IHJvYWRzLCB0YWtlIG1lIGhvbWUKVG8gdGhlIHBsYWNlIEkgYmVsb25nCldlc3QgVmlyZ2luaWEsIG1vdW50YWluIG1hbWEKVGFrZSBtZSBob21lLCBjb3VudHJ5IHJvYWRzCkFsbCBteSBtZW1vcmllcyBnYXRoZXIgcm91bmQgaGVyCk1pbmVyJ3MgbGFkeSwgc3RyYW5nZXIgdG8gYmx1ZSB3YXRlcgpEYXJrIGFuZCBkdXN0eSwgcGFpbnRlZCBvbiB0aGUgc2t5Ck1pc3R5IHRhc3RlIG9mIG1vb25zaGluZSwgdGVhcmRyb3AgaW4gbXkgZXllCkNvdW50cnkgcm9hZHMsIHRha2UgbWUgaG9tZQpUbyB0aGUgcGxhY2UgSSBiZWxvbmcKV2VzdCBWaXJnaW5pYSwgbW91bnRhaW4gbWFtYQpUYWtlIG1lIGhvbWUsIGNvdW50cnkgcm9hZHMKSSBoZWFyIGhlciB2b2ljZSwgaW4gdGhlIG1vcm5pbmcgaG91ciBzaGUgY2FsbHMgbWUKVGhlIHJhZGlvIHJlbWluZHMgbWUgb2YgbXkgaG9tZSBmYXIgYXdheQpBbmQgZHJpdmluZyBkb3duIHRoZSByb2FkIEkgZ2V0IGEgZmVlbGluZwpUaGF0IEkgc2hvdWxkIGhhdmUgYmVlbiBob21lIHllc3RlcmRheSwgeWVzdGVyZGF5CkNvdW50cnkgcm9hZHMsIHRha2UgbWUgaG9tZQpUbyB0aGUgcGxhY2UgSSBiZWxvbmcKV2VzdCBWaXJnaW5pYSwgbW91bnRhaW4gbWFtYQpUYWtlIG1lIGhvbWUsIGNvdW50cnkgcm9hZHMKQ291bnRyeSByb2FkcywgdGFrZSBtZSBob21lClRvIHRoZSBwbGFjZSBJIGJlbG9uZwpXZXN0IFZpcmdpbmlhLCBtb3VudGFpbiBtYW1hClRha2UgbWUgaG9tZSwgY291bnRyeSByb2FkcwpUYWtlIG1lIGhvbWUsIGRvd24gY291bnRyeSByb2FkcwpUYWtlIG1lIGhvbWUsIGRvd24gY291bnRyeSByb2Fkcw==", "(Base64) Country Roads failed to encode properly")
- assert(encodeBase64("Man", true) == "TWFu", "(Base64) Man with padding disabled failed to encode into TWFu")
- assert(encodeBase64("Ma", true) == "TWE", "(Base64) Ma with padding disabled failed to encode into TWE")
- assert(encodeBase64("M", true) == "TQ", "(Base64) M with padding disabled failed to encode into TQ")
- assert(encodeBase64("Baby shark", true) == "QmFieSBzaGFyaw", "(Base64) Baby shark with padding disabled failed to encode into QmFieSBzaGFyaw")
- assert(encodeBase64("") == "", "(Base64) Empty string failed to encode properly")
- assert(encodeBase64("f") == "Zg==", "(Base64) f failed to encode into Zg==")
- assert(encodeBase64("fo") == "Zm8=", "(Base64) fo failed to encode into Zm8=")
- assert(encodeBase64("foo") == "Zm9v", "(Base64) foo failed to encode into Zm9v")
- assert(encodeBase64("foob") == "Zm9vYg==", "(Base64) foob failed to encode into Zm9vYg==")
- assert(encodeBase64("fooba") == "Zm9vYmE=", "(Base64) fooba failed to encode into Zm9vYmE=")
- assert(encodeBase64("foobar") == "Zm9vYmFy", "(Base64) foobar failed to encode into Zm9vYmFy")
- assert(encodeBase64("A\0B") == "QQBC", "(Base64) A\\0B failed to encode into QQBC")
- assert(encodeBase64("A\n\t\v") == "QQoJCw==", "(Base64) A\\n\\t\\v failed to encode into QQoJCw==")
- assert(encodeBase64("☺☻") == "4pi64pi7", "(Base64) ☺☻ failed to encode into 4pi64pi7")
- assert(encodeBase64("テスト") == "44OG44K544OI", "(Base64) テスト failed to encode into 44OG44K544OI")
- assert(decodeBase64("TWFu") == "Man", "(Base64) Man failed to decode into TWFu")
- assert(decodeBase64("TWE=") == "Ma", "(Base64) Ma failed to decode into TWE=")
- assert(decodeBase64("TQ==") == "M", "(Base64) M failed to decode into TQ==")
- assert(decodeBase64("QmFieSBzaGFyaw==") == "Baby shark", "(Base64) Baby shark failed to decode into QmFieSBzaGFyaw==")
- assert(decodeBase64("QWxtb3N0IGhlYXZlbiwgV2VzdCBWaXJnaW5pYQpCbHVlIFJpZGdlIE1vdW50YWlucywgU2hlbmFuZG9haCBSaXZlcgpMaWZlIGlzIG9sZCB0aGVyZSwgb2xkZXIgdGhhbiB0aGUgdHJlZXMKWW91bmdlciB0aGFuIHRoZSBtb3VudGFpbnMsIGJsb3dpbmcgbGlrZSBhIGJyZWV6ZQpDb3VudHJ5IHJvYWRzLCB0YWtlIG1lIGhvbWUKVG8gdGhlIHBsYWNlIEkgYmVsb25nCldlc3QgVmlyZ2luaWEsIG1vdW50YWluIG1hbWEKVGFrZSBtZSBob21lLCBjb3VudHJ5IHJvYWRzCkFsbCBteSBtZW1vcmllcyBnYXRoZXIgcm91bmQgaGVyCk1pbmVyJ3MgbGFkeSwgc3RyYW5nZXIgdG8gYmx1ZSB3YXRlcgpEYXJrIGFuZCBkdXN0eSwgcGFpbnRlZCBvbiB0aGUgc2t5Ck1pc3R5IHRhc3RlIG9mIG1vb25zaGluZSwgdGVhcmRyb3AgaW4gbXkgZXllCkNvdW50cnkgcm9hZHMsIHRha2UgbWUgaG9tZQpUbyB0aGUgcGxhY2UgSSBiZWxvbmcKV2VzdCBWaXJnaW5pYSwgbW91bnRhaW4gbWFtYQpUYWtlIG1lIGhvbWUsIGNvdW50cnkgcm9hZHMKSSBoZWFyIGhlciB2b2ljZSwgaW4gdGhlIG1vcm5pbmcgaG91ciBzaGUgY2FsbHMgbWUKVGhlIHJhZGlvIHJlbWluZHMgbWUgb2YgbXkgaG9tZSBmYXIgYXdheQpBbmQgZHJpdmluZyBkb3duIHRoZSByb2FkIEkgZ2V0IGEgZmVlbGluZwpUaGF0IEkgc2hvdWxkIGhhdmUgYmVlbiBob21lIHllc3RlcmRheSwgeWVzdGVyZGF5CkNvdW50cnkgcm9hZHMsIHRha2UgbWUgaG9tZQpUbyB0aGUgcGxhY2UgSSBiZWxvbmcKV2VzdCBWaXJnaW5pYSwgbW91bnRhaW4gbWFtYQpUYWtlIG1lIGhvbWUsIGNvdW50cnkgcm9hZHMKQ291bnRyeSByb2FkcywgdGFrZSBtZSBob21lClRvIHRoZSBwbGFjZSBJIGJlbG9uZwpXZXN0IFZpcmdpbmlhLCBtb3VudGFpbiBtYW1hClRha2UgbWUgaG9tZSwgY291bnRyeSByb2FkcwpUYWtlIG1lIGhvbWUsIGRvd24gY291bnRyeSByb2FkcwpUYWtlIG1lIGhvbWUsIGRvd24gY291bnRyeSByb2Fkcw==") == "Almost heaven, West Virginia\nBlue Ridge Mountains, Shenandoah River\nLife is old there, older than the trees\nYounger than the mountains, blowing like a breeze\nCountry roads, take me home\nTo the place I belong\nWest Virginia, mountain mama\nTake me home, country roads\nAll my memories gather round her\nMiner's lady, stranger to blue water\nDark and dusty, painted on the sky\nMisty taste of moonshine, teardrop in my eye\nCountry roads, take me home\nTo the place I belong\nWest Virginia, mountain mama\nTake me home, country roads\nI hear her voice, in the morning hour she calls me\nThe radio reminds me of my home far away\nAnd driving down the road I get a feeling\nThat I should have been home yesterday, yesterday\nCountry roads, take me home\nTo the place I belong\nWest Virginia, mountain mama\nTake me home, country roads\nCountry roads, take me home\nTo the place I belong\nWest Virginia, mountain mama\nTake me home, country roads\nTake me home, down country roads\nTake me home, down country roads", "(Base64) Country Roads failed to decode properly")
- assert(decodeBase64("TWE") == "Ma", "(Base64) TWE failed to decode into Ma")
- assert(decodeBase64("TQ") == "M", "(Base64) TQ failed to decode into M")
- assert(decodeBase64("QmFieSBzaGFyaw") == "Baby shark", "(Base64) QmFieSBzaGFyaw failed to decode into Baby shark")
- assert(decodeBase64("") == "", "(Base64) Empty string failed to decode")
- assert(decodeBase64("Zg==") == "f", "(Base64) Zg== failed to decode into f")
- assert(decodeBase64("Zm8=") == "fo", "(Base64) Zm8= failed to decode into fo")
- assert(decodeBase64("Zm9v") == "foo", "(Base64) Zm9v failed to decode into foo")
- assert(decodeBase64("Zm9vYg==") == "foob", "(Base64) Zm9vYg== failed to decode into foob")
- assert(decodeBase64("Zm9vYmE=") == "fooba", "(Base64) Zm9vYmE= failed to decode into fooba")
- assert(decodeBase64("Zm9vYmFy") == "foobar", "(Base64) Zm9vYmFy failed to decode into foobar")
- assert(decodeBase64("QQBC") == "A\0B", "(Base64) QQBC failed to decode into A\\0B")
- assert(decodeBase64("QQoJCw==") == "A\n\t\v", "(Base64) QQoJCw== failed to decode into A\\n\\t\\v")
- assert(decodeBase64("4pi64pi7") == "☺☻", "(Base64) 4pi64pi7 failed to decode into ☺☻")
- assert(decodeBase64("44OG44K544OI") == "テスト", "(Base64) 44OG44K544OI failed to decode into テスト")
- end
- return {
- encode = encodeBase64,
- decode = decodeBase64,
- }
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBX4b14a91351d1404f89ac5d7683addde5">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">hmac</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local ASSERTIONS_ENABLED = false -- Whether to run several checks when the module is first loaded
- local INNER_PADDING_CHAR = string.char(0x36)
- local OUTER_PADDING_CHAR = string.char(0x5C)
- local binaryStringMap = {} -- For the sake of speed of converting hexes to strings, there's a map of the conversions here
- for i = 0, 255 do
- binaryStringMap[string.format("%02x", i)] = string.char(i)
- end
- --- XORs two strings together on a byte level
- local function xorStrings(str1, str2)
- local output = {}
- for i = 1, #str1 do
- output[i] = string.char(bit32.bxor(string.byte(str1, i), string.byte(str2, i)))
- end
- return table.concat(output)
- end
- --- Converts hex strings to their binary equivalent
- local function hexToBinary(string)
- return ( string.gsub(string, "%x%x", binaryStringMap) )
- end
- --- Outputs a HMAC string (in hex) given a key, message, hashing function, and block size.
- --- Optionally accepts an output size to truncate the HMAC to, and a boolean to indicate whether to output the data as a binary string or not.
- --- Both blockSize and outputSize are in bytes for ease of computation.
- local function hmac(key, message, hash, blockSize, outputSize, asBinaryData)
- local innerPadding = string.rep(INNER_PADDING_CHAR, blockSize)
- local outerPadding = string.rep(OUTER_PADDING_CHAR, blockSize)
- if #key > blockSize then
- key = hexToBinary(hash(key))
- end
- if #key < blockSize then
- key = key..string.rep("\0", blockSize-#key)
- end
- local outerKey = xorStrings(key, outerPadding)
- local innerKey = xorStrings(key, innerPadding)
- local mac = hash( outerKey..hexToBinary(hash(innerKey..message)) )
- local output;
- if outputSize then
- output = string.sub(mac, 1, outputSize*2) -- Today's gross hack is brought to you by every byte being represented by two hex digits
- else
- output = mac
- end
- if asBinaryData then
- return hexToBinary(output)
- else
- return output
- end
- end
- if ASSERTIONS_ENABLED then
- local sha256 = require(script.Parent.sha256)
- -- SHA-256 tests (https://tools.ietf.org/html/rfc4231)
- assert(hmac(string.rep(string.char(0x0b), 20), "Hi There", sha256, 64) == "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", "(HMAC-SHA-256) RFC test case 1 hash does not match")
- assert(hmac("Jefe", "what do ya want for nothing?", sha256, 64) == "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", "(HMAC-SHA-256) RFC test case 2 hash does not match")
- assert(hmac(string.rep(string.char(0xaa), 20), string.rep(string.char(0xdd), 50), sha256, 64) == "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", "(HMAC-SHA-256) RFC test case 3 hash does not match")
- assert(hmac(hexToBinary("0102030405060708090a0b0c0d0e0f10111213141516171819"), string.rep(string.char(0xcd), 50), sha256, 64) == "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", "(HMAC-SHA-256) RFC test case 4 hash does not match")
- assert(hmac(string.rep(string.char(0x0c), 20), "Test With Truncation", sha256, 64, 16) == "a3b6167473100ee06e0c796c2955552b", "(HMAC-SHA-256) RFC test case 5 hash does not match")
- assert(hmac(string.rep(string.char(0xaa), 131), "Test Using Larger Than Block-Size Key - Hash Key First", sha256, 64) == "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", "(HMAC-SHA-256) RFC test case 6 hash does not match")
- assert(hmac(string.rep(string.char(0xaa), 131), "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", sha256, 64) == "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2", "(HMAC-SHA-256) RFC test case 7 hash does not match")
- -- Tests explicitly for binary data output
- assert(hmac(string.rep(string.char(0x0b), 20), "Hi There", sha256, 64, nil, true) == "\176\52\76\97\216\219\56\83\92\168\175\206\175\11\241\43\136\29\194\0\201\131\61\167\38\233\55\108\46\50\207\247", "(HMAC-SHA-256) RFC test case 1 hash as binary does not match")
- assert(hmac("Jefe", "what do ya want for nothing?", sha256, 64, nil, true) == "\91\220\193\70\191\96\117\78\106\4\36\38\8\149\117\199\90\0\63\8\157\39\57\131\157\236\88\185\100\236\56\67", "(HMAC-SHA-256) RFC test case 2 hash as binary does not match")
- assert(hmac(string.rep(string.char(0xaa), 20), string.rep(string.char(0xdd), 50), sha256, 64, nil, true) == "\119\62\169\30\54\128\14\70\133\77\184\235\208\145\129\167\41\89\9\139\62\248\193\34\217\99\85\20\206\213\101\254", "(HMAC-SHA-256) RFC test case 3 hash as binary does not match")
- assert(hmac(hexToBinary("0102030405060708090a0b0c0d0e0f10111213141516171819"), string.rep(string.char(0xcd), 50), sha256, 64, nil, true) == "\130\85\138\56\154\68\60\14\164\204\129\152\153\242\8\58\133\240\250\163\229\120\248\7\122\46\63\244\103\41\102\91", "(HMAC-SHA-256) RFC test case 4 hash as binary does not match")
- assert(hmac(string.rep(string.char(0x0c), 20), "Test With Truncation", sha256, 64, 16, true) == "\163\182\22\116\115\16\14\224\110\12\121\108\41\85\85\43", "(HMAC-SHA-256) RFC test case 5 hash as binary does not match")
- assert(hmac(string.rep(string.char(0xaa), 131), "Test Using Larger Than Block-Size Key - Hash Key First", sha256, 64, nil, true) == "\96\228\49\89\30\224\182\127\13\138\38\170\203\245\183\127\142\11\198\33\55\40\197\20\5\70\4\15\14\227\127\84", "(HMAC-SHA-256) RFC test case 6 hash as binary does not match")
- assert(hmac(string.rep(string.char(0xaa), 131), "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", sha256, 64, nil, true) == "\155\9\255\167\27\148\47\203\39\99\95\188\213\176\233\68\191\220\99\100\79\7\19\147\138\127\81\83\92\58\53\226", "(HMAC-SHA-256) RFC test case 7 hash as binary does not match")
- end
- return hmac]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBX4b14a91351d1404f89ac5d7683afffe5">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">sha256</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local ASSERTIONS_ENABLED = false -- Whether to run several checks when the module is first loaded and when a message is preprocessed.
- local INIT_0_256 = 0x6a09e667
- local INIT_1_256 = 0xbb67ae85
- local INIT_2_256 = 0x3c6ef372
- local INIT_3_256 = 0xa54ff53a
- local INIT_4_256 = 0x510e527f
- local INIT_5_256 = 0x9b05688c
- local INIT_6_256 = 0x1f83d9ab
- local INIT_7_256 = 0x5be0cd19
- local APPEND_CHAR = string.char(0x80)
- local INT_32_CAP = 2^32
- local K = { [0] =
- 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
- 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
- 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
- 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
- 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
- 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
- 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
- }
- ---Packs four 8-bit integers into one 32-bit integer
- local function packUint32(a, b, c, d)
- return bit32.lshift(a, 24)+bit32.lshift(b, 16)+bit32.lshift(c, 8)+d
- end
- ---Unpacks one 32-bit integer into four 8-bit integers
- local function unpackUint32(int)
- return bit32.extract(int, 24, 8), bit32.extract(int, 16, 8),
- bit32.extract(int, 08, 8), bit32.extract(int, 00, 8)
- end
- local function CH(x, y, z)
- -- C ~ (A & (B ~ C)) has less ops than (A & B) ^ (~A & C)
- return bit32.bxor( z, bit32.band(x, bit32.bxor(y, z)) )
- end
- local function MAJ(x, y, z)
- -- A | (B | C) | (B & C) has less ops than (A & B) ^ (A & C) ^ (B & C)
- return bit32.bor( bit32.band(x, bit32.bor(y, z)), bit32.band(y, z) )
- end
- local function BSIG0(x)
- return bit32.bxor( bit32.rrotate(x, 2), bit32.rrotate(x, 13), bit32.rrotate(x, 22) )
- end
- local function BSIG1(x)
- return bit32.bxor( bit32.rrotate(x, 6), bit32.rrotate(x, 11), bit32.rrotate(x, 25) )
- end
- local function SSIG0(x)
- return bit32.bxor( bit32.rrotate(x, 7), bit32.rrotate(x, 18), bit32.rshift(x, 3) )
- end
- local function SSIG1(x)
- return bit32.bxor( bit32.rrotate(x, 17), bit32.rrotate(x, 19), bit32.rshift(x, 10) )
- end
- local function preprocessMessage(message)
- local initMsgLen = #message*8 -- Message length in bits
- local msgLen = initMsgLen+8
- local nulCount = 4 -- This is equivalent to 32 bits.
- -- We're packing 32 bits of size, but the SHA-256 standard calls for 64, meaning we have to add at least 32 0s
- -- Unfortunately 64 bits is not possible due to Lua numbers being doubles
- message = message..APPEND_CHAR
- while (msgLen+64)%512 ~= 0 do
- nulCount = nulCount+1
- msgLen = msgLen+8
- end
- message = message..string.rep("\0", nulCount)
- message = message..string.char(unpackUint32(initMsgLen))
- if ASSERTIONS_ENABLED then
- assert(msgLen%512 == 448, "message length space check")
- assert(#message%64 == 0, "message length check")
- end
- return message
- end
- local function sha256(message)
- local message = preprocessMessage(message)
- local H0 = INIT_0_256
- local H1 = INIT_1_256
- local H2 = INIT_2_256
- local H3 = INIT_3_256
- local H4 = INIT_4_256
- local H5 = INIT_5_256
- local H6 = INIT_6_256
- local H7 = INIT_7_256
- local W = {}
- for chunkStart = 1, #message, 64 do
- local place = chunkStart
- for t = 0, 15 do
- W[t] = packUint32(string.byte(message, place, place+3))
- place = place+4
- end
- for t = 16, 63 do
- W[t] = SSIG1(W[t-2])+W[t-7]+SSIG0(W[t-15])+W[t-16]
- end
- local a, b, c, d, e, f, g, h = H0, H1, H2, H3, H4, H5, H6, H7
- for t = 0, 63 do
- T1 = h + BSIG1(e) + CH(e, f, g) + K[t] + W[t]
- T2 = BSIG0(a) + MAJ(a, b, c)
- h = g
- g = f
- f = e
- e = d + T1
- d = c
- c = b
- b = a
- a = T1 + T2
- end
- H0 = (H0+a)%INT_32_CAP
- H1 = (H1+b)%INT_32_CAP
- H2 = (H2+c)%INT_32_CAP
- H3 = (H3+d)%INT_32_CAP
- H4 = (H4+e)%INT_32_CAP
- H5 = (H5+f)%INT_32_CAP
- H6 = (H6+g)%INT_32_CAP
- H7 = (H7+h)%INT_32_CAP
- end
- return string.format("%08x%08x%08x%08x%08x%08x%08x%08x", H0, H1, H2, H3, H4, H5, H6, H7)
- end
- if ASSERTIONS_ENABLED then
- assert(packUint32(255, 167, 125, 235) == 4289166827, "(SHA-256/224) packUint32 check 1")
- assert(packUint32(255, 0, 125, 235) == 4278222315, "(SHA-256/224) packUint32 check 2")
- local b0, b1, b2, b3 = unpackUint32(4278222315)
- assert(b0 == 255, "(SHA-256/224) unpackUint32 check 1")
- assert(b1 == 000, "(SHA-256/224) unpackUint32 check 2")
- assert(b2 == 125, "(SHA-256/224) unpackUint32 check 3")
- assert(b3 == 235, "(SHA-256/224) unpackUint32 check 4")
- -- SHA-256 tests
- assert(sha256("abc") == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "(SHA-256) abc hash does not match")
- assert(sha256("") == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "(SHA-256) empty hash does not match")
- assert(sha256("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") == "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "(SHA-256) 448 bit alphabet hash does not match")
- assert(sha256("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu") == "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1", "(SHA-256) 896 bit alphabet hash does not match")
- assert(sha256("foo") == "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", "(SHA-256) foo hash does not match")
- assert(sha256("bar") == "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9", "(SHA-256) bar hash does not match")
- assert(sha256("baz") == "baa5a0964d3320fbc0c6a922140453c8513ea24ab8fd0577034804a967248096", "(SHA-256) baz hash does not match")
- if true then
- assert(sha256(string.rep("e", 199999)) == "434cf81dca15a72777e811ed4ae9144f9272ca3c04ff9c2de1533bbbffed5449", "(SHA-256) e hash does not match")
- assert(sha256(string.rep("a", 1e6)) == "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0", "(SHA-256) million a hash does not match")
- end
- end
- return sha256]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- </Item>
- <Item class="ModuleScript" referent="RBX4b14a91351d1404f89ac5d7683abdde5">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">Logger</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local RunService = game:GetService("RunService")
- --local GameAnalyticsSendMessage
- local logger = {
- _infoLogEnabled = false,
- _infoLogAdvancedEnabled = false,
- _debugEnabled = RunService:IsStudio()
- }
- function logger:setDebugLog(enabled)
- self._debugEnabled = enabled
- end
- function logger:setInfoLog(enabled)
- self._infoLogEnabled = enabled
- end
- function logger:setVerboseLog(enabled)
- self._infoLogAdvancedEnabled = enabled
- end
- function logger:i(format)
- if not self._infoLogEnabled then
- return
- end
- local m = "Info/GameAnalytics: " .. format
- print(m)
- -- GameAnalyticsSendMessage = GameAnalyticsSendMessage or game:GetService("ReplicatedStorage"):WaitForChild("GameAnalyticsSendMessage")
- -- GameAnalyticsSendMessage:FireAllClients({
- -- Text = m,
- -- Font = Enum.Font.Arial,
- -- Color = Color3.new(255, 255, 255),
- -- FontSize = Enum.FontSize.Size96
- -- })
- end
- function logger:w(format)
- local m = "Warning/GameAnalytics: " .. format
- warn(m)
- -- GameAnalyticsSendMessage = GameAnalyticsSendMessage or game:GetService("ReplicatedStorage"):WaitForChild("GameAnalyticsSendMessage")
- -- GameAnalyticsSendMessage:FireAllClients({
- -- Text = m,
- -- Font = Enum.Font.Arial,
- -- Color = Color3.new(255, 255, 0),
- -- FontSize = Enum.FontSize.Size96
- -- })
- end
- function logger:e(format)
- spawn(function ()
- local m = "Error/GameAnalytics: " .. format
- error(m, 0)
- -- GameAnalyticsSendMessage = GameAnalyticsSendMessage or game:GetService("ReplicatedStorage"):WaitForChild("GameAnalyticsSendMessage")
- -- GameAnalyticsSendMessage:FireAllClients({
- -- Text = m,
- -- Font = Enum.Font.Arial,
- -- Color = Color3.new(255, 0, 0),
- -- FontSize = Enum.FontSize.Size96
- -- })
- end)
- end
- function logger:d(format)
- if not self._debugEnabled then
- return
- end
- local m = "Debug/GameAnalytics: " .. format
- print(m)
- -- GameAnalyticsSendMessage = GameAnalyticsSendMessage or game:GetService("ReplicatedStorage"):WaitForChild("GameAnalyticsSendMessage")
- -- GameAnalyticsSendMessage:FireAllClients({
- -- Text = m,
- -- Font = Enum.Font.Arial,
- -- Color = Color3.new(255, 255, 255),
- -- FontSize = Enum.FontSize.Size96
- -- })
- end
- function logger:ii(format)
- if not self._infoLogAdvancedEnabled then
- return
- end
- local m = "Verbose/GameAnalytics: " .. format
- print(m)
- -- GameAnalyticsSendMessage = GameAnalyticsSendMessage or game:GetService("ReplicatedStorage"):WaitForChild("GameAnalyticsSendMessage")
- -- GameAnalyticsSendMessage:FireAllClients({
- -- Text = m,
- -- Font = Enum.Font.Arial,
- -- Color = Color3.new(255, 255, 255),
- -- FontSize = Enum.FontSize.Size96
- -- })
- end
- return logger
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBX0fd16702bea24d64882fe169a7f9157d">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">Store</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local DS = game:GetService("DataStoreService")
- local RunService = game:GetService("RunService")
- local store = {
- PlayerDS = RunService:IsStudio() and {} or DS:GetDataStore("GA_PlayerDS_1.0.0"),
- AutoSaveData = 180, --Set to 0 to disable
- BasePlayerData = {
- Sessions = 0,
- Transactions = 0,
- ProgressionTries = {},
- CurrentCustomDimension01 = "",
- CurrentCustomDimension02 = "",
- CurrentCustomDimension03 = "",
- InitAuthorized = false,
- SdkConfig = {},
- ClientServerTimeOffset = 0,
- Configurations = {},
- CommandCenterIsReady = false,
- PlayerTeleporting = false
- },
- DataToSave = {
- "Sessions",
- "Transactions",
- "ProgressionTries",
- "CurrentCustomDimension01",
- "CurrentCustomDimension02",
- "CurrentCustomDimension03"
- },
- --Cache
- PlayerCache = {},
- EventsQueue = {}
- }
- function store:GetPlayerData(Player)
- local PlayerData
- local success, _ = pcall(function()
- PlayerData = RunService:IsStudio() and {} or (store.PlayerDS:GetAsync(Player.UserId) or {})
- end)
- if not success then
- PlayerData = {}
- end
- return PlayerData
- end
- function store:GetErrorDataStore(scope)
- local ErrorDS
- local success, _ = pcall(function()
- ErrorDS = RunService:IsStudio() and {} or DS:GetDataStore("GA_ErrorDS_1.0.0", scope)
- end)
- if not success then
- ErrorDS = {}
- end
- return ErrorDS
- end
- function store:SavePlayerData(Player)
- --Variables
- local PlayerData = store.PlayerCache[Player.UserId]
- local SavePlayerData = {}
- if not PlayerData then
- return
- end
- --Fill
- for _, key in pairs(store.DataToSave) do
- SavePlayerData[key] = PlayerData[key]
- end
- --Save
- if not RunService:IsStudio() then
- pcall(function()
- store.PlayerDS:SetAsync(Player.UserId, SavePlayerData)
- end)
- end
- end
- function store:IncrementErrorCount(ErrorDS, ErrorKey, step)
- if not ErrorKey then
- return
- end
- local count = 0
- --Increment count
- if not RunService:IsStudio() then
- pcall(function()
- count = ErrorDS:IncrementAsync(ErrorKey, step)
- end)
- end
- return count
- end
- return store
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBXbba5a886fb3443b989529266284f613e">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">Events</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local events = {
- ProcessEventsInterval = 8,
- GameKey = "",
- SecretKey = "",
- _build = "",
- _availableResourceCurrencies = {},
- _availableResourceItemTypes = {},
- }
- local store = require(script.Parent.Store)
- local logger = require(script.Parent.Logger)
- local version = require(script.Parent.Version)
- local validation = require(script.Parent.Validation)
- local threading = require(script.Parent.Threading)
- local http_api = require(script.Parent.HttpApi)
- local utilities = require(script.Parent.Utilities)
- local GAResourceFlowType = require(script.Parent.GAResourceFlowType)
- local GAProgressionStatus = require(script.Parent.GAProgressionStatus)
- local GAErrorSeverity = require(script.Parent.GAErrorSeverity)
- local HTTP = game:GetService("HttpService")
- local CategorySessionStart = "user"
- local CategorySessionEnd = "session_end"
- local CategoryBusiness = "business"
- local CategoryResource = "resource"
- local CategoryProgression = "progression"
- local CategoryDesign = "design"
- local CategoryError = "error"
- local MAX_EVENTS_TO_SEND_IN_ONE_BATCH = 500
- local MAX_AGGREGATED_EVENTS = 2000
- local function addDimensionsToEvent(playerId, eventData)
- if not eventData then
- return
- end
- if not playerId then
- return
- end
- local PlayerData = store.PlayerCache[playerId]
- -- add to dict (if not nil)
- if PlayerData and PlayerData.CurrentCustomDimension01 and #PlayerData.CurrentCustomDimension01 > 0 then
- eventData["custom_01"] = PlayerData.CurrentCustomDimension01
- end
- if PlayerData and PlayerData.CurrentCustomDimension02 and #PlayerData.CurrentCustomDimension02 > 0 then
- eventData["custom_02"] = PlayerData.CurrentCustomDimension02
- end
- if PlayerData and PlayerData.CurrentCustomDimension03 and #PlayerData.CurrentCustomDimension03 > 0 then
- eventData["custom_03"] = PlayerData.CurrentCustomDimension03
- end
- end
- local function getClientTsAdjusted(playerId)
- if not playerId then
- return os.time()
- end
- local PlayerData = store.PlayerCache[playerId]
- local clientTs = os.time()
- local clientTsAdjustedInteger = clientTs + PlayerData.ClientServerTimeOffset
- if validation:validateClientTs(clientTsAdjustedInteger) then
- return clientTsAdjustedInteger;
- else
- return clientTs
- end
- end
- local DUMMY_SESSION_ID = HTTP:GenerateGUID(false):lower()
- local function getEventAnnotations(playerId)
- local PlayerData
- local id
- if playerId then
- id = playerId
- PlayerData = store.PlayerCache[playerId]
- else
- id = "DummyId"
- PlayerData = {
- OS = "uwp_desktop 0.0.0",
- Platform = "uwp_desktop",
- SessionID = DUMMY_SESSION_ID,
- Sessions = 1
- }
- end
- local annotations = {
- -- ---- REQUIRED ----
- -- collector event API version
- ["v"] = 2,
- -- User identifier
- ["user_id"] = tostring(id),
- -- Client Timestamp (the adjusted timestamp)
- ["client_ts"] = getClientTsAdjusted(playerId),
- -- SDK version
- ["sdk_version"] = "roblox " .. version.SdkVersion,
- -- Operation system version
- ["os_version"] = PlayerData.OS,
- -- Device make (hardcoded to apple)
- ["manufacturer"] = "unknown",
- -- Device version
- ["device"] = "unknown",
- -- Platform (operating system)
- ["platform"] = PlayerData.Platform,
- -- Session identifier
- ["session_id"] = PlayerData.SessionID,
- -- Session number
- ["session_num"] = PlayerData.Sessions
- }
- if validation:validateBuild(events._build) then
- annotations["build"] = events._build
- end
- return annotations
- end
- local function addEventToStore(playerId, eventData)
- -- Get default annotations
- local ev = getEventAnnotations(playerId)
- -- Merge with eventData
- for k,_ in pairs(eventData) do
- ev[k] = eventData[k]
- end
- -- Create json string representation
- local json = HTTP:JSONEncode(ev)
- -- output if VERBOSE LOG enabled
- logger:ii("Event added to queue: " .. json)
- -- Add to store
- store.EventsQueue[#store.EventsQueue + 1] = ev
- end
- local function dequeueMaxEvents()
- if #store.EventsQueue <= MAX_EVENTS_TO_SEND_IN_ONE_BATCH then
- local eventsQueue = store.EventsQueue
- store.EventsQueue = {}
- return eventsQueue
- else
- logger:w(("More than %d events queued! Sending %d."):format(MAX_EVENTS_TO_SEND_IN_ONE_BATCH, MAX_EVENTS_TO_SEND_IN_ONE_BATCH))
- if #self.EventsQueue > MAX_AGGREGATED_EVENTS then
- logger:w(("DROPPING EVENTS: More than %d events queued!"):format(MAX_AGGREGATED_EVENTS))
- end
- -- Expensive operation to get ordered events cleared out (O(n))
- local eventsQueue = {}
- for i=1, MAX_EVENTS_TO_SEND_IN_ONE_BATCH do
- eventsQueue[i] = store.EventsQueue[i]
- end
- -- Shift everything down and overwrite old events
- local eventCount = #self._events
- for i=1, math.min(MAX_AGGREGATED_EVENTS, eventCount) do
- store.EventsQueue[i] = store.EventsQueue[i + MAX_EVENTS_TO_SEND_IN_ONE_BATCH]
- end
- -- Clear additional events
- for i=MAX_AGGREGATED_EVENTS+1, eventCount do
- store.EventsQueue[i] = nil
- end
- return eventsQueue
- end
- end
- local function processEvents()
- local queue = dequeueMaxEvents()
- if #queue == 0 then
- logger:i("Event queue: No events to send")
- return
- end
- -- Log
- logger:i("Event queue: Sending " .. tostring(#queue) .. " events.")
- local eventsResult = http_api:sendEventsInArray(events.GameKey, events.SecretKey, queue)
- local statusCode = eventsResult.statusCode
- local responseBody = eventsResult.body
- if statusCode == http_api.EGAHTTPApiResponse.Ok and responseBody then
- logger:i("Event queue: " .. tostring(#queue) .. " events sent.")
- else
- if statusCode == http_api.EGAHTTPApiResponse.NoResponse then
- logger:w("Event queue: Failed to send events to collector - Retrying next time")
- for _,e in pairs(queue) do
- if #store.EventsQueue < MAX_AGGREGATED_EVENTS then
- store.EventsQueue[#store.EventsQueue + 1] = e
- else
- break
- end
- end
- else
- if statusCode == http_api.EGAHTTPApiResponse.BadRequest and responseBody then
- logger:w("Event queue: " .. tostring(#queue) .. " events sent. " .. tostring(#responseBody) .. " events failed GA server validation.")
- else
- logger:w("Event queue: Failed to send events.")
- end
- end
- end
- end
- function events:processEventQueue()
- processEvents()
- threading:scheduleTimer(events.ProcessEventsInterval, function()
- events:processEventQueue()
- end)
- end
- function events:setBuild(build)
- if not validation:validateBuild(build) then
- logger:w("Validation fail - configure build: Cannot be null, empty or above 32 length. String: " .. build)
- return
- end
- self._build = build
- logger:i("Set build version: " .. build)
- end
- function events:setAvailableResourceCurrencies(availableResourceCurrencies)
- if not validation:validateResourceCurrencies(availableResourceCurrencies) then
- return
- end
- self._availableResourceCurrencies = availableResourceCurrencies
- logger:i("Set available resource currencies: (" .. table.concat(availableResourceCurrencies, ", ") .. ")")
- end
- function events:setAvailableResourceItemTypes(availableResourceItemTypes)
- if not validation:validateResourceCurrencies(availableResourceItemTypes) then
- return
- end
- self._availableResourceItemTypes = availableResourceItemTypes
- logger:i("Set available resource item types: (" .. table.concat(availableResourceItemTypes, ", ") .. ")")
- end
- function events:addSessionStartEvent(playerId, teleportData)
- local PlayerData = store.PlayerCache[playerId]
- if teleportData then
- PlayerData.Sessions = teleportData.Sessions
- else
- local eventDict = {}
- -- Event specific data
- eventDict["category"] = CategorySessionStart
- -- Increment session number and persist
- PlayerData.Sessions = PlayerData.Sessions + 1
- -- Add custom dimensions
- addDimensionsToEvent(playerId, eventDict)
- -- Add to store
- addEventToStore(playerId, eventDict)
- logger:i("Add SESSION START event")
- processEvents()
- end
- end
- function events:addSessionEndEvent(playerId)
- local PlayerData = store.PlayerCache[playerId]
- local session_start_ts = PlayerData.SessionStart
- local client_ts_adjusted = getClientTsAdjusted(playerId)
- local sessionLength = client_ts_adjusted - session_start_ts
- if sessionLength < 0 then
- -- Should never happen.
- -- Could be because of edge cases regarding time altering on device.
- logger:w("Session length was calculated to be less then 0. Should not be possible. Resetting to 0.")
- sessionLength = 0
- end
- -- Event specific data
- local eventDict = {}
- eventDict["category"] = CategorySessionEnd
- eventDict["length"] = sessionLength
- -- Add custom dimensions
- addDimensionsToEvent(playerId, eventDict)
- -- Add to store
- addEventToStore(playerId, eventDict)
- logger:i("Add SESSION END event.")
- processEvents()
- end
- function events:addBusinessEvent(playerId, currency, amount, itemType, itemId, cartType)
- -- Validate event params
- if not validation:validateBusinessEvent(currency, amount, cartType, itemType, itemId) then
- -- TODO: add sdk error event
- return
- end
- -- Create empty eventData
- local eventDict = {}
- -- Increment transaction number and persist
- local PlayerData = store.PlayerCache[playerId]
- PlayerData.Transactions = PlayerData.Transactions + 1
- -- Required
- eventDict["event_id"] = itemType .. ":" .. itemId
- eventDict["category"] = CategoryBusiness
- eventDict["currency"] = currency
- eventDict["amount"] = amount
- eventDict["transaction_num"] = PlayerData.Transactions
- -- Optional
- if not utilities:isStringNullOrEmpty(cartType) then
- eventDict["cart_type"] = cartType
- end
- -- Add custom dimensions
- addDimensionsToEvent(playerId, eventDict)
- logger:i("Add BUSINESS event: {currency:" .. currency .. ", amount:" .. tostring(amount) .. ", itemType:" .. itemType .. ", itemId:" .. itemId .. ", cartType:" .. cartType .. "}")
- -- Send to store
- addEventToStore(playerId, eventDict)
- end
- function events:addResourceEvent(playerId, flowType, currency, amount, itemType, itemId)
- -- Validate event params
- if not validation:validateResourceEvent(GAResourceFlowType, flowType, currency, amount, itemType, itemId, self._availableResourceCurrencies, self._availableResourceItemTypes) then
- -- TODO: add sdk error event
- return
- end
- -- If flow type is sink reverse amount
- if flowType == GAResourceFlowType.Sink then
- amount = (-1 * amount)
- end
- -- Create empty eventData
- local eventDict = {}
- -- insert event specific values
- local flowTypeString = GAResourceFlowType[flowType]
- eventDict["event_id"] = flowTypeString .. ":" .. currency .. ":" .. itemType .. ":" .. itemId
- eventDict["category"] = CategoryResource
- eventDict["amount"] = amount
- -- Add custom dimensions
- addDimensionsToEvent(playerId, eventDict)
- logger:i("Add RESOURCE event: {currency:" .. currency .. ", amount:" .. tostring(amount) .. ", itemType:" .. itemType .. ", itemId:" .. itemId .. "}")
- -- Send to store
- addEventToStore(playerId, eventDict)
- end
- function events:addProgressionEvent(playerId, progressionStatus, progression01, progression02, progression03, score)
- -- Validate event params
- if not validation:validateProgressionEvent(GAProgressionStatus, progressionStatus, progression01, progression02, progression03) then
- -- TODO: add sdk error event
- return
- end
- -- Create empty eventData
- local eventDict = {}
- -- Progression identifier
- local progressionIdentifier
- if utilities:isStringNullOrEmpty(progression02) then
- progressionIdentifier = progression01
- elseif utilities:isStringNullOrEmpty(progression03) then
- progressionIdentifier = progression01 .. ":" .. progression02
- else
- progressionIdentifier = progression01 .. ":" .. progression02 .. ":" .. progression03
- end
- local statusString = GAProgressionStatus[progressionStatus]
- -- Append event specifics
- eventDict["category"] = CategoryProgression
- eventDict["event_id"] = statusString .. ":" .. progressionIdentifier
- -- Attempt
- local attempt_num = 0
- -- Add score if specified and status is not start
- if score ~= nil and progressionStatus ~= GAProgressionStatus.Start then
- eventDict["score"] = score
- end
- local PlayerData = store.PlayerCache[playerId]
- -- Count attempts on each progression fail and persist
- if progressionStatus == GAProgressionStatus.Fail then
- -- Increment attempt number
- local progressionTries = PlayerData.ProgressionTries[progressionIdentifier] or 0
- PlayerData.ProgressionTries[progressionIdentifier] = progressionTries + 1
- end
- -- increment and add attempt_num on complete and delete persisted
- if progressionStatus == GAProgressionStatus.Complete then
- -- Increment attempt number
- local progressionTries = PlayerData.ProgressionTries[progressionIdentifier] or 0
- PlayerData.ProgressionTries[progressionIdentifier] = progressionTries + 1
- -- Add to event
- attempt_num = PlayerData.ProgressionTries[progressionIdentifier]
- eventDict["attempt_num"] = attempt_num
- -- Clear
- PlayerData.ProgressionTries[progressionIdentifier] = 0
- end
- -- Add custom dimensions
- addDimensionsToEvent(playerId, eventDict)
- local progression02String = ""
- if not utilities:isStringNullOrEmpty(progression02) then
- progression02String = progression02
- end
- local progression03String = ""
- if not utilities:isStringNullOrEmpty(progression03) then
- progression03String = progression03
- end
- logger:i("Add PROGRESSION event: {status:" .. statusString .. ", progression01:" .. progression01 .. ", progression02:" .. progression02String .. ", progression03:" .. progression03String .. ", score:" .. tostring(score) .. ", attempt:" .. tostring(attempt_num) .. "}")
- -- Send to store
- addEventToStore(playerId, eventDict)
- end
- function events:addDesignEvent(playerId, eventId, value)
- -- Validate
- if not validation:validateDesignEvent(eventId) then
- -- TODO: add sdk error event
- return
- end
- -- Create empty eventData
- local eventData = {}
- -- Append event specifics
- eventData["category"] = CategoryDesign
- eventData["event_id"] = eventId
- if value ~= nil then
- eventData["value"] = value
- end
- -- Add custom dimensions
- addDimensionsToEvent(playerId, eventData)
- logger:i("Add DESIGN event: {eventId:" .. eventId .. ", value:" .. tostring(value) .. "}")
- -- Send to store
- addEventToStore(playerId, eventData)
- end
- function events:addErrorEvent(playerId, severity, message)
- -- Validate
- if not validation:validateErrorEvent(GAErrorSeverity, severity, message) then
- -- TODO: add sdk error event
- return
- end
- -- Create empty eventData
- local eventData = {}
- local severityString = GAErrorSeverity[severity]
- eventData["category"] = CategoryError
- eventData["severity"] = severityString
- eventData["message"] = message
- -- Add custom dimensions
- addDimensionsToEvent(playerId, eventData)
- local messageString = ""
- if not utilities:isStringNullOrEmpty(message) then
- messageString = message
- end
- logger:i("Add ERROR event: {severity:" .. severityString .. ", message:" .. messageString .. "}")
- -- Send to store
- addEventToStore(playerId, eventData)
- end
- return events
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBX5190ffbee57e4a9b94c4e0a74eb63cab">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">Utilities</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local utilities = {}
- function utilities:isStringNullOrEmpty(s)
- return (not s) or #s == 0
- end
- function utilities:stringArrayContainsString(array, search)
- if #array == 0 then
- return false
- end
- for _,s in pairs(array) do
- if s == search then
- return true
- end
- end
- return false
- end
- return utilities
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBX02095c98e62847afaf7fdea69dab9f66">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">Version</string>
- <string name="ScriptGuid">{08f3dc9a-fca3-4b1b-8436-e07880f9095e}</string>
- <ProtectedString name="Source"><![CDATA[local version = {
- SdkVersion = "1.4.2"
- }
- return version
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBX8bd464ce27cb4e64ab219c6cb8ccb43d">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">State</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local state = {
- _availableCustomDimensions01 = {},
- _availableCustomDimensions02 = {},
- _availableCustomDimensions03 = {},
- _enableEventSubmission = true,
- Initialized = false,
- ReportErrors = true,
- AutomaticSendBusinessEvents = true
- }
- local validation = require(script.Parent.Validation)
- local logger = require(script.Parent.Logger)
- local http_api = require(script.Parent.HttpApi)
- local store = require(script.Parent.Store)
- local events = require(script.Parent.Events)
- local HTTP = game:GetService("HttpService")
- local GameAnalyticsCommandCenter
- local function getClientTsAdjusted(playerId)
- local PlayerData = store.PlayerCache[playerId]
- if not PlayerData then
- return os.time()
- end
- local clientTs = os.time()
- local clientTsAdjustedInteger = clientTs + PlayerData.ClientServerTimeOffset
- if validation:validateClientTs(clientTsAdjustedInteger) then
- return clientTsAdjustedInteger;
- else
- return clientTs
- end
- end
- local function populateConfigurations(player)
- local PlayerData = store.PlayerCache[player.UserId]
- local sdkConfig = PlayerData.SdkConfig
- if sdkConfig["configurations"] then
- local configurations = sdkConfig["configurations"]
- for _,configuration in pairs(configurations) do
- if configuration then
- local key = configuration["key"] or ""
- local start_ts = configuration["start"] or 0
- local end_ts = configuration["end"] or math.huge
- local client_ts_adjusted = getClientTsAdjusted(player.UserId)
- if #key > 0 and configuration["value"] and client_ts_adjusted > start_ts and client_ts_adjusted < end_ts then
- PlayerData.Configurations[key] = configuration["value"]
- logger:d("configuration added: key=" .. configuration["key"] .. ", value=" .. configuration["value"])
- end
- end
- end
- end
- PlayerData.CommandCenterIsReady = true
- GameAnalyticsCommandCenter = GameAnalyticsCommandCenter or game:GetService("ReplicatedStorage"):WaitForChild("GameAnalyticsCommandCenter")
- GameAnalyticsCommandCenter:FireClient(player, PlayerData.Configurations)
- end
- function state:sessionIsStarted(playerId)
- local PlayerData = store.PlayerCache[playerId]
- if not PlayerData then
- return false
- end
- return PlayerData.SessionStart ~= 0
- end
- function state:isEnabled(playerId)
- local PlayerData = store.PlayerCache[playerId]
- if not PlayerData then
- return false
- elseif PlayerData.SdkConfig and PlayerData.SdkConfig["enabled"] == false then
- return false
- elseif not PlayerData.InitAuthorized then
- return false
- else
- return true
- end
- end
- function state:validateAndFixCurrentDimensions(playerId)
- local PlayerData = store.PlayerCache[playerId]
- -- validate that there are no current dimension01 not in list
- if not validation:validateDimension(self._availableCustomDimensions01, PlayerData.CurrentCustomDimension01) then
- logger:d("Invalid dimension01 found in variable. Setting to nil. Invalid dimension: " .. PlayerData.CurrentCustomDimension01)
- end
- -- validate that there are no current dimension02 not in list
- if not validation:validateDimension(self._availableCustomDimensions02, PlayerData.CurrentCustomDimension02) then
- logger:d("Invalid dimension02 found in variable. Setting to nil. Invalid dimension: " .. PlayerData.CurrentCustomDimension02)
- end
- -- validate that there are no current dimension03 not in list
- if not validation:validateDimension(self._availableCustomDimensions03, PlayerData.CurrentCustomDimension03) then
- logger:d("Invalid dimension03 found in variable. Setting to nil. Invalid dimension: " .. PlayerData.CurrentCustomDimension03)
- end
- end
- function state:setAvailableCustomDimensions01(availableCustomDimensions)
- if not validation:validateCustomDimensions(availableCustomDimensions) then
- return
- end
- self._availableCustomDimensions01 = availableCustomDimensions
- logger:i("Set available custom01 dimension values: (" .. table.concat(availableCustomDimensions, ", ") .. ")")
- end
- function state:setAvailableCustomDimensions02(availableCustomDimensions)
- if not validation:validateCustomDimensions(availableCustomDimensions) then
- return
- end
- self._availableCustomDimensions02 = availableCustomDimensions
- logger:i("Set available custom02 dimension values: (" .. table.concat(availableCustomDimensions, ", ") .. ")")
- end
- function state:setAvailableCustomDimensions03(availableCustomDimensions)
- if not validation:validateCustomDimensions(availableCustomDimensions) then
- return
- end
- self._availableCustomDimensions03 = availableCustomDimensions
- logger:i("Set available custom03 dimension values: (" .. table.concat(availableCustomDimensions, ", ") .. ")")
- end
- function state:setEventSubmission(flag)
- self._enableEventSubmission = flag
- end
- function state:isEventSubmissionEnabled()
- return self._enableEventSubmission
- end
- function state:setCustomDimension01(playerId, dimension)
- local PlayerData = store.PlayerCache[playerId]
- PlayerData.CurrentCustomDimension01 = dimension
- end
- function state:setCustomDimension02(playerId, dimension)
- local PlayerData = store.PlayerCache[playerId]
- PlayerData.CurrentCustomDimension02 = dimension
- end
- function state:setCustomDimension03(playerId, dimension)
- local PlayerData = store.PlayerCache[playerId]
- PlayerData.CurrentCustomDimension03 = dimension
- end
- function state:startNewSession(player, teleportData)
- if state:isEventSubmissionEnabled() then
- logger:i("Starting a new session.")
- end
- local PlayerData = store.PlayerCache[player.UserId]
- -- make sure the current custom dimensions are valid
- state:validateAndFixCurrentDimensions(player.UserId)
- local initResult = http_api:initRequest(events.GameKey, events.SecretKey, PlayerData, player.UserId)
- local statusCode = initResult.statusCode
- local responseBody = initResult.body
- if statusCode == http_api.EGAHTTPApiResponse.Ok and responseBody then
- -- set the time offset - how many seconds the local time is different from servertime
- local timeOffsetSeconds = 0
- local serverTs = responseBody["server_ts"] or -1
- if serverTs > 0 then
- local clientTs = os.time()
- timeOffsetSeconds = serverTs - clientTs
- end
- responseBody["time_offset"] = timeOffsetSeconds
- PlayerData.SdkConfig = responseBody
- PlayerData.InitAuthorized = true
- elseif statusCode == http_api.EGAHTTPApiResponse.Unauthorized then
- logger:w("Initialize SDK failed - Unauthorized")
- PlayerData.InitAuthorized = false
- else
- -- log the status if no connection
- if statusCode == http_api.EGAHTTPApiResponse.NoResponse or statusCode == http_api.EGAHTTPApiResponse.RequestTimeout then
- logger:i("Init call (session start) failed - no response. Could be offline or timeout.")
- elseif statusCode == http_api.EGAHTTPApiResponse.BadResponse or statusCode == http_api.EGAHTTPApiResponse.JsonEncodeFailed or statusCode == http_api.EGAHTTPApiResponse.JsonDecodeFailed then
- logger:i("Init call (session start) failed - bad response. Could be bad response from proxy or GA servers.")
- elseif statusCode == http_api.EGAHTTPApiResponse.BadRequest or statusCode == http_api.EGAHTTPApiResponse.UnknownResponseCode then
- logger:i("Init call (session start) failed - bad request or unknown response.")
- end
- PlayerData.InitAuthorized = true
- end
- -- set offset in state (memory) from current config (config could be from cache etc.)
- PlayerData.ClientServerTimeOffset = PlayerData.SdkConfig["time_offset"] or 0
- -- populate configurations
- populateConfigurations(player)
- if not state:isEnabled(player.UserId) then
- logger:w("Could not start session: SDK is disabled.")
- return
- end
- if teleportData then
- PlayerData.SessionID = teleportData.SessionID
- PlayerData.SessionStart = teleportData.SessionStart
- else
- PlayerData.SessionID = HTTP:GenerateGUID(false):lower()
- PlayerData.SessionStart = getClientTsAdjusted(player.UserId)
- end
- if state:isEventSubmissionEnabled() then
- events:addSessionStartEvent(player.UserId, teleportData)
- end
- end
- function state:endSession(playerId)
- if state.Initialized and state:isEventSubmissionEnabled() then
- logger:i("Ending session.")
- if state:isEnabled(playerId) and state:sessionIsStarted(playerId) then
- events:addSessionEndEvent(playerId)
- store.PlayerCache[playerId] = nil
- end
- end
- end
- function state:getConfigurationStringValue(playerId, key, defaultValue)
- local PlayerData = store.PlayerCache[playerId]
- return PlayerData.Configurations[key] or defaultValue
- end
- function state:isCommandCenterReady(playerId)
- local PlayerData = store.PlayerCache[playerId]
- return PlayerData.CommandCenterIsReady
- end
- function state:getConfigurationsContentAsString(playerId)
- local PlayerData = store.PlayerCache[playerId]
- return HTTP:JSONEncode(PlayerData.Configurations)
- end
- return state
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBX4f67f666894641cf9fa8b191f2a175c9">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">Validation</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local validation = {}
- local logger = require(script.Parent.Logger)
- local utilities = require(script.Parent.Utilities)
- function validation:validateCustomDimensions(customDimensions)
- return validation:validateArrayOfStrings(20, 32, false, "custom dimensions", customDimensions)
- end
- function validation:validateDimension(dimensions, dimension)
- -- allow nil
- if utilities:isStringNullOrEmpty(dimension) then
- return true
- end
- if not utilities:stringArrayContainsString(dimensions, dimension) then
- return false
- end
- return true
- end
- function validation:validateResourceCurrencies(resourceCurrencies)
- if not validation:validateArrayOfStrings(20, 64, false, "resource currencies", resourceCurrencies) then
- return false
- end
- -- validate each string for regex
- for _,resourceCurrency in pairs(resourceCurrencies) do
- if not string.find(resourceCurrency, "^[A-Za-z]+$") then
- logger:w("resource currencies validation failed: a resource currency can only be A-Z, a-z. String was: " .. resourceCurrency)
- return false
- end
- end
- return true
- end
- function validation:validateResourceItemTypes(resourceItemTypes)
- if not validation:validateArrayOfStrings(20, 32, false, "resource item types", resourceItemTypes) then
- return false
- end
- -- validate each string for regex
- for _,resourceItemType in pairs(resourceItemTypes) do
- if not validation:validateEventPartCharacters(resourceItemType) then
- logger:w("resource item types validation failed: a resource item type cannot contain other characters than A-z, 0-9, -_., ()!?. String was: " .. resourceItemType)
- return false
- end
- end
- return true
- end
- function validation:validateEventPartCharacters(eventPart)
- if not string.find(eventPart, "^[A-Za-z0-9%s%-_%.%(%)!%?]+$") then
- return false
- end
- return true
- end
- function validation:validateArrayOfStrings(maxCount, maxStringLength, allowNoValues, logTag, arrayOfStrings)
- local arrayTag = logTag
- if not arrayTag then
- arrayTag = "Array"
- end
- -- use arrayTag to annotate warning log
- if not arrayOfStrings then
- logger:w(arrayTag .. " validation failed: array cannot be nil.")
- return false
- end
- -- check if empty
- if not allowNoValues and #arrayOfStrings == 0 then
- logger:w(arrayTag .. " validation failed: array cannot be empty.")
- return false
- end
- -- check if exceeding max count
- if maxCount > 0 and #arrayOfStrings > maxCount then
- logger:w(arrayTag .. " validation failed: array cannot exceed " .. tostring(maxCount) .. " values. It has " .. #arrayOfStrings .. " values.")
- return false
- end
- -- validate each string
- for _,arrayString in pairs(arrayOfStrings) do
- local stringLength = 0
- if arrayString then
- stringLength = #arrayString
- end
- -- check if empty (not allowed)
- if stringLength == 0 then
- logger:w(arrayTag .. " validation failed: contained an empty string.")
- return false
- end
- -- check if exceeding max length
- if maxStringLength > 0 and stringLength > maxStringLength then
- logger:w(arrayTag .. " validation failed: a string exceeded max allowed length (which is: " .. tostring(maxStringLength) .. "). String was: " .. arrayString)
- return false
- end
- end
- return true
- end
- function validation:validateBuild(build)
- if not validation:validateShortString(build, false) then
- return false
- end
- return true
- end
- function validation:validateShortString(shortString, canBeEmpty)
- -- String is allowed to be empty or nil
- if canBeEmpty and utilities:isStringNullOrEmpty(shortString) then
- return true
- end
- if utilities:isStringNullOrEmpty(shortString) or #shortString > 32 then
- return false
- end
- return true
- end
- function validation:validateKeys(gameKey, secretKey)
- if string.find(gameKey, "^[A-Za-z0-9]+$") and #gameKey == 32 then
- if string.find(secretKey, "^[A-Za-z0-9]+$") and #secretKey == 40 then
- return true
- end
- end
- return false
- end
- function validation:validateAndCleanInitRequestResponse(initResponse)
- -- make sure we have a valid dict
- if not initResponse then
- logger:w("validateInitRequestResponse failed - no response dictionary.")
- return nil
- end
- local validatedDict = {}
- -- validate enabled field
- validatedDict["enabled"] = initResponse["enabled"] or true
- -- validate server_ts
- local serverTsNumber = initResponse["server_ts"] or -1
- if serverTsNumber > 0 then
- validatedDict["server_ts"] = serverTsNumber
- end
- validatedDict["configurations"] = initResponse["configurations"] or {}
- return validatedDict
- end
- function validation:validateClientTs(clientTs)
- if clientTs < 1000000000 or clientTs > 9999999999 then
- return false
- end
- return true
- end
- function validation:validateCurrency(currency)
- if utilities:isStringNullOrEmpty(currency) then
- return false
- end
- if string.find(currency, "^[A-Z]+$") and #currency == 3 then
- return true
- end
- return false
- end
- function validation:validateEventPartLength(eventPart, allowNull)
- if allowNull and utilities:isStringNullOrEmpty(eventPart) then
- return true
- end
- if utilities:isStringNullOrEmpty(eventPart) then
- return false
- end
- if #eventPart == 0 or #eventPart > 64 then
- return false
- end
- return true
- end
- function validation:validateBusinessEvent(currency, amount, cartType, itemType, itemId)
- -- validate currency
- if not validation:validateCurrency(currency) then
- logger:w("Validation fail - business event - currency: Cannot be (null) and need to be A-Z, 3 characters and in the standard at openexchangerates.org. Failed currency: " .. currency)
- return false
- end
- if amount < 0 then
- logger:w("Validation fail - business event - amount: Cannot be less then 0. Failed amount: " .. amount)
- return false
- end
- -- validate cartType
- if not validation:validateShortString(cartType, true) then
- logger:w("Validation fail - business event - cartType. Cannot be above 32 length. String: " .. cartType)
- return false
- end
- -- validate itemType length
- if not validation:validateEventPartLength(itemType, false) then
- logger:w("Validation fail - business event - itemType: Cannot be (null), empty or above 64 characters. String: " .. itemType)
- return false
- end
- -- validate itemType chars
- if not validation:validateEventPartCharacters(itemType) then
- logger:w("Validation fail - business event - itemType: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " .. itemType)
- return false
- end
- -- validate itemId
- if not validation:validateEventPartLength(itemId, false) then
- logger:w("Validation fail - business event - itemId. Cannot be (null), empty or above 64 characters. String: " .. itemId)
- return false
- end
- if not validation:validateEventPartCharacters(itemId) then
- logger:w("Validation fail - business event - itemId: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " .. itemId)
- return false
- end
- return true
- end
- function validation:validateResourceEvent(flowTypeValues, flowType, currency, amount, itemType, itemId, currencies, itemTypes)
- if flowType ~= flowTypeValues.Source and flowType ~= flowTypeValues.Sink then
- logger:w("Validation fail - resource event - flowType: Invalid flow type " .. tostring(flowType))
- return false
- end
- if utilities:isStringNullOrEmpty(currency) then
- logger:w("Validation fail - resource event - currency: Cannot be (null)")
- return false
- end
- if not utilities:stringArrayContainsString(currencies, currency) then
- logger:w("Validation fail - resource event - currency: Not found in list of pre-defined available resource currencies. String: " .. currency)
- return false
- end
- if not (amount > 0) then
- logger:w("Validation fail - resource event - amount: Float amount cannot be 0 or negative. Value: " .. tostring(amount))
- return false
- end
- if utilities:isStringNullOrEmpty(itemType) then
- logger:w("Validation fail - resource event - itemType: Cannot be (null)")
- return false
- end
- if not validation:validateEventPartLength(itemType, false) then
- logger:w("Validation fail - resource event - itemType: Cannot be (null), empty or above 64 characters. String: " .. itemType)
- return false
- end
- if not validation:validateEventPartCharacters(itemType) then
- logger:w("Validation fail - resource event - itemType: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " .. itemType)
- return false
- end
- if not utilities:stringArrayContainsString(itemTypes, itemType) then
- logger:w("Validation fail - resource event - itemType: Not found in list of pre-defined available resource itemTypes. String: " .. itemType)
- return false
- end
- if not validation:validateEventPartLength(itemId, false) then
- logger:w("Validation fail - resource event - itemId: Cannot be (null), empty or above 64 characters. String: " .. itemId)
- return false
- end
- if not validation:validateEventPartCharacters(itemId) then
- logger:w("Validation fail - resource event - itemId: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " .. itemId)
- return false
- end
- return true
- end
- function validation:validateProgressionEvent(progressionStatusValues, progressionStatus, progression01, progression02, progression03)
- if progressionStatus ~= progressionStatusValues.Start and progressionStatus ~= progressionStatusValues.Complete and progressionStatus ~= progressionStatusValues.Fail then
- logger:w("Validation fail - progression event: Invalid progression status " .. tostring(progressionStatus))
- return false
- end
- -- Make sure progressions are defined as either 01, 01+02 or 01+02+03
- if not utilities:isStringNullOrEmpty(progression03) and not (not utilities:isStringNullOrEmpty(progression02) or utilities:isStringNullOrEmpty(progression01)) then
- logger:w("Validation fail - progression event: 03 found but 01+02 are invalid. Progression must be set as either 01, 01+02 or 01+02+03.")
- return false
- elseif not utilities:isStringNullOrEmpty(progression02) and utilities:isStringNullOrEmpty(progression01) then
- logger:w("Validation fail - progression event: 02 found but not 01. Progression must be set as either 01, 01+02 or 01+02+03")
- return false
- elseif utilities:isStringNullOrEmpty(progression01) then
- logger:w("Validation fail - progression event: progression01 not valid. Progressions must be set as either 01, 01+02 or 01+02+03")
- return false
- end
- -- progression01 (required)
- if not validation:validateEventPartLength(progression01, false) then
- logger:w("Validation fail - progression event - progression01: Cannot be (null), empty or above 64 characters. String: " .. progression01)
- return false
- end
- if not validation:validateEventPartCharacters(progression01) then
- logger:w("Validation fail - progression event - progression01: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " .. progression01)
- return false
- end
- -- progression02
- if not utilities:isStringNullOrEmpty(progression02) then
- if not validation:validateEventPartLength(progression02, false) then
- logger:w("Validation fail - progression event - progression02: Cannot be empty or above 64 characters. String: " .. progression02)
- return false
- end
- if not validation:validateEventPartCharacters(progression02) then
- logger:w("Validation fail - progression event - progression02: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " .. progression02)
- return false
- end
- end
- -- progression03
- if not utilities:isStringNullOrEmpty(progression03) then
- if not validation:validateEventPartLength(progression03, false) then
- logger:w("Validation fail - progression event - progression03: Cannot be empty or above 64 characters. String: " .. progression03)
- return false
- end
- if not validation:validateEventPartCharacters(progression03) then
- logger:w("Validation fail - progression event - progression03: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: " .. progression03)
- return false
- end
- end
- return true
- end
- function validation:validateEventIdLength(eventId)
- if utilities:isStringNullOrEmpty(eventId) then
- return false
- end
- local count = 0
- for s in string.gmatch(eventId, "([^:]+)") do
- count = count + 1
- if count > 5 then
- return false
- end
- if #s > 64 then
- return false
- end
- end
- return true
- end
- function validation:validateEventIdCharacters(eventId)
- if utilities:isStringNullOrEmpty(eventId) then
- return false
- end
- local count = 0
- for s in string.gmatch(eventId, "([^:]+)") do
- count = count + 1
- if count > 5 then
- return false
- end
- if not string.find(s, "^[A-Za-z0-9%s%-_%.%(%)!%?]+$") then
- return false
- end
- end
- return true
- end
- function validation:validateDesignEvent(eventId)
- if not validation:validateEventIdLength(eventId) then
- logger:w("Validation fail - design event - eventId: Cannot be (null) or empty. Only 5 event parts allowed seperated by :. Each part need to be 32 characters or less. String: " .. eventId)
- return false
- end
- if not validation:validateEventIdCharacters(eventId) then
- logger:w("Validation fail - design event - eventId: Non valid characters. Only allowed A-z, 0-9, -_., ()!?. String: " .. eventId)
- return false
- end
- -- value: allow 0, negative and nil (not required)
- return true
- end
- function validation:validateLongString(longString, canBeEmpty)
- -- String is allowed to be empty
- if canBeEmpty and utilities:isStringNullOrEmpty(longString) then
- return true
- end
- if utilities:isStringNullOrEmpty(longString) or #longString > 8192 then
- return false
- end
- return true
- end
- function validation:validateErrorEvent(severityValues, severity, message)
- if severity ~= severityValues.debug and severity ~= severityValues.info and severity ~= severityValues.warning and severity ~= severityValues.error and severity ~= severityValues.critical then
- logger:w("Validation fail - error event - severity: Severity was unsupported value " .. tostring(severity))
- return false
- end
- if not validation:validateLongString(message, true) then
- logger:w("Validation fail - error event - message: Message cannot be above 8192 characters.")
- return false
- end
- return true
- end
- return validation
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBX14584cc0d6704a73828d545fa96d87a7">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">Threading</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local threading = {
- _canSafelyClose = true,
- _endThread = false,
- _isRunning = false,
- _blocks = {},
- _scheduledBlock = nil,
- _hasScheduledBlockRun = true
- }
- local logger = require(script.Parent.Logger)
- local RunService = game:GetService("RunService")
- local function getScheduledBlock()
- local now = tick()
- if not threading._hasScheduledBlockRun and threading._scheduledBlock ~= nil and threading._scheduledBlock.deadline <= now then
- threading._hasScheduledBlockRun = true
- return threading._scheduledBlock
- else
- return nil
- end
- end
- local function run()
- spawn(function()
- logger:d("Starting GA thread")
- while not threading._endThread do
- threading._canSafelyClose = false
- if #threading._blocks ~= 0 then
- for _,b in pairs(threading._blocks) do
- pcall(function()
- b.block()
- end)
- end
- threading._blocks = {}
- end
- local timedBlock = getScheduledBlock()
- if timedBlock ~= nil then
- pcall(function()
- timedBlock.block()
- end)
- end
- threading._canSafelyClose = true
- wait(1)
- end
- logger:d("GA thread stopped")
- end)
- --Safely Close
- game:BindToClose(function()
- -- waiting bug fix to work inside studio
- if RunService:IsStudio() then
- return
- end
- --Give game.Players.PlayerRemoving time to to its thang
- wait(1)
- --Delay
- if not threading._canSafelyClose then
- repeat
- wait()
- until threading._canSafelyClose
- end
- wait(3)
- end)
- end
- function threading:scheduleTimer(interval, callback)
- if self._endThread then
- return
- end
- if not self._isRunning then
- self._isRunning = true
- run()
- end
- local timedBlock = {
- block = callback,
- deadline = tick() + interval
- }
- if self._hasScheduledBlockRun then
- self._scheduledBlock = timedBlock
- self._hasScheduledBlockRun = false
- end
- end
- function threading:performTaskOnGAThread(callback)
- if self._endThread then
- return
- end
- if not self._isRunning then
- self._isRunning = true
- run()
- end
- local timedBlock = {
- block = callback,
- }
- self._blocks[#self._blocks + 1] = timedBlock
- end
- function threading:stopThread()
- self._endThread = true
- end
- return threading
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBX99cd668c174048349f2ece6db3c8afd4">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">GAErrorSeverity</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local function readonlytable(table)
- return setmetatable({}, {
- __index = table,
- __newindex = function(t, k, v)
- error("Attempt to modify read-only table: " .. t .. ", key=" .. k .. ", value=" .. v)
- end,
- __metatable = false
- });
- end
- return readonlytable({
- debug = "debug";
- info = "info";
- warning = "warning";
- error = "error";
- critical = "critical";
- })
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBX9a762c40155746f09057ee57f778235c">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">GAProgressionStatus</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local function readonlytable(table)
- return setmetatable({}, {
- __index = table,
- __newindex = function(t, k, v)
- error("Attempt to modify read-only table: " .. t .. ", key=" .. k .. ", value=" .. v)
- end,
- __metatable = false
- });
- end
- return readonlytable({
- Start = "Start";
- Complete = "Complete";
- Fail = "Fail";
- })
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- <Item class="ModuleScript" referent="RBXeaa8109bf36e45c987ef4664347edc33">
- <Properties>
- <Content name="LinkedSource"><null></null></Content>
- <string name="Name">GAResourceFlowType</string>
- <string name="ScriptGuid"></string>
- <ProtectedString name="Source"><![CDATA[local function readonlytable(table)
- return setmetatable({}, {
- __index = table,
- __newindex = function(t, k, v)
- error("Attempt to modify read-only table: " .. t .. ", key=" .. k .. ", value=" .. v)
- end,
- __metatable = false
- });
- end
- return readonlytable({
- Source = "Source";
- Sink = "Sink";
- })
- ]]></ProtectedString>
- <BinaryString name="Tags"></BinaryString>
- </Properties>
- </Item>
- </Item>
- </Item>
- </roblox>
|