├── .github └── pull_request_template.md ├── .gitignore ├── README.md ├── UPGRADE_FROM_RATCHET.md ├── build.sh ├── build ├── swc │ └── Rollbar.swc └── swf │ ├── RollbarNotifer.swf │ └── test.swf ├── index.html ├── libs ├── asuuid.swc └── flexunit-4.1.0-8-as3_4.1.0.16076.swc ├── src └── com │ └── rollbar │ ├── json │ └── JSONEncoder.as │ ├── notifier │ ├── Rollbar.as │ ├── RollbarNotifier.as │ └── Test.as │ ├── stacktrace │ ├── StackTrace.as │ ├── StackTraceLine.as │ └── StackTraceParser.as │ └── tests │ ├── ErrorCausingConstructorClass.as │ ├── StackTraceParserTest.as │ └── TestRunner.as └── static ├── js └── swfobject.js └── swf └── expressInstall.swf /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description of the change 2 | 3 | > Please include a summary of the change and which issues are fixed. 4 | > Please also include relevant motivation and context. 5 | 6 | ## Type of change 7 | 8 | - [ ] Bug fix (non-breaking change that fixes an issue) 9 | - [ ] New feature (non-breaking change that adds functionality) 10 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 11 | - [ ] Maintenance 12 | - [ ] New release 13 | 14 | ## Related issues 15 | 16 | > ClubHouse stories and GitHub issues (delete irrelevant) 17 | 18 | - Fix [ch] 19 | - Fix #1 20 | 21 | ## Checklists 22 | 23 | ### Development 24 | 25 | - [ ] Lint rules pass locally 26 | - [ ] The code changed/added as part of this pull request has been covered with tests 27 | - [ ] All tests related to the changed code pass in development 28 | 29 | ### Code review 30 | 31 | - [ ] This pull request has a descriptive title and information useful to a reviewer. There may be a screenshot or screencast attached 32 | - [ ] "Ready for review" label attached to the PR and reviewers assigned 33 | - [ ] Issue from task tracker has a link to this pull request 34 | - [ ] Changes have been reviewed by at least one other engineer 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | # Project property files 4 | .actionScriptProperties 5 | .flexProperties 6 | .settings/ 7 | .project 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Attention: 2 | 3 | As of May 2024, Rollbar will not be actively updating this repository and plans to archive it in January of 2025. We encourage our community to fork this repo if you wish to continue its development. While Rollbar will no longer be engaging in active development, we remain committed to reviewing and merging pull requests related to security updates. If an actively maintained fork emerges, please reach out to our support team and we will link to it from our documentation. 4 | 5 | 6 | # Rollbar notifier for Flash (AS3) 7 | 8 | 9 | Flash (ActionScript 3) library for reporting exceptions, errors, and log messages to [Rollbar](https://rollbar.com). 10 | 11 | 12 | 13 | ## Quick start 14 | 15 | 1. Download the [flash_rollbar](https://github.com/rollbar/flash_rollbar/tree/master/src) code or just the [Rollbar.swc](https://github.com/rollbar/flash_rollbar/blob/master/build/swc/Rollbar.swc) file. 16 | 2. Place the ```flash_rollbar/src``` directory in your source path or place the ```Rollbar.swc``` file in your project's library path. 17 | 3. Call ```Rollbar.init(this, accessToken, environment);``` from your top-level ```DisplayObject```. 18 | 19 | ```actionscript 20 | package { 21 | import com.rollbar.notifier.Rollbar; 22 | 23 | public class MyApp extends Sprite { 24 | 25 | public static const ROLLBAR_ACCESS_TOKEN:String = "POST_CLIENT_ITEM_ACCESS_TOKEN"; 26 | 27 | public function MyApp() { 28 | var environment:String = isDebug() ? "development" : "production"; 29 | var person:Object = {id: getUserId(), email: getEmail(), name: getName()}; // optional 30 | Rollbar.init(this, ROLLBAR_ACCESS_TOKEN, environment, person); 31 | } 32 | } 33 | } 34 | ``` 35 | 36 | 37 | ```Rollbar.init()``` installed a global error handler, so you don't need to do anything else. 38 | 39 | 40 | Be sure to replace ```POST_CLIENT_ITEM_ACCESS_TOKEN``` with your project's ```post_client_item``` access token, which you can find in the Rollbar.com interface. 41 | 42 | 43 | ## Requirements 44 | 45 | - Flash Player 10.1+ 46 | - May work on 9, but not tested. 47 | - mxmlc/compc if you plan on building from the command-line 48 | - A [Rollbar](http://rollbar.com) account 49 | 50 | ## Reporting caught errors 51 | 52 | If you want to instrument specific parts of your code, call ```Rollbar.handleError(err)```: 53 | 54 | ```actionscript 55 | private function onEnterFrame(event:Event) { 56 | try { 57 | gameLoop(event); 58 | } catch (err:Error) { 59 | Rollbar.handleError(err); 60 | } 61 | } 62 | ``` 63 | 64 | **Advanced:** to override parts of the payload before it is sent to the Rollbar API, pass them in the second argument to `handleError()`. For example, to control how your data will be grouped, you can pass a custom `fingerprint`: 65 | 66 | ```actionscript 67 | Rollbar.handleError(err, {fingerprint: "a string to uniquely identify this error"}); 68 | ``` 69 | 70 | The second argument, `extraData`, should be an object. Each key in `extraData` will overwrite the previous contents of the payload. For all options, see the [API documentation](https://rollbar.com/docs/api/items_post/). 71 | 72 | 73 | ## Configuration 74 | 75 | At the topmost level of your display list, instantiate the Rollbar singleton. 76 | 77 | ```actionscript 78 | Rollbar.init(this, accessToken, environment); 79 | ``` 80 | 81 | Here's the full list of constructor parameters (in order): 82 | 83 |
84 |
parent 85 |
86 |
The parent display object container; should usually be ```this```. The notifier will report all errors for SWFs that are loaded with ```parent.loaderInfo```. 87 |
88 |
accessToken 89 |
90 |
Access token from your Rollbar project 91 |
92 |
environment 93 |
94 |
Environment name. Any string up to 255 chars is OK. For best results, use ```"production"``` for your production environment. 95 | 96 | Default: ``"production"`` 97 | 98 |
99 |
person 100 |
101 |
Optional but can be one of: 102 | 103 | - A string identifier for the current person/user. 104 | - An object describing the current person/user, containing 105 | - Required - id, userId, user_id, user 106 | - Optional - email, userEmail, user_email, emailAddress, email_address 107 | - Optional - username, userName, user_name, name 108 | - A function returning an object like the one described above 109 | 110 |
111 |
rootPath 112 |
113 |
If you compiled the SWC/SWF using the debug or verbose stack trace flags, you'll want this to be the absolute path to the root of your Actionscript source code, not including the final ```/```. 114 | 115 | Otherwise, set this to the source path relative to your repository's root. 116 | e.g. if your source tree looks like this: 117 | ``` 118 | /myApp/src/com/myApp 119 | ``` 120 | 121 | Set this to ```"src"``` 122 | 123 |
124 |
codeBranch 125 |
126 |
Name of the branch used to compile your Flash movie. 127 | 128 | Default: ```"master"``` 129 | 130 |
131 |
serverData 132 |
133 |
An Object containing any data you would like to pass along with this item to store. 134 |
135 |
maxItemCount 136 |
137 |
The maximum number of items to send to Rollbar for the lifetime of the notifier instance. This is useful for rate-limiting the number of items sent to Rollbar. 138 |
139 |
endpointUrl 140 |
141 |
URL items are posted to. 142 | 143 | Default: ```"https://api.rollbar.com/api/1/item/"``` 144 |
145 |
146 | -------------------------------------------------------------------------------- /UPGRADE_FROM_RATCHET.md: -------------------------------------------------------------------------------- 1 | # Upgrading from flash_ratchet 2 | 3 | Download and install the latest SWF/SWC into your project, (e.g. replace `RatchetNotifier.swc` with `RollbarNotifier.swc`) 4 | 5 | ## Update references in code 6 | 7 | Change your initialization call from `Ratchet.init(...)` to `Rollbar.init(...)`. 8 | 9 | Search your app for all references to `ratchet`/`Ratchet` and replace them with `rollbar`/`Rollbar`. 10 | 11 | Update all references to the `io.ratchet.*` package to the new one, `com.rollbar.*`. 12 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | target="$1" 4 | debug= 5 | verbose= 6 | 7 | if [ $# -gt 1 ] 8 | then 9 | echo "Building with -debug" 10 | debug="-debug=true" 11 | else 12 | debug="" 13 | fi 14 | 15 | if [ $# -gt 2 ] 16 | then 17 | echo "Building with -compiler.verbose-stacktraces" 18 | verbose="-compiler.verbose-stacktraces=true" 19 | else 20 | verbose="" 21 | fi 22 | 23 | if [ "$target" = "swf" ] 24 | then 25 | echo 'Building Test SWF into build/swf/test.swf' 26 | mxmlc -compiler.source-path=./src -static-link-runtime-shared-libraries=true $debug $verbose ./src/com/rollbar/notifier/Test.as -include-libraries=./libs/asuuid.swc -output build/swf/test.swf 27 | elif [ "$target" = "swc" ] 28 | then 29 | echo 'Building Rollbar SWC into build/swc/Rollbar.swc' 30 | compc -include-libraries=./libs/asuuid.swc -include-sources ./src/com/rollbar/json -include-sources ./src/com/rollbar/notifier -include-sources ./src/com/rollbar/stacktrace -output build/swc/Rollbar.swc 31 | elif [ "$target" = "test" ] 32 | then 33 | echo 'Building test suite into build/swf/testsuite.swf' 34 | mxmlc -compiler.source-path=./src -static-link-runtime-shared-libraries=true -debug=true -compiler.verbose-stacktraces=true -include-libraries=./libs/asuuid.swc -include-libraries=./libs/flexunit-4.1.0-8-as3_4.1.0.16076.swc ./src/com/rollbar/tests/TestRunner.as -output build/swf/testsuite.swf 35 | ret_code=$? 36 | if [ $ret_code == 0 ]; then 37 | expect -c "spawn fdb build/swf/testsuite.swf; send -- continue\r; expect eof;" 38 | else 39 | echo 'Aborting test suite due to build failure' 40 | fi 41 | else 42 | echo 'Building Notifier SWF into build/swf/RollbarNotifier.swf' 43 | mxmlc -compiler.source-path=./src -static-link-runtime-shared-libraries=true $debug $verbose ./src/com/rollbar/notifier/RollbarNotifier.as -output build/swf/RollbarNotifer.swf 44 | fi 45 | 46 | echo 'Done' 47 | -------------------------------------------------------------------------------- /build/swc/Rollbar.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rollbar/flash_rollbar/0a053c362621e6ea1bfdd76373f27b500f1dd1ae/build/swc/Rollbar.swc -------------------------------------------------------------------------------- /build/swf/RollbarNotifer.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rollbar/flash_rollbar/0a053c362621e6ea1bfdd76373f27b500f1dd1ae/build/swf/RollbarNotifer.swf -------------------------------------------------------------------------------- /build/swf/test.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rollbar/flash_rollbar/0a053c362621e6ea1bfdd76373f27b500f1dd1ae/build/swf/test.swf -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Get Adobe Flash player 22 | 23 | 24 | 25 | 26 | 27 |
28 |

Note, if you get an error about a security violation, make sure to give this movie access to your local file settings by right clicking and updating the movie's settings.

29 | 30 | 31 | -------------------------------------------------------------------------------- /libs/asuuid.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rollbar/flash_rollbar/0a053c362621e6ea1bfdd76373f27b500f1dd1ae/libs/asuuid.swc -------------------------------------------------------------------------------- /libs/flexunit-4.1.0-8-as3_4.1.0.16076.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rollbar/flash_rollbar/0a053c362621e6ea1bfdd76373f27b500f1dd1ae/libs/flexunit-4.1.0-8-as3_4.1.0.16076.swc -------------------------------------------------------------------------------- /src/com/rollbar/json/JSONEncoder.as: -------------------------------------------------------------------------------- 1 | /* 2 | Originally from: https://github.com/mikechambers/as3corelib/ 3 | 4 | Copyright (c) 2008, Adobe Systems Incorporated 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, 12 | this list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 18 | * Neither the name of Adobe Systems Incorporated nor the names of its 19 | contributors may be used to endorse or promote products derived from 20 | this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 23 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 24 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 25 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 26 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | package com.rollbar.json { 36 | 37 | import flash.utils.describeType; 38 | 39 | public class JSONEncoder { 40 | 41 | /** The string that is going to represent the object we're encoding */ 42 | private var jsonString:String; 43 | 44 | public static function encode(o:Object):String { 45 | return new JSONEncoder(o).getString(); 46 | } 47 | 48 | /** 49 | * Creates a new JSONEncoder. 50 | * 51 | * @param o The object to encode as a JSON string 52 | * @langversion ActionScript 3.0 53 | * @playerversion Flash 9.0 54 | * @tiptext 55 | */ 56 | public function JSONEncoder(value:*) { 57 | jsonString = convertToString( value ); 58 | } 59 | 60 | /** 61 | * Gets the JSON string from the encoder. 62 | * 63 | * @return The JSON string representation of the object 64 | * that was passed to the constructor 65 | * @langversion ActionScript 3.0 66 | * @playerversion Flash 9.0 67 | * @tiptext 68 | */ 69 | public function getString():String { 70 | return jsonString; 71 | } 72 | 73 | /** 74 | * Converts a value to it's JSON string equivalent. 75 | * 76 | * @param value The value to convert. Could be any 77 | * type (object, number, array, etc) 78 | */ 79 | private function convertToString(value:*):String { 80 | // determine what value is and convert it based on it's type 81 | if (value is String) { 82 | // escape the string so it's formatted correctly 83 | return escapeString(value as String); 84 | } 85 | else if (value is Number) { 86 | // only encode numbers that finate 87 | return isFinite( value as Number ) ? value.toString() : "null"; 88 | } else if (value is Boolean) { 89 | // convert boolean to string easily 90 | return value ? "true" : "false"; 91 | } else if (value is Array) { 92 | // call the helper method to convert an array 93 | return arrayToString( value as Array ); 94 | } else if (value is Object && value != null) { 95 | // call the helper method to convert an object 96 | return objectToString( value ); 97 | } 98 | 99 | return "null"; 100 | } 101 | 102 | /** 103 | * Escapes a string according to the JSON specification. 104 | * 105 | * @param str The string to be escaped 106 | * @return The string with escaped special characters 107 | * according to the JSON specification 108 | */ 109 | private function escapeString(str:String):String { 110 | // create a string to store the string's jsonstring value 111 | var s:String = ""; 112 | // current character in the string we're processing 113 | var ch:String; 114 | // store the length in a local variable to reduce lookups 115 | var len:Number = str.length; 116 | 117 | // loop over all of the characters in the string 118 | for (var i:int = 0; i < len; i++) { 119 | // examine the character to determine if we have to escape it 120 | ch = str.charAt(i); 121 | switch ( ch ) { 122 | case '"': // quotation mark 123 | s += "\\\""; 124 | break; 125 | 126 | //case '/': // solidus 127 | // s += "\\/"; 128 | // break; 129 | 130 | case '\\': // reverse solidus 131 | s += "\\\\"; 132 | break; 133 | 134 | case '\b': // bell 135 | s += "\\b"; 136 | break; 137 | 138 | case '\f': // form feed 139 | s += "\\f"; 140 | break; 141 | 142 | case '\n': // newline 143 | s += "\\n"; 144 | break; 145 | 146 | case '\r': // carriage return 147 | s += "\\r"; 148 | break; 149 | 150 | case '\t': // horizontal tab 151 | s += "\\t"; 152 | break; 153 | 154 | default: // everything else 155 | 156 | // check for a control character and escape as unicode 157 | if (ch < ' ') { 158 | // get the hex digit(s) of the character (either 1 or 2 digits) 159 | var hexCode:String = ch.charCodeAt(0).toString(16); 160 | 161 | // ensure that there are 4 digits by adjusting 162 | // the # of zeros accordingly. 163 | var zeroPad:String = hexCode.length == 2 ? "00" : "000"; 164 | 165 | // create the unicode escape sequence with 4 hex digits 166 | s += "\\u" + zeroPad + hexCode; 167 | } else { 168 | 169 | // no need to do any special encoding, just pass-through 170 | s += ch; 171 | 172 | } 173 | } // end switch 174 | 175 | } // end for loop 176 | 177 | return "\"" + s + "\""; 178 | } 179 | 180 | /** 181 | * Converts an array to it's JSON string equivalent 182 | * 183 | * @param a The array to convert 184 | * @return The JSON string representation of a 185 | */ 186 | private function arrayToString(a:Array):String { 187 | // create a string to store the array's jsonstring value 188 | var s:String = ""; 189 | 190 | // loop over the elements in the array and add their converted 191 | // values to the string 192 | var length:int = a.length; 193 | for (var i:int = 0; i < length; i++) { 194 | // when the length is 0 we're adding the first element so 195 | // no comma is necessary 196 | if (s.length > 0) { 197 | // we've already added an element, so add the comma separator 198 | s += "," 199 | } 200 | 201 | // convert the value to a string 202 | s += convertToString(a[i]); 203 | } 204 | 205 | // KNOWN ISSUE: In ActionScript, Arrays can also be associative 206 | // objects and you can put anything in them, ie: 207 | // myArray["foo"] = "bar"; 208 | // 209 | // These properties aren't picked up in the for loop above because 210 | // the properties don't correspond to indexes. However, we're 211 | // sort of out luck because the JSON specification doesn't allow 212 | // these types of array properties. 213 | // 214 | // So, if the array was also used as an associative object, there 215 | // may be some values in the array that don't get properly encoded. 216 | // 217 | // A possible solution is to instead encode the Array as an Object 218 | // but then it won't get decoded correctly (and won't be an 219 | // Array instance) 220 | 221 | // close the array and return it's string value 222 | return "[" + s + "]"; 223 | } 224 | 225 | /** 226 | * Converts an object to it's JSON string equivalent 227 | * 228 | * @param o The object to convert 229 | * @return The JSON string representation of o 230 | */ 231 | private function objectToString(o:Object):String { 232 | // create a string to store the object's jsonstring value 233 | var s:String = ""; 234 | 235 | // determine if o is a class instance or a plain object 236 | var classInfo:XML = describeType(o); 237 | if (classInfo.@name.toString() == "Object") { 238 | // the value of o[key] in the loop below - store this 239 | // as a variable so we don't have to keep looking up o[key] 240 | // when testing for valid values to convert 241 | var value:Object; 242 | 243 | // loop over the keys in the object and add their converted 244 | // values to the string 245 | for (var key:String in o) { 246 | // assign value to a variable for quick lookup 247 | value = o[key]; 248 | 249 | // don't add function's to the JSON string 250 | if (value is Function) { 251 | // skip this key and try another 252 | continue; 253 | } 254 | 255 | // when the length is 0 we're adding the first item so 256 | // no comma is necessary 257 | if (s.length > 0) { 258 | // we've already added an item, so add the comma separator 259 | s += "," 260 | } 261 | 262 | s += escapeString(key) + ":" + convertToString(value); 263 | } 264 | } else { // o is a class instance 265 | // Loop over all of the variables and accessors in the class and 266 | // serialize them along with their values. 267 | for each (var v:XML in classInfo..*.(name() == "variable" || 268 | ( 269 | name() == "accessor" 270 | // Issue #116 - Make sure accessors are readable 271 | && attribute("access").charAt(0) == "r") 272 | )) { 273 | // Issue #110 - If [Transient] metadata exists, then we should skip 274 | if (v.metadata && v.metadata.(@name == "Transient").length() > 0) { 275 | continue; 276 | } 277 | 278 | // When the length is 0 we're adding the first item so 279 | // no comma is necessary 280 | if (s.length > 0) { 281 | // We've already added an item, so add the comma separator 282 | s += "," 283 | } 284 | 285 | s += escapeString(v.@name.toString()) + ":" + convertToString(o[v.@name]); 286 | } 287 | } 288 | 289 | return "{" + s + "}"; 290 | } 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/com/rollbar/notifier/Rollbar.as: -------------------------------------------------------------------------------- 1 | package com.rollbar.notifier { 2 | 3 | import flash.display.DisplayObjectContainer; 4 | import flash.events.ErrorEvent; 5 | 6 | import com.rollbar.notifier.RollbarNotifier; 7 | 8 | /** 9 | * Static wrapper around a RollbarNotifier singleton. 10 | * 11 | * Use this unless you have some specific reason to use RollbarNotifer directly. 12 | */ 13 | public final class Rollbar { 14 | 15 | private static var notifier:RollbarNotifier = null; 16 | 17 | /** 18 | * Initialize the Rollbar notifier. Constructs a RollbarNotifier instance and adds it to the stage, 19 | * which will trigger final initialization. 20 | * All arguments after 'stage' are passed to RollbarNotifier's constructor. 21 | * 22 | * @param stage The stage 23 | * @param accessToken Rollbar project access token 24 | * @param environment Environment name (i.e. "development", "production") 25 | * @param person Person identifier string or object or function which returns an object. 26 | * @param rootPath Path to the application code root, not including the final slash. 27 | * @param srcPath Path to the source code root, not including the final slash. 28 | * @param codeBranch Code branch name, e.g. "master" 29 | * @param serverData Object containing server information, will be passed along with error reports 30 | * @param maxItemCount Max number of items to report per load. 31 | */ 32 | public static function init(parent:DisplayObjectContainer, 33 | accessToken:String, environment:String, person:* = null, 34 | rootPath:String = null, srcPath:String = null, 35 | codeBranch:String = null, serverData:Object = null, 36 | maxItemCount:int = 5, endpointUrl:String = null):void { 37 | 38 | if (notifier !== null) { 39 | trace("WARNING: Rollbar.init() called more than once. Subsequent calls ignored."); 40 | return; 41 | } 42 | 43 | notifier = new RollbarNotifier(accessToken, environment, person, rootPath, srcPath, 44 | codeBranch, serverData, maxItemCount, endpointUrl); 45 | parent.addChild(notifier); 46 | } 47 | 48 | public static function handleError(err:Error, extraData:Object = null):String { 49 | if (notifier === null) { 50 | trace("WARNING: Rollbar.handleError() called before init(). Call ignored."); 51 | return null; 52 | } 53 | return notifier.handleError(err, extraData); 54 | } 55 | 56 | public static function handleErrorEvent(event:ErrorEvent):String { 57 | if (notifier === null) { 58 | trace("WARNING: Rollbar.handleErrorEvent() called before init(). Call ignored."); 59 | return null; 60 | } 61 | return notifier.handleErrorEvent(event); 62 | } 63 | 64 | public static function handleOtherEvent(event:*):String { 65 | if (notifier === null) { 66 | trace("WARNING: Rollbar.handleOtherEvent() called before init(). Call ignored."); 67 | return null; 68 | } 69 | return notifier.handleOtherEvent(event); 70 | } 71 | 72 | public static function setCodeVersion(codeVersion:String):void { 73 | if (notifier === null) { 74 | trace("WARNING: Rollbar.setCodeVersion() called before init(). Call ignored."); 75 | return; 76 | } 77 | notifier.setCodeVersion(codeVersion); 78 | } 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/com/rollbar/notifier/RollbarNotifier.as: -------------------------------------------------------------------------------- 1 | package com.rollbar.notifier { 2 | 3 | import flash.display.Sprite; 4 | import flash.display.LoaderInfo; 5 | 6 | import flash.errors.IllegalOperationError; 7 | 8 | import flash.events.Event; 9 | import flash.events.ErrorEvent; 10 | import flash.events.EventDispatcher; 11 | import flash.events.HTTPStatusEvent; 12 | import flash.events.IOErrorEvent; 13 | import flash.events.IEventDispatcher; 14 | import flash.events.SecurityErrorEvent; 15 | import flash.events.UncaughtErrorEvent; 16 | 17 | import flash.external.ExternalInterface; 18 | 19 | import flash.net.URLLoader; 20 | import flash.net.URLLoaderDataFormat; 21 | import flash.net.URLRequest; 22 | import flash.net.URLRequestMethod; 23 | import flash.net.URLVariables; 24 | 25 | import flash.system.Capabilities; 26 | import flash.system.System; 27 | 28 | import flash.utils.getTimer; 29 | 30 | import com.laiyonghao.Uuid; 31 | 32 | import com.rollbar.json.JSONEncoder; 33 | import com.rollbar.stacktrace.StackTrace; 34 | import com.rollbar.stacktrace.StackTraceParser; 35 | 36 | [Event(name="complete", type="flash.events.Event")] 37 | [Event(name="httpStatus", type="flash.events.HTTPStatusEvent")] 38 | [Event(name="ioError", type="flash.events.IOErrorEvent")] 39 | [Event(name="securityError", type="flash.events.SecurityErrorEvent")] 40 | public final class RollbarNotifier extends Sprite { 41 | 42 | private static const API_ENDPONT_URL:String = "https://api.rollbar.com/api/1/item/"; 43 | private static const NOTIFIER_DATA:Object = {name: "flash_rollbar", version: "0.9.2"}; 44 | private static const MAX_ITEM_COUNT:int = 5; 45 | 46 | private static var instance:RollbarNotifier = null; 47 | 48 | // Keep URLLoaders from being garbage collected before they finish 49 | private var loaders:Array; 50 | 51 | private var accessToken:String; 52 | private var environment:String; 53 | private var swfUrl:String; 54 | private var embeddedUrl:String; 55 | private var queryString:String; 56 | private var serverData:Object; 57 | private var itemCount:int = 0; 58 | private var endpointUrl:String; 59 | private var maxItemCount:int; 60 | private var personFn:Function; 61 | private var userId:String; 62 | private var person:Object; 63 | private var startTime:int; 64 | private var branch:String; 65 | private var rootPath:String; 66 | private var srcPath:String; 67 | private var codeVersion:String; 68 | 69 | public function RollbarNotifier(accessToken:String, 70 | environment:String, 71 | person:* = null, 72 | rootPath:String = null, 73 | srcPath:String = null, 74 | codeBranch:String = null, 75 | serverData:Object = null, 76 | maxItemCount:int = 5, 77 | endpointUrl:String = null) { 78 | this.accessToken = accessToken; 79 | this.environment = environment; 80 | this.serverData = serverData || {}; 81 | this.endpointUrl = endpointUrl || API_ENDPONT_URL; 82 | this.maxItemCount = maxItemCount || MAX_ITEM_COUNT; 83 | this.branch = codeBranch || "master"; 84 | this.rootPath = rootPath; 85 | this.srcPath = srcPath; 86 | 87 | this.loaders = new Array(); 88 | 89 | if (person) { 90 | if (person is Function) { 91 | this.personFn = person; 92 | } else if (person is String) { 93 | this.userId = person; 94 | } else if (person is Object) { 95 | this.person = person; 96 | this.userId = resolveField(['id', 'userId', 'user_id', 'user'], person); 97 | } else { 98 | this.userId = '' + person; 99 | } 100 | } 101 | 102 | addEventListener(Event.ADDED_TO_STAGE, function(event:Event):void { 103 | swfUrl = unescape(parent.loaderInfo.url); 104 | embeddedUrl = getEmbeddedUrl(); 105 | queryString = getQueryString(); 106 | 107 | // Register for uncaught errors if >= 10.1. 108 | if (parent.loaderInfo.hasOwnProperty('uncaughtErrorEvents')) { 109 | var uncaughtErrorEvents:IEventDispatcher = IEventDispatcher(parent.loaderInfo.uncaughtErrorEvents); 110 | uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, handleUncaughtError); 111 | } 112 | }); 113 | } 114 | 115 | public function dispose():void { 116 | } 117 | 118 | public function handleError(err:Error, extraData:Object = null):String { 119 | var stackTrace:String = err.getStackTrace(); 120 | if (stackTrace !== null) { 121 | // we got a stack trace (we're in the debug player). 122 | return handleStackTrace(stackTrace, extraData); 123 | } else { 124 | // no stack trace. just report the basics. 125 | return handlePlainError(err.errorID, err.name, err.message, extraData); 126 | } 127 | } 128 | 129 | public function handleErrorEvent(event:ErrorEvent):String { 130 | var newError:Error = new Error("An ErrorEvent was thrown and not caught: " + event.toString()); 131 | return handleError(newError); 132 | } 133 | 134 | public function handleOtherEvent(event:*):String { 135 | var newError:Error = new Error("A non-Error or ErrorEvent was thrown and not caught: " + event.toString()); 136 | return handleError(newError); 137 | } 138 | 139 | public function setCodeVersion(codeVersion:String):void { 140 | this.codeVersion = codeVersion; 141 | } 142 | 143 | private function handleUncaughtError(event:UncaughtErrorEvent):String { 144 | if (event.error is Error) { 145 | var error:Error = event.error as Error; 146 | return handleError(error); 147 | } else if (event.error is ErrorEvent) { 148 | var errorEvent:ErrorEvent = event.error as ErrorEvent; 149 | return handleErrorEvent(errorEvent); 150 | } else { 151 | // Inform the user that a non-error event was thrown and not caught. 152 | return handleOtherEvent(event); 153 | } 154 | } 155 | 156 | private function handleStackTrace(stackTrace:String, extraData:Object):String { 157 | var payload:Object = buildDebugPayload(stackTrace, extraData); 158 | sendPayload(payload); 159 | return payload['data']['uuid']; 160 | } 161 | 162 | private function handlePlainError(errorID:int, name:String, message:String, extraData:Object):String { 163 | var payload:Object = buildReleasePayload(errorID, name, message, extraData); 164 | sendPayload(payload); 165 | return payload['data']['uuid']; 166 | } 167 | 168 | private function sendPayload(payload:Object):void { 169 | if (itemCount < this.maxItemCount) { 170 | var request:URLRequest = new URLRequest(); 171 | request.method = URLRequestMethod.POST; 172 | request.data = JSONEncoder.encode(payload); 173 | request.url = this.endpointUrl; 174 | 175 | var loader:URLLoader = new URLLoader(); 176 | loader.dataFormat = URLLoaderDataFormat.TEXT; 177 | 178 | var handler:Function = function(event:Event):void { 179 | for (var i:int = 0; i < loaders.length; ++i) { 180 | if (loaders[i] == loader) { 181 | loaders.splice(i, 1); 182 | break; 183 | } 184 | } 185 | 186 | loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, handler); 187 | loader.removeEventListener(IOErrorEvent.IO_ERROR, handler); 188 | loader.removeEventListener(Event.COMPLETE, handler); 189 | 190 | dispatchEvent(event); 191 | } 192 | 193 | loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handler); 194 | loader.addEventListener(IOErrorEvent.IO_ERROR, handler); 195 | loader.addEventListener(Event.COMPLETE, handler); 196 | 197 | loaders.push(loader); 198 | 199 | loader.load(request); 200 | itemCount++; 201 | } else { 202 | // too many handled items 203 | } 204 | } 205 | 206 | private function getEmbeddedUrl():String { 207 | if (ExternalInterface.available) { 208 | return ExternalInterface.call("window.location.href.toString"); 209 | } 210 | return null; 211 | } 212 | 213 | private function getQueryString():String { 214 | if (ExternalInterface.available) { 215 | return ExternalInterface.call("window.location.search.substring", 1); 216 | } 217 | return null; 218 | } 219 | 220 | private function getBrowserUserAgent():String { 221 | if (ExternalInterface.available) { 222 | return ExternalInterface.call("window.navigator.userAgent.toString"); 223 | } 224 | return null; 225 | } 226 | 227 | /** 228 | * Builds and returns a payload object using the information available in the Release player. 229 | * 230 | * errorID, name, and message should come from the relevant Error object. 231 | */ 232 | private function buildReleasePayload(errorID:int, name:String, message:String, extraData:Object):Object { 233 | var messageTitle:String = name + ": " + message; 234 | var messageBody:String = "Error ID: " + errorID + "\n" + messageTitle; 235 | 236 | var payload:Object = buildCommonPayload(extraData); 237 | payload.data.body = { 238 | message: { 239 | body: messageBody, 240 | error_id: errorID 241 | } 242 | }; 243 | payload.data.title = messageTitle; 244 | return payload; 245 | } 246 | 247 | /** 248 | * Builds and returns a payload object using the information available in the Debug player. 249 | * 250 | * stackTrace should come from error.getStackTrace() 251 | */ 252 | private function buildDebugPayload(stackTrace:String, extraData:Object):Object { 253 | var stackTraceObj:StackTrace = StackTraceParser.parseStackTrace(srcPath, stackTrace); 254 | 255 | var payload:Object = buildCommonPayload(extraData); 256 | payload.data.body = { 257 | trace: { 258 | frames: stackTraceObj.frames, 259 | exception: { 260 | 'class': stackTraceObj.errorClassName, 261 | message: stackTraceObj.message 262 | } 263 | } 264 | }; 265 | return payload; 266 | } 267 | 268 | private function resolveField(fieldNames:Array, storage:Object):String { 269 | var index:Number; 270 | var len:Number = fieldNames.length; 271 | for (index = 0; index < len; ++index) { 272 | try { 273 | return storage[fieldNames[index]].toString(); 274 | } catch (e:*) { 275 | // ignore 276 | } 277 | } 278 | return null; 279 | } 280 | 281 | /** 282 | * Builds and returns common payload data. Used by buildReleasePayload and buildDebugPayload. 283 | */ 284 | private function buildCommonPayload(extraData:Object):Object { 285 | var tmpPerson:Object = this.person || (this.personFn != null ? this.personFn() : null); 286 | var userId:String = this.userId || resolveField(['id', 'userId', 'user_id', 'user'], tmpPerson); 287 | var person:Object; 288 | 289 | if (userId) { 290 | person = {id: userId}; 291 | var email:String = resolveField(['email', 'emailAddress', 'email_address', 292 | 'userEmail', 'user_email'], tmpPerson); 293 | var username:String = resolveField(['username', 'userName', 'user_name', 'name'], tmpPerson); 294 | 295 | if (email) { 296 | person['email'] = email; 297 | } 298 | if (username) { 299 | person['username'] = username; 300 | } 301 | } 302 | 303 | var payload:Object = { 304 | access_token: accessToken, 305 | data: { 306 | environment: environment, 307 | platform: "flash", 308 | language: "as3", 309 | request: { 310 | url: embeddedUrl, 311 | query_string: queryString, 312 | user_ip: "$remote_ip", 313 | user_id: userId 314 | }, 315 | client: { 316 | runtime_ms: getTimer(), 317 | timestamp: int((new Date()).getTime() / 1000), 318 | root: rootPath, 319 | branch: branch, 320 | flash: { 321 | browser: getBrowserUserAgent(), 322 | swf_url: swfUrl, 323 | player: { 324 | freeMemory: System.freeMemory, 325 | privateMemory: System.privateMemory, 326 | totalMemory: System.totalMemory, 327 | capabilities: { 328 | avHardwareDisable: Capabilities.avHardwareDisable, 329 | cpuArchitecture: Capabilities.cpuArchitecture, 330 | externalInterfaceAvailable: ExternalInterface.available, 331 | hasAccessibility: Capabilities.hasAccessibility, 332 | hasAudio: Capabilities.hasAudio, 333 | isDebugger: Capabilities.isDebugger, 334 | language: Capabilities.language, 335 | localFileReadDisable: Capabilities.localFileReadDisable, 336 | manufacturer: Capabilities.manufacturer, 337 | os: Capabilities.os, 338 | pixelAspectRatio: Capabilities.pixelAspectRatio, 339 | playerType: Capabilities.playerType, 340 | screenDPI: Capabilities.screenDPI, 341 | screenResolutionX: Capabilities.screenResolutionX, 342 | screenResolutionY: Capabilities.screenResolutionY, 343 | version: Capabilities.version 344 | } 345 | } 346 | } 347 | }, 348 | server: serverData, 349 | notifier: NOTIFIER_DATA, 350 | uuid: new Uuid().toString().toLowerCase() 351 | } 352 | }; 353 | 354 | if (this.codeVersion) { 355 | payload['data']['code_version'] = this.codeVersion; 356 | } 357 | 358 | if (person) { 359 | payload['data']['person'] = person; 360 | } 361 | 362 | if (extraData) { 363 | for (var k:String in extraData) { 364 | payload['data'][k] = extraData[k]; 365 | } 366 | } 367 | 368 | return payload; 369 | } 370 | 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/com/rollbar/notifier/Test.as: -------------------------------------------------------------------------------- 1 | package com.rollbar.notifier { 2 | import flash.display.Sprite; 3 | import flash.text.TextField; 4 | import flash.text.TextFieldAutoSize; 5 | import flash.text.TextFormat; 6 | import flash.text.TextFormatAlign; 7 | import flash.events.*; 8 | 9 | import com.rollbar.notifier.Rollbar; 10 | 11 | public class Test extends Sprite { 12 | // change to your own access token 13 | public static const ACCESS_TOKEN:String = '2f5ce4c49aec41c2bba8c3c5c448f8b2'; 14 | public static const ENV:String = 'production'; 15 | 16 | protected var caughtButton:Sprite = new Sprite(); 17 | protected var uncaughtButton:Sprite = new Sprite(); 18 | 19 | public function Test() { 20 | trace('Building Rollbar test...'); 21 | 22 | // Initialize the Rollbar notifier. 23 | /* 24 | Rollbar.init(this, // pass this sprite as first param 25 | ACCESS_TOKEN, // your rollbar project access token 26 | ENV, // environment name - i.e. "production" or "development" 27 | function():Object { 28 | return {user_id: "user123", name: "Cory Virok"} 29 | }, // user fn/id (optional). 30 | "/Users/coryvirok/Development/flash_rollbar", // the path to the project root, 31 | // not including the final slash. 32 | // Note: if the SWF/SWC is compiled with compiler.verbose-stacktraces=true 33 | // or -debug, you'll want to have this path reflect the root path from the 34 | // user who published the SWF/SWC file. Otherwise, you can set it to the 35 | // source directory of your project, e.g. "src". 36 | "/Users/coryvirok/Development/flash_rollbar/src" // the source code path 37 | ); 38 | */ 39 | 40 | Rollbar.init(this, ACCESS_TOKEN, ENV); 41 | Rollbar.handleError(new Error("flash test error", 666)); 42 | 43 | mouseEnabled = true; 44 | mouseChildren = true; 45 | 46 | // Draw some buttons. 47 | 48 | caughtButton.graphics.clear(); 49 | caughtButton.graphics.beginFill(0xD4D4D4); 50 | caughtButton.graphics.drawRoundRect(0, 100, 200, 50, 20, 20); 51 | caughtButton.graphics.endFill(); 52 | addChild(caughtButton); 53 | 54 | uncaughtButton.graphics.clear(); 55 | uncaughtButton.graphics.beginFill(0xD4D4D4); 56 | uncaughtButton.graphics.drawRoundRect(0, 200, 200, 50, 20, 20); 57 | uncaughtButton.graphics.endFill(); 58 | addChild(uncaughtButton); 59 | 60 | var format:TextFormat = new TextFormat(); 61 | format.size = 20; 62 | 63 | var format2:TextFormat = new TextFormat(); 64 | format2.size = 20; 65 | 66 | var caughtErrText:TextField = new TextField(); 67 | caughtErrText.defaultTextFormat = format; 68 | caughtErrText.autoSize = TextFieldAutoSize.RIGHT; 69 | caughtErrText.text = 'Cause Error'; 70 | caughtErrText.x = 10; 71 | caughtErrText.y = 105; 72 | caughtErrText.selectable = false; 73 | caughtButton.addChild(caughtErrText); 74 | 75 | var uncaughtErrText:TextField = new TextField(); 76 | uncaughtErrText.defaultTextFormat = format2; 77 | uncaughtErrText.autoSize = TextFieldAutoSize.RIGHT; 78 | uncaughtErrText.text = 'Cause Uncaught Error'; 79 | uncaughtErrText.x = 10; 80 | uncaughtErrText.y = 205; 81 | uncaughtErrText.selectable = false; 82 | uncaughtButton.addChild(uncaughtErrText); 83 | 84 | caughtButton.addEventListener(MouseEvent.MOUSE_DOWN, caughtMouseDownHandler); 85 | uncaughtButton.addEventListener(MouseEvent.MOUSE_DOWN, uncaughtMouseDownHandler); 86 | } 87 | 88 | private function caughtMouseDownHandler(event:MouseEvent):void { 89 | try { 90 | trace('causing error within try/catch'); 91 | causeError(); 92 | } catch (e:Error) { 93 | trace('caught error within try/catch'); 94 | Rollbar.handleError(e); 95 | } 96 | } 97 | 98 | private function uncaughtMouseDownHandler(event:MouseEvent):void { 99 | trace('caught uncaught error'); 100 | causeError(); 101 | } 102 | 103 | private function causeError():void { 104 | throw new Error('dummy'); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/com/rollbar/stacktrace/StackTrace.as: -------------------------------------------------------------------------------- 1 | package com.rollbar.stacktrace { 2 | import flash.system.Capabilities; 3 | public class StackTrace { 4 | public var errorClassName:String = ""; 5 | public var message:String = ""; 6 | public var lines:Vector.; 7 | public var srcPath:String; 8 | 9 | private var separator:String; 10 | 11 | public function StackTrace(srcPath:String) { 12 | this.srcPath = srcPath || ""; 13 | lines = new Vector.(); 14 | if (this.srcPath && this.srcPath.charAt(0) == '/') { 15 | separator = '/'; 16 | } else { 17 | separator = "\\"; 18 | } 19 | } 20 | 21 | public function get frames():Array { 22 | var ret:Array = new Array(); 23 | var filename:String; 24 | for each (var line:StackTraceLine in lines) { 25 | if (line.file.indexOf(srcPath) === -1) { 26 | if (line.file.charAt(0) != separator && 27 | srcPath.charAt(srcPath.length - 1) != separator) { 28 | srcPath = srcPath + separator; 29 | } 30 | filename = srcPath + line.file 31 | } else { 32 | filename = line.file; 33 | } 34 | ret.splice(0, 0, {filename: filename, 35 | lineno: int(line.number), 36 | method: line.method, 37 | code: null}); 38 | } 39 | return ret; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/rollbar/stacktrace/StackTraceLine.as: -------------------------------------------------------------------------------- 1 | package com.rollbar.stacktrace { 2 | public class StackTraceLine { 3 | public var file:String = ""; 4 | public var number:String = ""; 5 | public var method:String = ""; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/com/rollbar/stacktrace/StackTraceParser.as: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/StephanPartzsch/as3-airbrake-notifier 2 | package com.rollbar.stacktrace { 3 | public class StackTraceParser { 4 | public static function parseStackTrace(srcPath:String, stackTraceString:String):StackTrace { 5 | var data:Array = stackTraceString.split("\tat "); 6 | var stackTrace:StackTrace = new StackTrace(srcPath); 7 | 8 | parseErrorClassAndMessage(data.shift(), stackTrace); 9 | for (var i:int = 0; i < data.length; i++) { 10 | stackTrace.lines.push(parseStackTraceLine(data[i])); 11 | } 12 | 13 | return stackTrace; 14 | } 15 | 16 | private static function parseErrorClassAndMessage(input:String, stackTrace:StackTrace):void { 17 | var index:int = input.indexOf(": "); 18 | stackTrace.errorClassName = trim(input.substr(0, index)); 19 | stackTrace.message = trim(input.substr(index + 2)); 20 | } 21 | 22 | private static function parseStackTraceLine(lineString:String):StackTraceLine { 23 | var stackTraceLine:StackTraceLine = new StackTraceLine(); 24 | 25 | // Grab method name by looking from either ':' or '/' up to and including '()' 26 | var methodPat:RegExp = /[:|\/]([\w<>]+\(\))/; 27 | var methodName:String = methodPat.exec(lineString)[1]; 28 | 29 | stackTraceLine.method = methodName; 30 | 31 | // Grab debug file path if it exists by looking from a '[' to a ':' 32 | var filePath:String; 33 | var debugFilePat:RegExp = /\[([\w\/\.]+):/; 34 | var debugFilePatMatch:Object = debugFilePat.exec(lineString); 35 | if (debugFilePatMatch) { 36 | filePath = debugFilePatMatch[1]; 37 | } 38 | 39 | if (filePath) { // Debug line with file path and line number 40 | var linePat:RegExp = /:(\d+)\]/; 41 | var lineNumber:String = linePat.exec(lineString)[1]; 42 | 43 | stackTraceLine.number = lineNumber; 44 | stackTraceLine.file = filePath; 45 | } else { // Non-debug line with no file path and no line number 46 | var filePat:RegExp = /([\w\.:]+)[\(|\/]/; 47 | filePath = filePat.exec(lineString)[1]; 48 | 49 | stackTraceLine.number = ""; 50 | stackTraceLine.file = filePath.replace(/[\.|::]/g, '/') + '.as'; 51 | } 52 | 53 | return stackTraceLine; 54 | } 55 | 56 | // http://jeffchannell.com/ActionScript-3/as3-trim.html 57 | public static function trim(s:String):String { 58 | return s ? s.replace(/^\s+|\s+$/gs, '') : ""; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/com/rollbar/tests/ErrorCausingConstructorClass.as: -------------------------------------------------------------------------------- 1 | package com.rollbar.tests { 2 | public class ErrorCausingConstructorClass { 3 | public function ErrorCausingConstructorClass() { 4 | throw new Error('dummy'); 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/com/rollbar/tests/StackTraceParserTest.as: -------------------------------------------------------------------------------- 1 | package com.rollbar.tests { 2 | import flash.display.Sprite; 3 | 4 | import flash.events.Event; 5 | import flash.events.ErrorEvent; 6 | import flash.events.EventDispatcher; 7 | import flash.events.HTTPStatusEvent; 8 | import flash.events.IOErrorEvent; 9 | import flash.events.IEventDispatcher; 10 | import flash.events.SecurityErrorEvent; 11 | import flash.events.UncaughtErrorEvent; 12 | 13 | import org.flexunit.Assert; 14 | import org.flexunit.async.Async; 15 | 16 | import com.rollbar.tests.ErrorCausingConstructorClass; 17 | import com.rollbar.stacktrace.StackTrace; 18 | import com.rollbar.stacktrace.StackTraceParser; 19 | 20 | public class StackTraceParserTest { 21 | [BeforeClass] 22 | public static function runBeforeClass():void { 23 | } 24 | 25 | [Before] 26 | public function before():void { 27 | } 28 | 29 | [After] 30 | public function runAfterEveryTest():void { 31 | } 32 | 33 | [Test] 34 | public function testNormalFrame():void { 35 | try { 36 | causeError(); 37 | } catch (e:Error) { 38 | var frames:Array = getFrames(e); 39 | var lastFrame:Object = frames[frames.length - 1]; 40 | 41 | Assert.assertTrue(lastFrame.filename.indexOf('StackTraceParserTest.as') != -1); 42 | Assert.assertTrue(lastFrame.method == 'causeError()'); 43 | } 44 | } 45 | 46 | [Test] 47 | public function testConstructorFrame():void { 48 | try { 49 | new ErrorCausingConstructorClass(); 50 | } catch (e:Error) { 51 | var frames:Array = getFrames(e); 52 | var lastFrame:Object = frames[frames.length - 1]; 53 | 54 | Assert.assertTrue(lastFrame.filename.indexOf('ErrorCausingConstructorClass.as') != -1); 55 | Assert.assertTrue(lastFrame.method == 'ErrorCausingConstructorClass()'); 56 | } 57 | } 58 | 59 | [Test] 60 | public function testAnonymousFrame():void { 61 | var func:Function = function():void { 62 | try { 63 | causeError(); 64 | } catch (e:Error) { 65 | var frames:Array = getFrames(e); 66 | var lastFrame:Object = frames[frames.length - 1]; 67 | 68 | Assert.assertTrue(lastFrame.filename.indexOf('StackTraceParserTest.as') != -1); 69 | Assert.assertTrue(lastFrame.method == 'causeError()'); 70 | } 71 | } 72 | 73 | func(); 74 | } 75 | 76 | protected function causeError():void { 77 | throw new Error('dummy'); 78 | } 79 | 80 | protected function getFrames(error:Error):Array { 81 | var stackTraceString:String = error.getStackTrace(); 82 | var stackTraceObj:StackTrace = StackTraceParser.parseStackTrace(null, stackTraceString); 83 | return stackTraceObj.frames; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/com/rollbar/tests/TestRunner.as: -------------------------------------------------------------------------------- 1 | package com.rollbar.tests { 2 | import flash.display.Sprite; 3 | 4 | import org.flexunit.internals.TraceListener; 5 | import org.flexunit.runner.FlexUnitCore; 6 | 7 | import com.rollbar.tests.StackTraceParserTest; 8 | 9 | import com.rollbar.notifier.Rollbar; 10 | 11 | public class TestRunner extends Sprite { 12 | private var core:FlexUnitCore; 13 | 14 | public function TestRunner() { 15 | super(); 16 | 17 | Rollbar.init(this, 'aaaabbbbccccddddeeeeffff00001111', 'production'); 18 | 19 | core = new FlexUnitCore(); 20 | core.addListener(new TraceListener()); 21 | core.run(StackTraceParserTest); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /static/js/swfobject.js: -------------------------------------------------------------------------------- 1 | /* SWFObject v2.2 2 | is released under the MIT License 3 | */ 4 | var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab