SessionID & UserID

Monkey Forums/Monkey Programming/SessionID & UserID

Tibit(Posted 2013) [#1]
Looking at GameAnalytics.

I need a good way to generate unique User & Session IDs.

NOTE: the User ID should stay the same for each player across play sessions; if possible, it should remain the same across different games.


NOTE: Unlike the User ID, the Session ID should change for every play session.


Does anyone have any idea on how to do this? Any modules that would help?

I can imagine it can be done if I used an online service for account management, but for simpler offline games and Apps that is not always an option.

Also need to get a "build" number for each platform.


c.k.(Posted 2013) [#2]
Can you grab the user's email and use that? Session IDs can just be a random string of numbers and letters, or a time stamp.

I don't think you'd need either for offline games and apps. The point of Unique IDs and Session IDs is so the server can track them over time. If there will be no online activity, these things aren't needed.


Tibit(Posted 2013) [#3]
Hah. Yeah obviously I did not mean to say "offline", what was I thinking? You are right! Kinda difficult to send analytics over the internet if offline. No I meant smaller games that don't use an account system = no email.

Mac Adress, Device ID, something like that.

I could use a timestamp + some random letters for SessionID.


c.k.(Posted 2013) [#4]
I've heard, but never confirmed, that some devices don't provide device IDs. So what you could do is generate a random string of numbers and digits to identify the device (something like, "UDH948sahd93HF848FHisudh847"). Ask the server if that already exists. If not, use it. Otherwise, regenerate the ID.

Save the unique device ID to the device locally, then send it with each message.

I think Apple at one time had device IDs, but they stopped using that because of security issues.


Tibit(Posted 2013) [#5]
Yeah, I'll generate a TimeStampID and save it to disk, then reuse that unless the app has an account sytem.

For future reference UUID generation: [required the Md5 library!]
	Method GetUniqueTimeID:String()
		Local date:= GetDate()
		Local months:=["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
		Local day:= ("0" + date[2])[ - 2 ..]
		Local month:=months[date[1]-1]
		Local year:=date[0]
		Local hour:=("0"+date[3])[-2..]
		Local min:=("0"+date[4])[-2..]
		Local sec:=("0"+date[5])[-2..] + "." + ("00"+date[6])[-3..]
		Local now:string = hour + ":" + min + ":" + sec + "  " + day + " " + month + " " + year
		Return MD5(now)
	End



Nobuyuki(Posted 2013) [#6]
generating a random string, with a magic number baked in, is probably a good way to ID someone. If you need/want something UIID/GUID compatible for your game for online purposes, you could also implement it server-side on first request to the client program, or grab one from an online service that provides this for you using HttpRequest. One such service: http://www.famkruithof.net/uuid/uuidgen


DGuy(Posted 2013) [#7]
I recently added iOS/Android GameAnalytics support to my Monkey game framework. I handled the generation of User & Session IDs, by accessing the platforms' native UUID APIs.

iOS Native Code:
// uuid.ios.cpp
String Device_GetUUID()
{
	CFUUIDRef	UUIDObj = CFUUIDCreate( NULL );
	CFStringRef	UUIDStr = CFUUIDCreateString( NULL, UUIDObj );

	int   len    = CFStringGetLength( UUIDStr );
	char* buffer = (char *)calloc( 1, (len + 1) );
	CFStringGetBytes( UUIDStr, CFRangeMake(0, len), kCFStringEncodingASCII, '0',
		false, (UInt8*)buffer, len, NULL );

	String UUID = String( buffer );

	free( buffer );	
	CFRelease( (CFTypeRef)UUIDStr );
	CFRelease( (CFTypeRef)UUIDObj );

	return UUID;
}


Android Native Code:
// uuid.android.java
import java.util.UUID;
import java.util.Locale;

class Device
{
	public static String GetUUID()
	{
		return UUID.randomUUID().toString().toUpperCase( Locale.US );
	}
}


Monkey Code:
' uuid.monkey
#if TARGET="ios" or TARGET="android"

import "native/device.${TARGET}.${LANG}"

EXTERN PRIVATE
#if TARGET="ios"
function Device_GetUUID$()
#else
class Device
	method GetUUID$()
end
PRIVATE
global _Device:= new Device
#end
#end '#if TARGET="ios" or TARGET="android"

PUBLIC
function deviceGetUUID$()
#if TARGET="ios"
	return Device_GetUUID
#elseif TARGET="android"
	return _Device.GetUUID
#else
	Error "deviceGetUUID() Only Available On iOS/Android Targets!"
#end
end



Tibit(Posted 2013) [#8]
That is awesome!

Do you have a full working version of your whole GameAnalytics?

I used their REST API and wrote it in Monkey-only code to be as crossplatform as possible. I can send design events but still now too feature packed.

I assume you wrapped their iOS & Android API's?


DGuy(Posted 2013) [#9]
I also use the REST API. :)

UUID & MD5 is via the iOS & Android native APIs, HTTP is via native 3rd-Party SDKs and JSON support is via Damian Sinclar's JSON module (https://code.google.com/p/monkey-json).

Even though I went native for UUID, MD5 & HTTP support, I believe there are Monkey-code-only equivalents for each.

My Monkey GameAnalytics module:
[monkeycode]
#rem
gameanalytics.monkey
#end
import gamebase.http
import gamebase.json
import gamebase.device
import gamebase.director

'###############################################################################################################
PRIVATE
const _BASE_URL := "http://api.gameanalytics.com/1/"
const _KEY_USER_ID := "GameAnalytics_UserId"
const _SESSION_RESUME_WINDOW := ((1000 * 60) * 5) 'How long after OnSuspend session id expires (5 minutes)

global _extBaseURL$ ' base url + gameId

global _gameKey$
global _secretKey$

global _buildId$

global _userId$
global _sessionId$

global _sessionResumeCutoff

global _reqIdList:IntList


'---------------------------------------------------------------------------------------------------------------
function _SubmitEvent:void( eventType$, eventId$, aux:JSONObject )
if httpIsOnline

' get/descard any previous request responses
for local rid:= eachin _reqIdList
if httpPoll( rid )
httpResponse( rid ) ' get & discard response
_reqIdList.Remove( rid )
end
end

' setup request body
Local body_json:= new JSONObject()
body_json.AddPrim( "event_id", eventId )
body_json.AddPrim( "user_id", _userId )
body_json.AddPrim( "build", _buildId )
body_json.AddPrim( "session_id", _sessionId )
if not (aux = NULL)
for local name:= eachin aux.Names
body_json.AddItem( name, aux.GetItem(name) )
end
end
local body_str:= body_json.ToJSONString

' setup request header
Local header:= new StringMap<String>
header.Add( "Content-Length", body_str.Length )
header.Add( "Content-Type", "application/json" )
header.Add( "Authorization", deviceGetMD5Hash( body_str + _secretKey ) )

' construct complete request url
local url:= _extBaseURL + eventType

' make the request & store request-id
_reqIdList.AddLast( httpPost( url, body_str, header ) )
end
end


'###############################################################################################################
PUBLIC
'---------------------------------------------------------------------------------------------------------------
function GA_Init:void( gameKey$, secretKey$, buildId$ )
if not gameKey Error "GA_Init: Game Key Is Empty"
if not secretKey Error "GA_Init: Secret Key Is Empty"
if not buildId Error "GA_Init: Build Id Is Empty"

_gameKey = gameKey
_secretKey = secretKey
_buildId = buildId

_sessionId = deviceGetUUID

' NOTE: The user-id will persit until the app is uninstalled. On iOS, the id does
' have the potential to follow the user across device updates.

_userId = gReg.GetString( _KEY_USER_ID, deviceGetUUID )
gReg.SetString( _KEY_USER_ID, _userId, true )

_extBaseURL = _BASE_URL + _gameKey + "/"

_reqIdList = new IntList
end


'---------------------------------------------------------------------------------------------------------------
function GA_Suspend:void()
_sessionResumeCutoff = Millisecs + _SESSION_RESUME_WINDOW
end


'---------------------------------------------------------------------------------------------------------------
function GA_Resume:void()
if _sessionResumeCutoff < Millisecs _sessionId = deviceGetUUID
end


'---------------------------------------------------------------------------------------------------------------
function GA_User:void( eventId$, aux:JSONObject=NULL )
_SubmitEvent( "user", eventId, aux )
end

function GA_Design:void( eventId$, aux:JSONObject=NULL )
_SubmitEvent( "design", eventId, aux )
end

function GA_Quality:void( eventId$, aux:JSONObject=NULL )
_SubmitEvent( "quality", eventId, aux )
end

function GA_Business:void( eventId$, aux:JSONObject=NULL )
_SubmitEvent( "business", eventId, aux )
end
[/monkeycode]
I hope it all self-explanatory and easy to follow .. :)

NOTES:
- 'gReg' wraps a StringMap<String> that gets written-to/read-from the state (via SaveState/LoadState) during OnSuspend/ OnCreate
- I don't care about the POST responses which is why I just discard them
- I use 5 mins for the SessionID expire because that's how long a stretch of inactivity the GameAnalytics RealTime Dashboard waits before it considers a session to have ended.


muruba(Posted 2014) [#10]
Thanks David, great code, I also added a js/html5 version:

//  uuid.html5.js
function Device_GetUUID() {
	var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
		var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
		return v.toString(16);
		});
	return guid;
}


(c) http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript


muruba(Posted 2014) [#11]
' uuid.monkey
#if TARGET="ios" or TARGET="android" or TARGET="html5"

	import "native/uuid.${TARGET}.${LANG}"

	EXTERN PRIVATE

	#if TARGET="ios"

		function Device_GetUUID$()

	#elseif TARGET="android"

		class Device
			method GetUUID$()
		end

		PRIVATE

		global _Device:= new Device
		
	#elseif TARGET="html5"
		function Device_GetUUID$()
	#end

#end 

PUBLIC

function deviceGetUUID$()

#if TARGET="ios"
	return Device_GetUUID
#elseif TARGET="android"
	return _Device.GetUUID
#elseif TARGET="html5"
	return Device_GetUUID()
#else
	Error "deviceGetUUID() Only Available On iOS/Android/HTML5 Targets!"
#end
end




SLotman(Posted 2014) [#12]
Didn't Apple forbid apps that uses UUID? There was even some trouble with Admob because of that...


Tibit(Posted 2014) [#13]
Yes, you are not allowed to get a unique device ID, such as the previous well known DeviceID or the MacAddress. However many still call the unique identifier for UUID. Sometimes this means connecting to a website, if the App has login use the email or you could just randomize a number and save it in a file - however that UUID will be different even for the same user on their Pad compared to their phone.

Does anyone have a full updated working project for GameAnalytics? Looks like the above is missing a lot of code? GitHub/BitBucket link?