Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- GoogleAnalytics4.xml -->
- <!-- ************************************ -->
- <!-- Copyright 2021-2021 FutureSoft, Inc. -->
- <!-- ************************************ -->
- <component name="GoogleAnalytics4" extends="Task">
- <interface>
- <field id="RokuAnalyticsPlayerTitle" type="string" />
- <field id="RokuAnalyticsPlayerState" type="string" />
- <field id="RokuAnalyticsEventCategory" type="string" />
- <field id="RokuAnalyticsEventValue" type="string" />
- <field id="RokuAnalyticsLoginUserID" type="string" />
- <!-- Public -->
- <function name="initialize" />
- <function name="setConfig" />
- <function name="setOptions" />
- <function name="start" />
- <function name="logEvent" />
- <function name="logScreenView" />
- <function name="setUserProperties" />
- <function name="setCurrentScreen" />
- <function name="setUserId" />
- <function name="resetAnalyticsData" />
- <field id="httpMethod" type="string" />
- <field id="httpUrl" type="string" />
- </interface>
- <script type="text/brightscript" uri="GoogleAnalytics4.brs" />
- </component>
- <!-- ************************************
- Usage
- Bunch of functions used by many screens:
- <script type="text/brightscript" uri="pkg:/components/compUtils.brs" />
- Containing:
- Function utilPostAnalytics(passedTitle, passedState, passedCategory, passedUserID, passedEvtCnt)
- ?"utilPostAnalytics()", passedTitle, passedState, passedCategory, passedUserID
- m.GoogleAnalytics4Obj = createObject("roSGNode", "GoogleAnalytics4")
- m.GoogleAnalytics4Obj.setField("RokuAnalyticsPlayerTitle", passedTitle)
- m.GoogleAnalytics4Obj.setField("RokuAnalyticsPlayerState", passedState)
- m.GoogleAnalytics4Obj.setField("RokuAnalyticsEventValue", passedEvtCnt)
- m.GoogleAnalytics4Obj.setField("RokuAnalyticsEventCategory", passedCategory)
- m.GoogleAnalytics4Obj.setField("RokuAnalyticsLoginUserID", passedUserID)
- 'STOP
- m.GoogleAnalytics4Obj.control = "RUN"
- End Function
- Examples:
- sTmpClientID = WhateverCustomerIDYouAssign
- sTmpEventCnt = 1
- utilPostAnalytics(m.videoPlayer.content.Title, "start", "media player", sTmpClientID, sTmpEventCnt)
- sTmpClientID = WhateverCustomerIDYouAssign
- sTmpEventCnt = m.sixtySecTimePassed / 60
- utilPostAnalytics(m.videoPlayer.content.Title, "playing", "media player", sTmpClientID, sTmpEventCnt)
- sTmpClientID = WhateverCustomerIDYouAssign
- sTmpEventCnt = m.sixtySecTimePassed / 60
- utilPostAnalytics(m.audioPlayer.content.Title, "finished", "media player", sTmpClientID, sTmpEventCnt)
- ************************************ -->
- <!-- ************************************
- examples debug output sent to Google...
- [GoogleAnalytics4] https://analytics.google.com/g/collect
- ?sid=1611237152&_p=1490261140&_s=1&seg=1&ec=media_player&sr=1280x720&uid=1%3A999999999999%3Aweb%3A7777777777777777777777
- &v=2&en=start&sct=279&cid=420d8a2a-d34b-5dd6-9d2f-4305921dacb3&upn.first_open_time=1610744400000
- &dl=https%3A%2F%2FBIGQUERY-DBNAME.firebaseio.com&av=4.0.9&custom=unset&ea=start&up.userPropA=USERPROPA
- &tid=G-ABCDEFGHIJKLMNOP&ul=en_us&cd=media_player&up.id=1%3A999999999999%3Aweb%3A7777777777777777777777
- &el=Live%20Stream%20-%20Auf%20Deutsch&an=RokuApp
- POST https://analytics.google.com/g/collect
- ?_p=406324644&_s=1&seg=1&sr=1280x720&uid=1%3A999999999999%3Aweb%3A7777777777777777777777&v=2&en=start
- &cid=420d8a2a-d34b-5dd6-9d2f-4305921dacb3&upn.first_open_time=1610744400000&dl=https%3A%2F%2FBIGQUERY-DBNAME.firebaseio.com
- &av=4.0.9&up.userPropA=SET%20USER%20PROPERTIES%20HERE&tid=G-ABCDEFGHIJKLMNOABCDEFGHIJKLMNOP&ul=en_us
- &up.id=1%3A999999999999%3Aweb%3A7777777777777777777777&EXAMPLEPROPERTY=HERE%20YOU%20CAN%20PUT%20ANY%20CUSTOM%20ARG...
- &an=RokuApp
- ************************************ -->
- ' GoogleAnalytics4.brs
- ' ************************************
- ' Copyright 2021-2021 FutureSoft, Inc.
- ' ************************************
- ' see https://github.com/vixtech/roku-google-analytics-4
- ' Implementation based on https://gist.github.com/IjzerenHein/df3f65093038dd70ad871f926be6f45e
- sub Init()
- ?"[GoogleAnalytics4] Init()"
- initAnalytics()
- end sub
- sub initAnalytics()
- m.endpoint = "https://analytics.google.com/g/collect"
- m.config = invalid
- m.options = invalid
- m.codedUserProperties = {}
- m.userId = invalid
- m.screenName = invalid
- m.lastEventTime = invalid
- m.sessionHitsCount = 0
- 'm.top.functionName = "startTask"
- m.top.functionName = "doLogAnalyticsEvent"
- m.userEngagementIntervalSeconds = 30
- 'STOP
- 'Not used
- 'm.timer = CreateObject("roSgnode", "Timer")
- 'm.timer.observeField("fire","logUserEngagement")
- 'm.timer.duration = m.userEngagementIntervalSeconds
- 'm.timer.repeat = true
- myMeasurementId = "G-ABCDEFGHIJKLMNOP"
- myUserId = "1:999999999999:web:7777777777777777777777" 'NOT NEEDED?
- initObj = {
- appName: "RokuAppName"
- measurementId: myMeasurementId
- docLocation: "https://BIGQUERY-DBNAME.firebaseio.com"
- 'customArgs: { "custom": "unset" }
- userId: myUserId
- 'userProperties: { "userPropA": "USERPROPA" }
- }
- initialize(initObj)
- customArgs = m.options.customArgs
- gaSessionId = getGaSessionId()
- gaSessionNumber = getGaSessionNumber()
- customArgs["sid"] = gaSessionId
- customArgs["sct"] = gaSessionNumber
- options = m.options
- options.customArgs = customArgs
- m.options = options
- m.pendingSessionStart = true
- m.pendingFirstVisit = false
- if isFirstOpen()
- m.pendingFirstVisit = true
- end if
- end sub
- function initialize(params as object)
- ' Analytics stuff
- appInfo = CreateObject("roAppInfo")
- deviceInfo = CreateObject("roDeviceInfo")
- displaySize = deviceInfo.GetDisplaySize()
- screenRes = invalid
- if displaySize <> invalid and displaySize.w <> invalid and displaySize.h <> invalid
- screenRes = Str(displaySize.w).Trim() + "x" + Str(displaySize.h).Trim()
- end if
- 'STOP
- setConfig({
- measurementId: params.measurementId
- })
- setOptions({
- appName: params.appName
- appVersion: appInfo.GetVersion()
- screenRes: screenRes
- clientId: deviceInfo.GetChannelClientId()
- docLocation: params.docLocation
- userLanguage: deviceInfo.GetCurrentLocale()
- customArgs: params.customArgs
- isFirstOpen: params.isFirstOpen
- })
- if params.userId <> invalid
- setUserId(params.userId)
- end if
- if params.userProperties <> invalid
- setUserProperties(params.userProperties)
- end if
- end function
- function setConfig(config as object)
- ' Config Properties
- ' - measurementId
- m.config = config
- end function
- sub setOptions(options as object)
- ' Options Properties
- ' - clientId
- ' - docTitle
- ' - docLocation
- ' - screenRes
- ' - appName
- ' - appVersion
- ' - userLanguage
- ' - origin
- ' - customArgs
- if options.customArgs = invalid
- options.customArgs = {}
- end if
- m.options = options
- end sub
- sub startTask()
- ' The only async method is "sendHttpRequest"
- ? "ANALYTICS: The only async method is sendHttpRequest"
- 'STOP
- end sub
- sub doLogAnalyticsEvent()
- ?m.top
- 'STOP
- logEvent(m.top.RokuAnalyticsEventCategory, {})
- end sub
- sub OnChangeAnalyticsDoEvent()
- ?m.top
- 'STOP
- 'logEvent("page_view", {})
- logEvent(m.top.RokuAnalyticsPlayerState, {})
- end sub
- function start()
- customArgs = m.options.customArgs
- 'STOP
- gaSessionId = getGaSessionId()
- gaSessionNumber = getGaSessionNumber()
- customArgs["sid"] = gaSessionId
- customArgs["sct"] = gaSessionNumber
- options = m.options
- options.customArgs = customArgs
- m.options = options
- m.pendingSessionStart = true
- m.pendingFirstVisit = false
- if isFirstOpen()
- m.pendingFirstVisit = true
- end if
- logEvent("page_view", {})
- 'm.timer.control = "start"
- end function
- function getGaSessionId() as integer
- return CreateObject("roDateTime").AsSeconds()
- end function
- function getGaSessionNumber() as integer
- registry = getRegistry()
- lastSessionNumber = registry.read("lastSessionNumber")
- newSessionNumber = 1
- if lastSessionNumber <> invalid and lastSessionNumber <> ""
- newSessionNumber = lastSessionNumber.toInt() + 1
- end if
- registry.write("lastSessionNumber", newSessionNumber.toStr())
- return newSessionNumber
- end function
- function getFirstOpenTimeSeconds() as integer
- registry = getRegistry()
- firstOpenTimeSeconds = registry.read("firstOpenTimeSeconds")
- if firstOpenTimeSeconds <> "" and firstOpenTimeSeconds <> invalid
- return firstOpenTimeSeconds.toInt()
- end if
- firstOpenTimeSeconds = CreateObject("roDateTime").AsSeconds()
- registry.write("firstOpenTimeSeconds", firstOpenTimeSeconds.toStr())
- return firstOpenTimeSeconds
- end function
- function isFirstOpen() as boolean
- if m.options.isFirstOpen <> invalid
- return m.options.isFirstOpen
- end if
- registry = getRegistry()
- if registry.exists("firstOpenMark")
- return false
- else
- registry.write("firstOpenMark", "ok")
- return true
- end if
- end function
- function getRegistry() as object
- return CreateObject("roRegistrySection", "googleanalytics")
- end function
- sub logUserEngagement()
- logEvent("user_engagement", {
- _et: m.userEngagementIntervalSeconds * 1000
- })
- logEvent("app_time", {
- time_difference: m.userEngagementIntervalSeconds * 1000
- })
- end sub
- function send(codedEvent as Object)
- nowTime = CreateObject("roDateTime")
- queryArgs = {}
- if m.options.customArgs <> invalid
- for each key in m.options.customArgs
- queryArgs[key] = m.options.customArgs[key]
- end for
- end if
- m.sessionHitsCount = m.sessionHitsCount + 1
- queryArgs.v = 2
- queryArgs.tid = m.config.measurementId
- queryArgs.cid = m.options.clientId
- queryArgs._p = Rnd(CreateObject("roDateTime").AsSeconds())
- queryArgs._s = m.sessionHitsCount
- if m.options.userLanguage <> invalid
- queryArgs.ul = LCase(m.options.userLanguage)
- end if
- if m.options.appName <> invalid
- queryArgs.an = m.options.appName
- end if
- if m.options.appVersion <> invalid
- queryArgs.av = m.options.appVersion
- end if
- if m.options.docTitle <> invalid
- queryArgs.dt = m.options.docTitle
- end if
- if m.options.docLocation <> invalid
- queryArgs.dl = m.options.docLocation
- end if
- if m.options.screenRes <> invalid
- queryArgs.sr = m.options.screenRes
- end if
- if codedEvent.en = "page_view"
- queryArgs.seg = 0
- if m.pendingFirstVisit = true
- queryArgs._fv = 2
- m.pendingFirstVisit = false
- end if
- if m.pendingSessionStart = true
- queryArgs._ss = 2
- m.pendingSessionStart = false
- end if
- else
- queryArgs.seg = 1
- end if
- codedEvent = addFSFireBaseFields(codedEvent, queryArgs) 'slc added
- for each key in codedEvent
- queryArgs[key] = codedEvent[key]
- end for
- queryParts = []
- for each key in queryArgs
- queryParts.Push(key.EncodeUriComponent() + "=" + convertToString(queryArgs[key]).EncodeUriComponent())
- end for
- 'STOP
- ?queryArgs
- queryString = queryParts.Join("&")
- m.lastEventTime = nowTime
- 'STOP
- doSendHttpRequest(m.endpoint, queryString)
- return(0)
- 'NOT USED
- request = CreateObject("roSGNode", "GoogleAnalytics4")
- request.functionName = "sendHttpRequest"
- request.httpMethod = "POST"
- request.httpUrl = m.endpoint + "?" + queryString
- ?"[GoogleAnalytics4] url:" m.endpoint
- ?"[GoogleAnalytics4] post:" queryString
- request.control = "RUN"
- end function
- function addFSFireBaseFields(codedEvent, queryArgs)
- codedEvent["ep.origin"] = "firebase"
- codedEvent["ep.action"] = m.top.RokuAnalyticsPlayerState
- codedEvent["_s"] = m.top.RokuAnalyticsEventValue
- codedEvent["ec"] = m.top.RokuAnalyticsEventCategory.Replace(" ", "_")
- codedEvent["dt"] = m.top.RokuAnalyticsPlayerTitle
- codedEvent["ep.label"] = m.top.RokuAnalyticsPlayerTitle
- codedEvent["epn.value"] = "1"
- codedEvent["cd"] = m.top.RokuAnalyticsEventCategory.Replace(" ", "_")
- codedEvent["uid"] = m.top.RokuAnalyticsLoginUserID
- codedEvent["up.aid"] = "appIDhere"
- codedEvent["up.an"] = queryArgs.an
- codedEvent["up.av"] = queryArgs.av
- codedEvent["up.ds"] = "roku"
- codedEvent["ds"] = "roku"
- codedEvent["z"] = queryArgs._p
- return(codedEvent)
- end function
- Function doSendHttpRequest(sPostEndPt, sPostData)
- ?"[GoogleAnalytics4] url:";sPostEndPt
- ?"[GoogleAnalytics4] dat:";sPostData
- di=createobject("roDeviceInfo")
- tmpVer=di.GetOsVersion()
- 'STOP
- verMajor=tmpVer["major"]
- verMinor=tmpVer["minor"]
- verBuild=tmpVer["build"]
- 'STOP
- myDVPstr = "Roku/DVP-"+verMajor+"."+verMinor+" ("+di.GetModel()+")"
- myUserAgent = "Mozilla/5.0 (RokuOS) AppleWebKit/537.36 (KHTML, like Gecko) " + myDVPstr
- method = "POST"
- url = sPostEndPt + "?" + sPostData
- port = CreateObject("roMessagePort")
- xfer = CreateObject("roUrlTransfer")
- xfer.SetCertificatesFile("common:/certs/ca-bundle.crt")
- 'Examples
- 'xfer.AddHeader("user-agent", "Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0" )
- 'xfer.AddHeader("user-agent", "Mozilla/5.0 (compatible; Roku OS) AppleWebKit/533.3 (KHTML, like Gecko) Qt/4.7.0 Safari/533.3 Netflix/3.2 (DEVTYPE=RKU-42XXX-; CERTVER=0) QtWebKit/2.2, Roku 3/7.0 (Roku, 4200X, Wireless)")
- 'xfer.AddHeader("user-agent", "Mozilla/5.0 (Windows 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Roku/DVP (147.53E04068A)")
- 'xfer.AddHeader("user-agent", "Mozilla/5.0 (Windows 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Roku/DVP (147.53E04068A)")
- 'xfer.AddHeader("user-agent", "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543 Safari/419.3")
- 'xfer.AddHeader("user-agent", "Mozilla/5.0 (Android 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 Roku/DVP")
- 'xfer.AddHeader("user-agent", "Mozilla/5.0 (Roku 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 Roku/DVP")
- 'xfer.AddHeader("user-agent", "Mozilla/5.0 (RokuOS) AppleWebKit/537.36 (KHTML, like Gecko) Roku/DVP")
- 'xfer.AddHeader("user-agent", "Mozilla/5.0 (compatible; U; NETFLIX) AppleWebKit/533.3 (KHTML, like Gecko) Qt/4.7.0 Safari/533.3 Netflix/3.2 (DEVTYPE=RKU-42XXX-; CERTVER=0) QtWebKit/2.2, Roku 3/7.0 (Roku, 4200X, Wireless)")
- xfer.AddHeader("user-agent", myUserAgent)
- 'STOP
- xfer.SetMessagePort(port)
- xfer.SetRequest(method)
- xfer.SetUrl(url)
- ?"[GoogleAnalytics4] all:";url
- requestSent = xfer.AsyncPostFromString("")
- if (requestSent)
- msg = wait(0, port)
- if (type(msg) = "roUrlEvent")
- statusCode = msg.GetResponseCode()
- if statusCode < 200 or statusCode > 299
- ?"ANALYTICS: ERROR - " msg.GetFailureReason()
- 'STOP
- end if
- end if
- end if
- end function
- function sendHttpRequest() 'see above
- method = m.top.httpMethod
- url = m.top.httpUrl
- port = CreateObject("roMessagePort")
- request = CreateObject("roUrlTransfer")
- request.SetCertificatesFile("common:/certs/ca-bundle.crt")
- request.SetMessagePort(port)
- request.SetRequest(method)
- request.SetUrl(url)
- requestSent = request.AsyncPostFromString("")
- if (requestSent)
- msg = wait(0, port)
- if (type(msg) = "roUrlEvent")
- statusCode = msg.GetResponseCode()
- if statusCode < 200 or statusCode > 299
- ? "ANALYTICS: ERROR - " msg.GetFailureReason()
- 'STOP
- end if
- end if
- end if
- end function
- function parseEvent(eventName as String, eventParams as Object) as object
- codedEvent = {
- en: eventName.Replace(" ", "_")
- }
- if m.options.origin <> invalid
- codedEvent["ep.origin"] = m.options.origin
- end if
- for each key in eventParams
- value = eventParams[key]
- if value <> invalid
- codedKey = "ep." + key
- codedValue = value
- if GetInterface(value, "ifInt") <> invalid or GetInterface(value, "ifFloat") <> invalid or GetInterface(value, "ifDouble") <> invalid
- codedKey = "epn." + key
- end if
- if Type(codedValue) = "roInt" Or Type(codedValue) = "roInteger"
- if key.Instr("_") = -1
- ' convert integers to double (for custom parameters only)
- codedValue = Cdbl(codedValue)
- end if
- end if
- if key = "ec" or key = "_et"
- codedKey = key
- end if
- if key = "currency"
- codedKey = "cu"
- end if
- codedEvent[codedKey] = codedValue
- end if
- end for
- return codedEvent
- end function
- sub logEvent(eventName as String, eventParams as Object)
- 'STOP
- codedEvent = parseEvent(eventName, eventParams)
- if m.userId <> invalid and m.userId <> ""
- codedEvent.uid = m.userId
- end if
- if m.screenName <> invalid and m.screenName <> ""
- codedEvent["ep.screen_name"] = m.screenName
- end if
- if m.codedUserProperties <> invalid
- for each key in m.codedUserProperties
- codedEvent[key] = m.codedUserProperties[key]
- end for
- end if
- send(codedEvent)
- end sub
- sub logScreenView(screenName as String)
- params = {
- firebase_screen: screenName
- }
- if m.screenName <> invalid and m.screenName <> ""
- params.firebase_previous_screen = m.screenName
- end if
- if m.screenName <> screenName
- setCurrentScreen(screenName)
- logEvent("screen_view", params)
- end if
- end sub
- function parseUserProperties(userProperties as Object) as Object
- codedUserProperties = {}
- for each key in userProperties
- value = userProperties[key]
- if value <> invalid
- if key.Instr("up.") = 0 or key.Instr("upn.") = 0
- ' already coded
- codedKey = key
- else
- codedKey = "up." + key
- if GetInterface(value, "ifInt") <> invalid or GetInterface(value, "ifFloat") <> invalid or GetInterface(value, "ifDouble") <> invalid
- codedKey = "upn." + key
- end if
- end if
- codedUserProperties[codedKey] = value
- end if
- end for
- return codedUserProperties
- end function
- sub setUserProperties(userProperties as Object)
- m.codedUserProperties = parseUserProperties(userProperties)
- firstOpenTimeSeconds = getFirstOpenTimeSeconds()
- m.codedUserProperties["up.id"] = m.userId
- m.codedUserProperties["upn.first_open_time"] = (roundFloatToInteger(firstOpenTimeSeconds / 3600) * 3600).ToStr() + "000"
- end sub
- sub setUserId(userId)
- m.userId = userId
- end sub
- sub setCurrentScreen(screenName as String)
- m.screenName = screenName
- end sub
- sub resetAnalyticsData ()
- m.screenName = invalid
- m.userId = invalid
- m.codedUserProperties = invalid
- end sub
- function convertToString(variable As Dynamic) As String
- if GetInterface(variable, "ifIntOps") <> invalid then
- return variable.ToStr()
- else if Type(variable) = "roInt" Or Type(variable) = "roInteger" Then
- return Str(variable).Trim()
- else if Type(variable) = "roFloat" Or Type(variable) = "Float" Then
- strValue = Str(variable).Trim()
- if strValue.Instr(".") = -1
- strValue = strValue + ".0"
- end if
- return strValue
- else if Type(variable) = "roBoolean" Or Type(variable) = "Boolean" Then
- if variable = True Then
- return "true"
- end If
- return "false"
- else if Type(variable) = "roString" Or Type(variable) = "String" Then
- Return variable
- else if variable = invalid then
- return ""
- else
- return Type(variable)
- end if
- end function
- function roundFloatToInteger(number as Float) as Integer
- truncateValue = Fix(number)
- decimalValue = number - truncateValue
- if decimalValue > 0.5
- return truncateValue + 1
- else
- return truncateValue
- end if
- end function
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement