ruffle icon indicating copy to clipboard operation
ruffle copied to clipboard

Angry Birds Refresh - ExternalInterface Login Failure

Open ultra0000 opened this issue 1 year ago • 4 comments

Describe the bug

The authentication data fails to get sent through ExternalInterface, with the following error:

ERROR core/src/external.rs:310 Unhandled error in External Interface callback sendDiscordData: TypeError: Error #2007: Parameter closure must be non-null.

Expected behavior

The game should receive the data properly after authenticating with Discord.

Content Location

https://refresh.teamflashcord.com/

Affected platform

Browser's extension

Operating system

Windows 11

Browser

Microsoft Edge 126.0.2592.81

Additional information

Relevant JavaScript code:

var discordResult;
var gotResult = false;

function HandleDiscordResult(result) {
    discordResult = JSON.parse(result);
    gotResult = true;
}

function discordLogIn() {
    var urlToOpen = "/discordauth/login/";
    var popup = createPopupWindow(urlToOpen, "", 450, 600);
	
    var pollTimer = window.setInterval(function() {
        if (popup.closed !== false) {
            window.clearInterval(pollTimer);
            if (discordResult !== null && gotResult) {
                document.getElementById("AngryBirdsRefresh").sendDiscordData(discordResult);
                discordResult = null;
            }
            else {
                if (gotResult) {
                    document.getElementById("AngryBirdsRefresh").sendDiscordData({"error": "Result is null"});
                }
                else {
                    document.getElementById("AngryBirdsRefresh").sendDiscordData({"error": "Didn't Log In"});
                }
            }
        }
    }, 200);
}

Relevant ActionScript 3 code:

	  private static function serverCallDiscord() : void
	  {
		 if (AngryBirdsBase.singleton.mCanvas.loaderInfo.parameters.launcherType != "AIRBIRD")
		 {
			// we're not on airbird, proceed as normal
			ExternalInterfaceHandler.performCall("discordLogIn");
			ExternalInterfaceHandler.addCallback("sendDiscordData",onDiscordGotData);
		 }
		 ...
	  }

	  private static function onDiscordGotData(response:Object) : void
	  {
		 ExternalInterfaceHandler.removeCallback("sendDiscordData",onDiscordGotData);
		 if (response.error == "Didn't Log In")
		 {
			AngryBirdsBase.singleton.popupManager.openPopup(new ErrorPopup(ErrorPopup.DIDNT_LOG_IN,"Server Error: User didn't log in."));
		 }
		 else if (response.error == "Banned")
		 {
			AngryBirdsBase.singleton.popupManager.openPopup(new ErrorPopup(ErrorPopup.BANNED,"User has been banned.", response.banReason, response.banTime));
		 }
		 else if (response.error == "Success")
		 {
			sFacebookUserId = response.userId;
			sAccessToken = response.accessToken;
			sExpiresInSeconds = response.expiresInSeconds;
			serverCallLogIn();
		 }
		 else
		 {
			AngryBirdsBase.singleton.popupManager.openPopup(new ErrorPopup(ErrorPopup.ERROR_GENERAL,"Unknown Server Error"));
		 }
	  }

(AIRBird is our Adobe AIR launcher, which uses a different way of authenticating)

ultra0000 avatar Jun 30 '24 19:06 ultra0000

Relevant code from ExternalInterfaceHandler:

      public static function addCallback(externalMethod:String, callback:Function) : void
      {
         try
         {
            if(!externalMethods[externalMethod])
            {
               externalMethods[externalMethod] = new ExternalInterfaceMethod(externalMethod);
            }
            (externalMethods[externalMethod] as ExternalInterfaceMethod).addCallback(callback);
         }
         catch(e:Error)
         {
         }
      }
      
      public static function removeCallback(externalMethod:String, callback:Function) : void
      {
         var method:ExternalInterfaceMethod = externalMethods[externalMethod] as ExternalInterfaceMethod;
         if(method)
         {
            method.removeCallback(callback);
            if(method.callbackCount == 0)
            {
               method.dispose();
               delete externalMethods[externalMethod];
            }
         }
      }
      
      public static function performCall(call:String, ... params) : *
      {
         var logStr:String = "ExternalInterface call: " + call + "(" + params.join(", ") + ");";
         if(logStr.length > 300)
         {
            logStr = logStr.substr(0,300) + "[...]";
         }
         Log.log(logStr);
         if(ExternalInterface.available && EXTERNAL_INTERFACES_ENABLED)
         {
            try
            {
               params.unshift(call);
               return ExternalInterface.call.apply(null,params);
            }
            catch(e:Error)
            {
               Log.log("ExternalInterface call failed!\nCall was:" + call + "\nError data:" + e.toString());
            }
         }
         ...
      }

ultra0000 avatar Jun 30 '24 19:06 ultra0000

ExternalInterfaceMethod:

package com.rovio.externalInterface
{
   import com.rovio.factory.Log;
   import flash.external.ExternalInterface;
   
   public class ExternalInterfaceMethod
   {
       
      
      public var externalMethodName:String = "";
      
      private var callbacks:Array = null;
      
      public function ExternalInterfaceMethod(methodName:String)
      {
         super();
         this.externalMethodName = methodName;
         if(ExternalInterface.available)
         {
            ExternalInterface.addCallback(this.externalMethodName,this.methodListener);
         }
      }
      
      public function methodListener(... args) : *
      {
         var logStr:* = null;
         var i:Number = NaN;
         var f:Function = null;
         logStr = "call through externalInterface! " + this.externalMethodName + "(";
         for(i = 0; i < args.length; i++)
         {
            logStr += args[i] + ",";
         }
         logStr += ")";
         Log.log(logStr);
         var returnValue:* = null;
         if(this.callbacks != null)
         {
            for each(f in this.callbacks)
            {
               returnValue = f.apply(null,args);
            }
         }
         return returnValue;
      }
      
      public function addCallback(callback:Function) : void
      {
         if(this.callbacks == null)
         {
            this.callbacks = new Array();
         }
         if(this.callbacks.indexOf(callback) == -1)
         {
            this.callbacks.push(callback);
         }
      }
      
      public function removeCallback(callback:Function) : void
      {
         if(this.callbacks && this.callbacks.indexOf(callback) != -1)
         {
            this.callbacks.splice(this.callbacks.indexOf(callback),1);
         }
      }
      
      public function get callbackCount() : int
      {
         if(!this.callbacks)
         {
            return 0;
         }
         return this.callbacks.length;
      }
      
      public function dispose() : void
      {
         if (ExternalInterface.available)
         {
            ExternalInterface.addCallback(this.externalMethodName,null);
         }
      }
   }
}

ultra0000 avatar Jun 30 '24 19:06 ultra0000

According to ExternalInterface.addCallback docs, using null closure argument on an existing callback should remove it (I haven't tested this, so it might be wrong). We currently throw on null or undefined https://github.com/ruffle-rs/ruffle/blob/fd5ca6ae392b44a3bdf63bfd8407e4c05d17c8dd/core/src/avm2/globals/flash/external/external_interface.rs#L57

sleepycatcoding avatar Jun 30 '24 21:06 sleepycatcoding

According to ExternalInterface.addCallback docs, using null closure argument on an existing callback should remove it (I haven't tested this, so it might be wrong). We currently throw on null or undefined

https://github.com/ruffle-rs/ruffle/blob/fd5ca6ae392b44a3bdf63bfd8407e4c05d17c8dd/core/src/avm2/globals/flash/external/external_interface.rs#L57

This game works on Flash and AIR, so the docs must be wrong.

Fancy2209 avatar Jun 30 '24 22:06 Fancy2209