Funkin/source/io/newgrounds/NGLite.hx
2020-11-01 14:16:22 -05:00

287 lines
No EOL
8.4 KiB
Haxe

package io.newgrounds;
import haxe.crypto.Base64;
import haxe.io.Bytes;
import haxe.PosInfos;
import io.newgrounds.Call.ICallable;
import io.newgrounds.components.ComponentList;
import io.newgrounds.crypto.EncryptionFormat;
import io.newgrounds.crypto.Cipher;
import io.newgrounds.crypto.Rc4;
import io.newgrounds.objects.Error;
import io.newgrounds.objects.events.Response;
import io.newgrounds.objects.events.Result.ResultBase;
import io.newgrounds.objects.events.Result.SessionResult;
import io.newgrounds.utils.Dispatcher;
#if !(html5 || flash || desktop || neko)
#error "Target not supported, use: Flash, JS/HTML5, cpp or maybe neko";
#end
/**
* The barebones NG.io API. Allows API calls with code completion
* and retrieves server data via strongly typed Objects
*
* Contains many things ripped from MSGhero's repo
* - https://github.com/MSGhero/NG.hx
*
* @author GeoKureli
*/
class NGLite {
static public var core(default, null):NGLite;
static public var onCoreReady(default, null):Dispatcher = new Dispatcher();
/** Enables verbose logging */
public var verbose:Bool;
public var debug:Bool;
/** The unique ID of your app as found in the 'API Tools' tab of your Newgrounds.com project. */
public var appId(default, null):String;
/** The name of the host the game is being played on */
public var host:String;
@:isVar
public var sessionId(default, set):String;
function set_sessionId(value:String):String {
return this.sessionId = value == "" ? null : value;
}
/** Components used to call the NG server directly */
public var calls(default, null):ComponentList;
/**
* Converts an object to an encrypted string that can be decrypted by the server.
* Set your preffered encrypter here,
* or just call setDefaultEcryptionHandler with your app's encryption settings
**/
public var encryptionHandler:String->String;
/**
* Iniitializes the API, call before utilizing any other component
* @param appId The unique ID of your app as found in the 'API Tools' tab of your Newgrounds.com project.
* @param sessionId A unique session id used to identify the active user.
**/
public function new(appId = "test", sessionId:String = null, ?onSessionFail:Error->Void) {
this.appId = appId;
this.sessionId = sessionId;
calls = new ComponentList(this);
if (this.sessionId != null) {
calls.app.checkSession()
.addDataHandler(checkInitialSession.bind(onSessionFail))
.addErrorHandler(initialSessionFail.bind(onSessionFail))
.send();
}
}
function checkInitialSession(onFail:Error->Void, response:Response<SessionResult>):Void {
if (!response.success || !response.result.success || response.result.data.session.expired) {
initialSessionFail(onFail, response.success ? response.result.error : response.error);
}
}
function initialSessionFail(onFail:Error->Void, error:Error):Void {
sessionId = null;
if (onFail != null)
onFail(error);
}
/**
* Creates NG.core, the heart and soul of the API. This is not the only way to create an instance,
* nor is NG a forced singleton, but it's the only way to set the static NG.core.
**/
static public function create(appId = "test", sessionId:String = null, ?onSessionFail:Error->Void):Void {
core = new NGLite(appId, sessionId, onSessionFail);
onCoreReady.dispatch();
}
/**
* Creates NG.core, and tries to create a session. This is not the only way to create an instance,
* nor is NG a forced singleton, but it's the only way to set the static NG.core.
**/
static public function createAndCheckSession
( appId = "test"
, backupSession:String = null
, ?onSessionFail:Error->Void
):Void {
var session = getSessionId();
if (session == null)
session = backupSession;
create(appId, session, onSessionFail);
}
inline static public function getUrl():String {
#if html5
return js.Browser.document.location.href;
#elseif flash
return flash.Lib.current.stage.loaderInfo != null
? flash.Lib.current.stage.loaderInfo.url
: null;
#else
return null;
#end
}
static public function getSessionId():String {
#if html5
var url = getUrl();
// Check for URL params
var index = url.indexOf("?");
if (index != -1) {
// Check for session ID in params
for (param in url.substr(index + 1).split("&")) {
index = param.indexOf("=");
if (index != -1 && param.substr(0, index) == "ngio_session_id")
return param.substr(index + 1);
}
}
#elseif flash
if (flash.Lib.current.stage.loaderInfo != null
&& Reflect.hasField(flash.Lib.current.stage.loaderInfo.parameters, "ngio_session_id"))
return Reflect.field(flash.Lib.current.stage.loaderInfo.parameters, "ngio_session_id");
#end
return null;
// --- EXAMPLE LOADER PARAMS
//{ "1517703669" : ""
//, "ng_username" : "GeoKureli"
//, "NewgroundsAPI_SessionID" : "F1LusbG6P8Qf91w7zeUE37c1752563f366688ac6153996d12eeb111a2f60w2xn"
//, "NewgroundsAPI_PublisherID" : 1
//, "NewgroundsAPI_UserID" : 488329
//, "NewgroundsAPI_SandboxID" : "5a76520e4ae1e"
//, "ngio_session_id" : "0c6c4e02567a5116734ba1a0cd841dac28a42e79302290"
//, "NewgroundsAPI_UserName" : "GeoKureli"
//}
}
// -------------------------------------------------------------------------------------------
// CALLS
// -------------------------------------------------------------------------------------------
var _queuedCalls:Array<ICallable> = new Array<ICallable>();
var _pendingCalls:Array<ICallable> = new Array<ICallable>();
@:allow(io.newgrounds.Call)
@:generic
function queueCall<T:ResultBase>(call:Call<T>):Void {
logVerbose('queued - ${call.component}');
_queuedCalls.push(call);
checkQueue();
}
@:allow(io.newgrounds.Call)
@:generic
function markCallPending<T:ResultBase>(call:Call<T>):Void {
_pendingCalls.push(call);
call.addDataHandler(function (_):Void { onCallComplete(call); });
call.addErrorHandler(function (_):Void { onCallComplete(call); });
}
function onCallComplete(call:ICallable):Void {
_pendingCalls.remove(call);
checkQueue();
}
function checkQueue():Void {
if (_pendingCalls.length == 0 && _queuedCalls.length > 0)
_queuedCalls.shift().send();
}
// -------------------------------------------------------------------------------------------
// LOGGING / ERRORS
// -------------------------------------------------------------------------------------------
/** Called internally, set this to your preferred logging method */
dynamic public function log(any:Dynamic, ?pos:PosInfos):Void {//TODO: limit access via @:allow
haxe.Log.trace('[Newgrounds API] :: ${any}', pos);
}
/** used internally, logs if verbose is true */
inline public function logVerbose(any:Dynamic, ?pos:PosInfos):Void {//TODO: limit access via @:allow
if (verbose)
log(any, pos);
}
/** Used internally. Logs by default, set this to your preferred error handling method */
dynamic public function logError(any:Dynamic, ?pos:PosInfos):Void {//TODO: limit access via @:allow
log('Error: $any', pos);
}
/** used internally, calls log error if the condition is false. EX: if (assert(data != null, "null data")) */
inline public function assert(condition:Bool, msg:Dynamic, ?pos:PosInfos):Bool {//TODO: limit access via @:allow
if (!condition)
logError(msg, pos);
return condition;
}
// -------------------------------------------------------------------------------------------
// ENCRYPTION
// -------------------------------------------------------------------------------------------
/** Sets */
public function initEncryption
( key :String
, cipher:Cipher = Cipher.RC4
, format:EncryptionFormat = EncryptionFormat.BASE_64
):Void {
if (cipher == Cipher.NONE)
encryptionHandler = null;
else if (cipher == Cipher.RC4)
encryptionHandler = encryptRc4.bind(key, format);
else
throw "aes not yet implemented";
}
function encryptRc4(key:String, format:EncryptionFormat, data:String):String {
if (format == EncryptionFormat.HEX)
throw "hex format not yet implemented";
var keyBytes:Bytes;
if (format == EncryptionFormat.BASE_64)
keyBytes = Base64.decode(key);
else
keyBytes = null;//TODO
var dataBytes = new Rc4(keyBytes).crypt(Bytes.ofString(data));
if (format == EncryptionFormat.BASE_64)
return Base64.encode(dataBytes);
return null;
}
}