JSProxy Bug and Solution

For some time I was using the JSProxy class from Joan Garnet but now I have seen a BUG and patched it.

When working with Injected Javascript Code, sometimes the Javascript Compiler trow an error, but if you load the same code directly from a URL it works like a charm.

Then, whats is the problem? After some hours of investigation I managed that sometimes some caracters are not correctly encoded from ExternalInterfaces calls, then the code runs in wrong way.

The solution: Simple, encode in AS3 the Injected JS code to Base64, and decode it in JS before compile it.

Original JSProxy: Original Class
Modified JSProxy Class: Patched JSProxy Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package com.joangarnet.js
{
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    import flash.external.ExternalInterface;
    import flash.utils.Proxy;
    import flash.utils.flash_proxy;
    import flash.utils.ByteArray;
    import org.phprpc.util.Base64;

    /*
     * Original class: Joan Garnet http://www.joangarnet.com/blog/?p=437
     * Patched by Pedro Casaubon
     * This patch add Base64 encoding/decoding to the Injected Code
     */


    dynamic final public class JavaScript extends Proxy implements IEventDispatcher
    {
        private var eventDispatcher:EventDispatcher;

        /*
         * scope for calling the Javascript methods and properties. Defaults to DOM's window object.
         */

        public static const WINDOW:String = "";
        public static const DOCUMENT:String = "document";
        private var scope:String;

        /*
         * javascript toolkit to do some internal tasks
         */

        private const JSProxyTools:XML =
            <script type="text/javascript"><!--mce:0--></script>;
        /*
         * end javascript toolkit
         */


        private var movieID:String;

        public function JavaScript(movieID:String,url:String=null)
        {
            this.movieID = movieID;
            if( !ExternalInterface.available ){
                throw new Error("ExternalInterface not available");
            }
            eventDispatcher = new EventDispatcher(this);
            ExternalInterface.marshallExceptions = true;

            // compile internal toolkit
            try{
                ExternalInterface.call( "eval", JSProxyTools.toString() );
            }catch(err:Error){
                throw new Error( "com.joangarnet.js.JavaScript::constructor()\ngetStackTrace() "+ err.getStackTrace() );
            }

            // handle the loaded script callback
            ExternalInterface.addCallback( "jsLoadedHandler", jsLoaded );

            if(url)this.url = url;
        }

        private function callToolkit( methodName:String, params_arr:Array ):*
        {
            var res:*;
            try{
                res = ExternalInterface.call( "com.joangarnet.JSProxy.facade", movieID, methodName, params_arr );
            }catch(err:Error){
                throw new Error( "com.joangarnet.js.JavaScript::callToolkit()\ngetStackTrace() "+ err.getStackTrace() );
            }
            return res;
        }

        private function set url( url:String ):void
        {
            callToolkit( "loadScript", [url] );
        }

        public function set js( jsCode:String ):void
        {
            compile( jsCode );
        }

        public function getScope( scope:String=WINDOW ):JavaScript
        {
            this.scope = scope.replace( /^(window\.?)(.*)/i, "$2" );
            return this;
        }

        private function jsLoaded():void
        {
            trace("jsLoaded!");
            dispatchEvent( new Event(Event.COMPLETE) );
        }

        /*
         * Modified method to support Base64 encoding
         * @author Pedro Casaubon (http://www.xperiments.es/blog)
         */

        private function compile( jsCode:String ):void
        {
            // javascript compilation
            try{
                    var ba:ByteArray = new ByteArray();
                    ba.writeUTFBytes( jsCode );
                    ba.position = 0;
                    var baseCode:String = Base64.encode( ba );
                    callToolkit( "compileCode", [baseCode] );

            }catch(err:Error){
                throw new Error( "com.joangarnet.js.JavaScript::compile() javascript compilation error\ngetStackTrace() "+ err.getStackTrace() );
            }
            dispatchEvent( new Event(Event.COMPLETE) );
        }

        private function buildPath( objectMember:String ):String
        {
            return (scope.length&gt;0 ? scope+"."+objectMember : objectMember);
        }

        /*
         * Proxy implementation
         */

        override flash_proxy function callProperty(methodName:*, ... args):*
        {
            var res:*;
            var functionCall:String = buildPath((methodName as QName).localName);
            try{
                args = args!=null ? args : [];
                args.unshift( functionCall );
                res = ExternalInterface.call.apply(null, args);
            }catch(err:Error){
                throw new Error( "com.joangarnet.js.JavaScript::"+functionCall+"()\ngetStackTrace() "+ err.getStackTrace() );
            }
            return res;
        }
        override flash_proxy function getProperty(name:*):*
        {
            return callToolkit( "getProperty", [buildPath((name as QName).localName)] );
        }
        override flash_proxy function setProperty(name:*, value:*):void
        {
            callToolkit( "setProperty", [buildPath((name as QName).localName), value] );
        }

        /*
         * IEventDispatcher implementation
         */

        public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void
        {
            eventDispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
        }

        public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void
        {
            eventDispatcher.removeEventListener(type, listener, useCapture);
        }

        public function dispatchEvent(event:Event):Boolean
        {
            return eventDispatcher.dispatchEvent(event);
        }

        public function hasEventListener(type:String):Boolean
        {
            return eventDispatcher.hasEventListener(type);
        }

        public function willTrigger(type:String):Boolean
        {
            return eventDispatcher.willTrigger(type);
        }
    }
}
Comment are closed.