Functions registered with ExternalInterface.addCallback not available in Javascript - javascript

I'm working on a Flash game that needs to call some Javascript on the page and get data back from it. Calling Javascript from Flash works. Calling the Flash functions from Javascript (often) doesn't.
I'm using the Gaia framework.
What happens:
The swf is loaded in with SWFObject
There's a button in the Flash file. On click, it uses ExternalInterface.call() to call a Javascript function. This works.
The Javascript function calls a Flash function that was exposed with ExternalInterface.addCallback().
Sometimes, the Javascript produces the following error: TypeError: myFlash.testCallback is not a function.
When the error happens, it affects all functions registered with addCallback(). Gaia and some of its included libraries use addCallback(), and calling those functions from Javascript also produces the TypeError.
Waiting a long time before pressing the button in Flash doesn't solve the error.
Having Flash re-try addCallback() periodically doesn't solve the error
When the error occurs, ExternalInterface.available = true and ExternalInterface.objectID contains the correct name for the Flash embed object.
When the error occurs, document.getElementById('myflashcontent') correctly returns the Flash embed object.
Edited to add:
This issue shows up in Firefox 3.6, but not Chrome or IE8. I haven't tried older browsers.
I'm running the Debug version of the Flash player.
My calls to ExternalInterface.addCallback() are wrapped in a try...catch block. When the JS error occurs, the catch block is not triggered. It's a silent failure.
The error occurs when testing on a webhost, with the swf loaded from the same server as the page it's on.
I set allowScriptAccess = always.
Setting flash.system.Security.allowDomain("mydomain") doesn't fix the error.
From my Page class:
public class MyPage extends AbstractPage
{
// declarations of stage instances and class variables
// other functions
override public function transitionIn():void
{
send_button.addEventListener(MouseEvent.MOUSE_UP, callJS);
exposeCallbacks();
super.transitionIn();
}
private function exposeCallbacks():void
{
trace("exposeCallbacks()");
if (ExternalInterface.available) {
trace("ExternalInterface.objectID: " + ExternalInterface.objectID);
try {
ExternalInterface.addCallback("testCallback", simpleTestCallback);
trace("called ExternalInterface.addCallback");
}
catch (error:SecurityError) {
trace("A SecurityError occurred: " + error.message + "\n");
}
catch (error:Error) {
trace("An Error occurred: " + error.message + "\n");
}
}
else {
trace("exposeCallbacks() - ExternalInterface not available");
}
}
private function simpleTestCallback(str:String):void
{
trace("simpleTestCallback(str=\"" + str + "\")");
}
private function callJS(e:Event):void
{
if (ExternalInterface.available) {
ExternalInterface.call("sendTest", "name", "url");
}
else {
trace("callJS() - ExternalInterface not available");
}
}
}
My Javascript:
function sendTest(text, url) {
var myFlash = document.getElementById("myflashcontent");
var callbackStatus = "";
callbackStatus += '\nmyFlash[testCallback]: ' + myFlash['testCallback'];
//console.log(callbackStatus);
var errors = false;
try {
myFlash.testCallback("test string");
}
catch (err) {
alert("Error: " + err.toString());
error = true;
}
if (!error) {
alert("Success");
}
}
var params = {
quality: "high",
scale: "noscale",
wmode: "transparent",
allowscriptaccess: "always",
bgcolor: "#000000"
};
var flashVars = {
siteXML: "xml/site.xml"
};
var attributes = {
id: "myflashcontent",
name: "myflashcontent"
};
// load the flash movie.
swfobject.embedSWF("http://myurl.com/main.swf?v2", "myflashcontent",
"728", "676", "10.0.0", serverRoot + "expressInstall.swf",
flashVars, params, attributes, function(returnObj) {
console.log('Returned ' + returnObj.success);
if (returnObj.success) { returnObj.ref.focus(); }
});

Calls made to JS via ExternalInterface are wrapped within a try { } block and that causes subsequent JS errors to get suppressed.
A workaround for the same is to cause a function closure in JavaScript and execute the actual code after a timeout.
Example:
function myFnCalledByEI (arg1, arg2) {
setTimeout(myActualFunction () {
// You can use arg1 and arg2 here as well!
// Errors raised within this function will not be
// suppressed.
}, 0);
};

Here was our scenario once we narrowed down all the conditions:
Only on FireFox/Windows
Only when wmode=transparent
Only when using the js alert() function
In this specific scenario, ExternalInterface.call() would not fire right away. It only worked after creating a tiny delay with Timer class.
If we made wmode=window, or removed the alert() - everything worked. Try using console.log() to display debug text in firebug.
The other gotcha? Whether your js function returns an array or object vs a string. Surprisingly returning the native js array was interpreted by an array in Flash. Try outputting info about your return data like this:
var myRetVal = flash.external.ExternalInterface.call("my_js_func");
debug_txt.text = flash.utils.describeType(myRetVal).toString();

This might be similar to the issue you are experiencing.
http://www.google.com/support/forum/p/translator-toolkit-api/thread?tid=58cda1b34ae1e944&hl=en

You can see that type of error message if something wrong happened in Flash while making the call. Something like an uncaught exception.
Are you running the debug version of the player? That might give you more information about what's going on.
Also, is this consistent across browsers? I've seen old versions of IE having trouble accepting several consecutive Flash <-> JS calls.
J

I tryied your code, and It worked ok if I place an alert before everything, so, I thinkg it is something related to some kind of time you have to wait.

Have you tried this in JavaScript?
if (myFlash)
{
if (!myFlash.testCallback)
{
if (__flash__addCallback)
{
__flash__addCallback( myFlash, "testCallback" );
}
else
{
console.log("Error: Flash External Interface injected JavaScript function not found. The external interface function won't work.");
}
}
}
myFlash.testCallback("test string");
I had used this in many cases.
Again at certain places I had to redefine __flash_addCallback and _flash_removeCallback functions to minimize the errors.
Presently I do not remember what I did for __flash_addCallback, but this is what I did for the latter:
if (__flash__removeCallback)
{
__flash__removeCallback = function (instance, name) {if(instance && instance[name]) instance[name] = null;
}

We've run into the same problem, and only in Firefox.
Following the advice given by THM we were able to find a solution.
In our case, the swf being inserted was inside a div being animated into view with jQuery.slideDown(). This apparently causes it to sometimes reboot while being started. In some cases this led to the callback functions not being available.
Fixed by calling swfobject.embedSWF only after the slideDown effect had finished.

I was getting this error because I had the same Flash file on the page multiple times. Even though each one of them had a different id/name, they were all using the same callback.

Related

how to fix Error: java.lang.ClassNotFoundException on frida

I'm trying to bypass a root detection mechanism on an android app using Frida, I've tried so many different scripts (frida code share) and different approaches (like hiding root) with no luck!
So I tried to locate the class and method responsible for checking if the device is rooted or not and changing it's return value.
This is my script :
setTimeout(function() { // avoid java.lang.ClassNotFoundException
Java.perform(function() {
var hook = Java.use("app.name.RootUtils");
console.log("info: hooking target class");
hook.isRooted.overload().implementation = function() {
console.log("info: entered target method");
return Java.use("java.lang.Boolean").$new(false);
}
});
},0);
If I inject this code normally it won't work because it looks like the isRooted method will get called before it
If I use spawn to run the app and change this method return value it fails with error :
frida.core.RPCException: Error: java.lang.ClassNotFoundException: Didn't find class ...
I've also tried spawning the app and then using objection to run "android root disable" but it will return this error :
frida.core.RPCException: TypeError: cannot read property 'getApplicationContext' of null
at getApplicationContext (src/android/lib/libjava.ts:21)
I'm not sure if this is a problem with Frida or my system or ...
I think if I was able to make my main code runs at exactly after the class gets loaded (like using a loop to check or using a hook) the problem would be fixed but I don't know how to write that kind of code in js for frida.
I'm on macOS 11.5.1 using python 3.9 and installed latest version of frida and objection
I've tested on one rooted phone with android 10 and an emulator with android 6
I was able to solve this issue with a simple yet not very technical solution.
I used a setInteval to run my hooking over and over until it gets to work, (as #Robert mentioned, I also needed to wrap hooking inside a try catch to prevent the code from stoping after first try)
This may not work for everyone but since it worked for me I will post the final code, may it helps someone else in the future :)
Java.perform(function() {
var it = setInterval(function(){
try{
var hook = Java.use("app.name.RootUtils");
console.log("info: hooking target class");
hook.isRooted.overload().implementation = function() {
console.log("info: entered target method");
clearInterval(it);
return Java.use("java.lang.Boolean").$new(false);
}
} catch(e) {
console.log("failed!");
}
},200); // runs every 200milisecods
});
PS : you may need to change interval time to match your app needs, it worked for me with 200 miliseconds.
Sometimes app would do encryptions in its class loader, you may need to replace Java.classFactory.loader with the app's customized class loader in order to make Java.use function properly.
Here's how it's done:
Java.perform(function() {
//get real classloader
//from http://www.lixiaopeng.top/article/63.html
var application = Java.use("android.app.Application");
var classloader;
application.attach.overload('android.content.Context')
.implementation = function(context) {
var result = this.attach(context); // run attach as it is
classloader = context.getClassLoader(); // get real classloader
Java.classFactory.loader = classloader;
return result;
}
//interesting classes
const interestingClassPath = "com.myApp.SometingInteresting";
interestingClass = Java.use(interestingClassPath);
//do whatever you like here
})
Class not found
How do you know the class is app.name.RootUtils have you decompiled to app using Jadx or apktool? How about the method where RootUtils.isRooted() is called? Is there any special code that loads the RootUtils class e.g. from a non-standard dex file included in the app? If the class is loaded from a special dex file you could hook this dex loading mechanism and first execute it and then install your hook for RootUtils.isRooted().
Alternatively assuming RootUtils.isRooted() is called only from one other method and does not use special code for loading the RootUtils class you could hook that method and use the this hook to install install your RootUtils.isRooted() hook.
Error handling
The correct way to handle errors in JavaScript is using try catch block, not the setTimeout function:
Java.perform(() => {
try {
var hook = Java.use("app.name.RootUtils");
...
} catch (e) {
console.log("Failed to hook root detection" + e);
}
}
Regarding your problems hooking the class

Dinosaur game hack not working

I was making this hack for the google dinosaur game:
function speed(n) {
Runner.instance_.setSpeed(n);
}
function noHit() {
Runner.prototype.gameOver = function() {
console.log("");
}
}
function notNoHit() {
Runner.prototype.gameOver = function() {
this.playSound(this.soundFx.HIT);
vibrate(200);
this.stop();
this.crashed = true;
this.distanceMeter.acheivement = false;
this.tRex.update(100, Trex.status.CRASHED);
}
}
it is meant to be typed into the console, that way you don't have to edit the html of the page, which is pretty complicated (at least to me.) so, when i type it in, it returns undefined, as usual. when I use speed(n), it sets the speed to n. when i use noHit(), it makes it so i can't get hit. when i say notNoHit(), it returns undefined, as usual, but when i hit a cactus, it gives me an error:
Uncaught RefrenceError: vibrate is not defined
at Runner.gameOver (<anonymous>:14:3)
at Runner.update (data:text/html,chromewebdata:2005)
this kinda surprised me, because the way I did notNoHit() was to simply set the function back to what it was, instead of a junk command, (console.log("");) so i'm not really sure how to fix this.
Open the console of chrome browser write the below code it is working fine for me.
Runner.instance_.gameOver=()=>{}
Try this code by this Dinosaur will automatically jump when he will be near the obstacles
Runner.instance_.gameOver = function () {
this.playSound ( this.soundFx.HIT);
vibrate(500);
this.crashed = false;
//this.tRex.endJump ();
this.tRex.startJump (200);
// console.log (this.tRex)
}

Why is this Javascript library available in browser but not in PhoneGap?

I have defined a library in Javascript that works great when I am in a browser but whose name isn't found when running under PhoneGap on my device.
The library is defined as so:
(function(bsdi, $) {
bsdi.SomeName = "XYZ";
bsdi.addDays = function (date, days) { ...stuff here...}
....
}(bsdi = window.bsdi || {}, jQuery));
Later, in a .js file that is loaded last, I have:
function knockoutFn() {
var self = this;
if (bsdi.SomeName == "XYZ") { <<--- CRASHES HERE, "bsdi not defined" but only on Device
...stuff here...
}
}
// Happens to use Knockout...
var koFn = new knockoutFn();
ko.applyBindings(koFn);
function init() {
if (isPhoneGap) {
document.addEventListener("deviceready", onDeviceReady, false);
}
else {
koFn.InitPage();
}
}
function onDeviceReady() {
// Now safe to use the Cordova API
koFn.InitPage();
}
What happens is that a normal web browser handles this just fine. However, when I download to my iPhone using the PhoneGap Build app, it gets to "bsdi.SomeName" and crashes because bsdi is not defined. I thought that my method for defining the bsdi library was correct but, obviously, there is something in PhoneGap that doesn't like this. Note that "isPhoneGap" is true and we do use the addEventListener on the device.
Any ideas are greatly appreciated!
UPDATE: On a hunch, I tried moving the bsdi object into the same .js file as the code that uses it. In this case, it finds the object and uses it correctly. When it is an external file, however, it fails. And yes, I have triple-checked that the file exists and is at the correct location. Again, it works fine in a browser!
If window.bsdi isn't defined, then (as posted in your question) your initialization code never ensures that window.bsdi is defined by the time the code is finished. All it does is add those properties to the new empty object passed in, but that won't have any effect on anything once the initialization function is finished.

Uncaught TypeError: $player.jPlayer is not a function

I'm helping a friend with his site and after updating his WordPress installation to address the recent security issue, the JPlayer plugin that was handling audio on his site stopped working.
Chrome's console shows the error in the title, but I don't know JS well enough to be able to debug it properly. I'm pretty sure that the plugin itself is loaded correctly, along with JQuery, in the page header. I checked it against the plugin's instructions and it all appears fine.
I've also updated the plugin itself to ensure that it's not some compatibility issue.
I did not build his site, nor am I familiar with this particular plugin at all, I'm just trying to see if it's an easy fix or if I have to restore a backup.
I assume it has something to do with how his web designer (they had a falling out) implemented it in the main.js file, but that's about as far as I've gotten.
Help?
Really condensing and removing parts of main.js, it looks like
var $player = false,
$(document).ready(function() {
if(!$player) {
$("#jPlayer").jPlayer({
ready: function() {
$player = $(this); // IT'S BEING SET HERE !
PlaylistPlay(playlistObject,trackIndex);
}
});
} else {
PlaylistPlay(playlistObject,trackIndex);
}
});
function PlaylistPlay(lePID,trackIndex) {
playTrack(trackIndex);
}
function playTrack(index) {
$player.jPlayer("setMedia", {mp3: trackObject.mp3,oga: trackObject.oga}).jPlayer("play");
}
If you look closely at that, you'll see that there is a distinct possibility that PlaylistPlay can be called without $player being set to $(this), it's actually almost a certaintity, which means that $player is false, and doing
false.jPlayer(...
doesn't really work, see the console output that confirms the variable is false
The plugin is not initializing correctly. On $(document).ready() it's trying to initialize the plugin and it's reporting a Flash error.
Here's the significant part of the code:
$("#jPlayer").jPlayer({
...
error: function(event) {
var out = "<p id=\"noSolution\">Sorry, you need an HTML5 capable browser or flash to be able to listen to music on this website.<br /> Reason: ";
switch(event.jPlayer.error.type) {
case $.jPlayer.error.FLASH:
out += "A problem with the Flash insertion on the page.";
break;
...
}
}
...
});
Digging a bit deeper, I can trace this back to the vimeo.jplayer in the specific code block:
_flash_volume: function(a) {
try {
this._getMovie().fl_volume(a)
} catch (b) {
this._flashError(b)
}
}
That function is throwing an exception because this._getMovie() does not have a property named fl_volume.
The error you actually see is a side-effect of this failure. You could try removing the line: this._flashError(b) from the above statement and see if the error can be safely ignored.

How do I solve the JavaScript error: method XX is undefined?

I am working on asp.net and jquery website , and when run browse the site an java script error has occurred .
Visual studio debuger references that GetMainFrameUrl is undefined :
function EBNavigateComplete()
{
if (!GetMainFrameUrl())
{
navCounter++;
if (navCounter%100==0)
{
o.loadEmptyTerm();
counter = 0;
}
}
else
{
if (o.pbNavigateComplete(GetMainFrameUrl()))
{
counter = 0;
}
}
}
is there help please???
Are you using the Conduit API? If so, do you have that script library referenced?
http://www.conduit.com/Developers/HtmlAndGadget/Methods/GetMainFrameUrl.aspx
http://www.conduit.com/Developers/HtmlAndGadget/Events/EBNavigateComplete.aspx
Visual Studio debugger isn't always capable of loading all dynamically loaded scripts (but normally it does though). Is the same error occurring if you run it in, say, Firefox or Opera?
The error means that the function GetMainFrameUrl is not defined. That can happen if you misspelled the name of an existing function, or when the function is loaded only later in the chain. In the latter case, change the order of your <script> blocks to have the one with GetMainFrameUrl in it load first.
One easy way to find out whether that function is available somewhere is hitting Ctrl-Shift-F in Visual Studio, select "Entire Solution" and nothing for the file filter and search for the name of the missing function.
If you whant to check if the GetMainFrameUrl-function exists you cant use "if (!GetMainFrameUrl())". In this case javascript execute the function and comprare the return value.
You can use "if (!GetMainFrameUrl)" but this only checks if any function, object or variable exists with this name.
You should use "typeof". See exampel:
function EBNavigateComplete()
{
if ( typeof GetMainFrameUrl !== 'function' )
{
navCounter++;
if (navCounter%100==0)
{
o.loadEmptyTerm();
counter = 0;
}
}
else
{
if (o.pbNavigateComplete(GetMainFrameUrl()))
{
counter = 0;
}
}
}

Categories

Resources