├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── bower.json ├── dist ├── aws-sdk-mobile-analytics.js └── aws-sdk-mobile-analytics.min.js ├── doc ├── AMA.Client.html ├── AMA.Manager.html ├── AMA.Session.html ├── MobileAnalyticsClient.js.html ├── MobileAnalyticsSession.js.html ├── MobileAnalyticsSessionManager.js.html ├── ama.js.html ├── index.html ├── module-AMA.html ├── scripts │ ├── linenumber.js │ └── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js └── styles │ ├── jsdoc-default.css │ ├── prettify-jsdoc.css │ └── prettify-tomorrow.css ├── grunttasks ├── blanket.js ├── browserify.js ├── clean.js ├── copy.js ├── jshint.js ├── mocha.js ├── mochaTest.js └── uglify.js ├── lib ├── MobileAnalyticsClient.js ├── MobileAnalyticsSession.js ├── MobileAnalyticsSessionManager.js ├── MobileAnalyticsUtilities.js ├── StorageClients │ ├── LocalStorage.js │ └── StorageKeys.js └── ama.js ├── package.json └── test ├── browser ├── aws-sdk.min.js ├── mocha.css ├── mocha.js └── unittests.html ├── helpers.coffee ├── mobileAnalyticsClient.coffee ├── mobileAnalyticsSessionClient.coffee ├── session.coffee ├── storage.coffee ├── utils.coffee └── v090_migration_v091.coffee /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws/aws-sdk-mobile-analytics-js/issues), or [recently closed](https://github.com/aws/aws-sdk-mobile-analytics-js/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/aws-sdk-mobile-analytics-js/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws/aws-sdk-mobile-analytics-js/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | */ 9 | 10 | module.exports = function (grunt) { 11 | 'use strict'; 12 | 13 | require('load-grunt-tasks')(grunt); 14 | grunt.loadTasks('grunttasks'); 15 | 16 | // server-side tests 17 | grunt.registerTask('test-nodejs', ['blanket', 'copy:test-setup', 'mochaTest']); 18 | // client-side tests 19 | grunt.registerTask('test-phantomjs', ['copy:test-setup', 'copy:browserify-test-setup', 'copy:browserify-dist-setup', 'browserify:test', 'uglify:test', 'mocha']); 20 | 21 | // test, dist and release 22 | grunt.registerTask('test', ['jshint', 'test-nodejs', 'test-phantomjs']); 23 | grunt.registerTask('dist', ['copy:browserify-dist-setup', 'browserify:dist', 'uglify:dist']); 24 | grunt.registerTask('release', ['test', 'dist', 'clean']); 25 | }; -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 4 | 1. Definitions. 5 | 6 | “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 7 | 8 | “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 9 | 10 | “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 11 | 12 | “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. 13 | 14 | “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 15 | 16 | “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 17 | 18 | “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 19 | 20 | “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 21 | 22 | “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” 23 | 24 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 25 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 26 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 27 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 28 | 29 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 30 | You must cause any modified files to carry prominent notices stating that You changed the files; and 31 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 32 | If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 33 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 34 | 35 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 36 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 37 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 38 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 39 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 40 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Amazon Mobile Analytics SDK for JavaScript 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon Mobile Analytics SDK for JavaScript 2 | 3 | **Developer Preview:** We welcome developer feedback on this project. You can reach us by creating an issue on the 4 | GitHub repository or posting to the Amazon Mobile Analytics forums: 5 | * https://github.com/aws/aws-sdk-mobile-analytics-js 6 | * https://forums.aws.amazon.com/forum.jspa?forumID=174 7 | 8 | Introduction 9 | ============ 10 | The Mobile Analytics SDK for JavaScript allows JavaScript enabled applications to create and submit events for analysis in the AWS Console and through Auto Export to S3 and Redshift. The library uses the browser's local storage API to create a local cache for the data, allowing your web application to batch and record events even when the app is offline. 11 | 12 | ## Setup 13 | 14 | 1. Download and include the AWS JavaScript SDK (minimum version 2.1.18): 15 | * http://aws.amazon.com/sdk-for-browser/ 16 | 17 | 2. Download and include the Amazon Mobile Analytics SDK for JavaScript: 18 | * [/dist/aws-sdk-mobile-analytics.min.js](https://raw.githubusercontent.com/aws/aws-sdk-mobile-analytics-js/master/dist/aws-sdk-mobile-analytics.min.js) 19 | 20 |
 21 |     <script src="/js/aws-sdk.min.js"></script>
 22 |     <script src="/js/aws-sdk-mobile-analytics.min.js"></script>
 23 | 
24 | 25 | ## Usage 26 | 27 | **Step 1.** Log in to the [Amazon Mobile Analytics management console](https://console.aws.amazon.com/mobileanalytics/home/?region=us-east-1) and create a new app. Be sure to note your App Id and Cognito Identity Pool Id. 28 | * https://console.aws.amazon.com/mobileanalytics/home/?region=us-east-1 29 | 30 | **Step 2.** Initialize the credentials provider using a Cognito Identity Pool ID. This is necessary for the AWS SDK to manage authentication to the Amazon Mobile Analytics REST API. 31 | 32 |
 33 |     AWS.config.region = 'us-east-1';
 34 |     AWS.config.credentials = new AWS.CognitoIdentityCredentials({
 35 |         IdentityPoolId: COGNITO_IDENTITY_POOL_ID   //Required e.g. 'us-east-1:12345678-c1ab-4122-913b-22d16971337b'
 36 |     });
 37 | 
38 | 39 | **Step 3.** Instantiate the Mobile Analytics Manager, including your App ID generated in Step 1, above. Session events will be automatically recorded and the client will batch and automatically submit events to Amazon Mobile Analytics every 10 seconds. 40 | 41 |
 42 |     var options = {
 43 |         appId : MOBILE_ANALYTICS_APP_ID   //Required e.g. 'c5d69c75a92646b8953126437d92c007'
 44 |     };
 45 |     mobileAnalyticsClient = new AMA.Manager(options);
 46 | 
47 | 48 | To manually force an event submission you can call: 49 |
 50 |     mobileAnalyticsClient.submitEvents();
 51 | 
52 | 53 | ## Additional Options 54 | ### Custom Events 55 | You can optionally add custom events to capture additional information you find valuable. 56 | 57 |
 58 |     mobileAnalyticsClient.recordEvent('CUSTOM EVENT NAME', {
 59 |             'ATTRIBUTE_1_NAME': 'ATTRIBUTE_1_VALUE',
 60 |             'ATTRIBUTE_2_NAME': 'ATTRIBUTE_2_VALUE'
 61 |             /* ... */
 62 |         }, {
 63 |             'METRIC_1_NAME': 1,
 64 |             'METRIC_2_NAME': 99.3
 65 |             /* ... */
 66 |         });
 67 | 
68 | 69 | 70 | ### Session Settings 71 | By default a session lasts 10 minutes. You can override this default setting when initializing the Mobile Analytics Manager by including "sessionLength" in the "options" object. 72 | 73 |
 74 |     var options = {
 75 |         appId : MOBILE_ANALYTICS_APP_ID, 
 76 |         sessionLength: 300000            //Session Length in milliseconds.  This will evaluate to 5min.
 77 |     };
 78 |     mobileAnalyticsClient = new AMA.Manager(options);
 79 | 
80 | 81 | A session's timeout can also be updated to allow for continuation of a session. 82 | 83 |
 84 |     //This will set the current session to expire in 5 seconds from now.
 85 |     mobileAnalyticsClient.resetSessionTimeout(5000); 
 86 |     
 87 |     //This will reset the current session's expiration time using the time specified during initialization. 
 88 |     //If the default setting was used (10 minutes) then the session will expire 10 minutes from now. 
 89 |     mobileAnalyticsClient.resetSessionTimeout();
 90 | 
91 | 92 | ### Record Monetization Event 93 | You can record monetization events to enable reports such as Average Revenue Per User (ARPU) and more. 94 | 95 |
 96 |     mobileAnalyticsClient.recordMonetizationEvent(
 97 |         {
 98 |             productId : PRODUCT_ID,   //Required e.g. 'My Example Product'
 99 |             price : PRICE,            //Required e.g. 1.99
100 |             quantity : QUANTITY,      //Required e.g. 1
101 |             currency : CURRENCY_CODE  //Optional ISO currency code e.g. 'USD'
102 |         }, 
103 |         {/* Custom Attributes */}, 
104 |         {/* Custom Metrics */}
105 |     );
106 | 
107 | 108 | ### Add App Details to Events 109 | Additional app and environment details can be added to the "options" object when initializing the SDK. These details will be captured and applied to all events and can be useful if using Auto Export for custom analysis of your data. 110 | 111 |
112 |     var options = {
113 |         appId : MOBILE_ANALYTICS_APP_ID,       //Required e.g. 'c5d69c75a92646b8953126437d92c007'
114 |         appTitle : APP_TITLE,                  //Optional e.g. 'Example App'
115 |         appVersionName : APP_VERSION_NAME,     //Optional e.g. '1.4.1'
116 |         appVersionCode : APP_VERSION_CODE,     //Optional e.g. '42'
117 |         appPackageName : APP_PACKAGE_NAME,     //Optional e.g. 'com.amazon.example'
118 |         make : DEVICE_MAKE,                    //Optional e.g. 'Amazon'
119 |         model : DEVICE_MODEL,                  //Optional e.g. 'KFTT'
120 |         platform : DEVICE_PLATFORM,            //Optional valid values: 'Android', 'iPhoneOS', 'WindowsPhone', 'Blackberry', 'Windows', 'MacOS', 'Linux'
121 |         platformVersion : DEVICE_PLATFORM_VER  //Optional e.g. '4.4'
122 |     };
123 |     mobileAnalyticsClient = new AMA.Manager(options);
124 | 
125 | 126 | Please note, if device details are not specified Amazon Mobile Analytics will make best efforts to determine these values based on the User-Agent header value. It is always better to specify these values during initialization if they are available. 127 | 128 | ### Further Documentation 129 | Further documentation and advanced configurations can be found here: 130 | 131 | https://aws.github.io/aws-sdk-mobile-analytics-js/doc/AMA.Manager.html 132 | 133 | ## Network Configuration 134 | The Amazon Mobile Analytics JavaScript SDK will make requests to the following endpoints 135 | * For Event Submission: "https://mobileanalytics.us-east-1.amazonaws.com" 136 | * For Cognito Authentication: "https://cognito-identity.us-east-1.amazonaws.com" 137 | * This endpoint may change based on which region your Identity Pool was created in. 138 | 139 | For most frameworks you can whitelist both domains by whitelisting all AWS endpoints with "*.amazonaws.com". 140 | 141 | ## Change Log 142 | **v0.9.2:** 143 | * Bug Fixes: 144 | * Fixed data loss issue on migration from previous versions 145 | 146 | **v0.9.1:** 147 | * Updated Dependency: aws-sdk-js v2.2.37 148 | * Increase base delay between retries from 30ms to 3s 149 | * Allow passing of configurations to the low level client via clientOptions attribute 150 | * Local events from different apps are stored in different locations 151 | * Improved retry strategies 152 | * CorrectClockSkew is enabled by default for Sigv4 Signatures [per Issue#7](https://github.com/aws/aws-sdk-mobile-analytics-js/issues/7) 153 | * Bug Fixes: 154 | * Fixed timer from being killed in cases where multiple submissions happened in under a second 155 | * Fixed duplicate batch re-submission to the Mobile Analytics service 156 | * Fixed delayed auto-submission of first _session.start event 157 | * Fixed Safari throwing exception when in private browsing mode 158 | 159 | **v0.9.0:** 160 | * Initial release. Developer preview. 161 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-sdk-mobile-analytics", 3 | "version": "0.9.2", 4 | "main": "dist/aws-sdk-mobile-analytics.min.js", 5 | "ignore": [ 6 | "lib", 7 | "node_modules", 8 | "test", 9 | "Gruntfile.js", 10 | "package.json" 11 | ], 12 | "dependencies": { 13 | "aws-sdk-js": ">=2.2.37" 14 | }, 15 | "devDependencies": { 16 | } 17 | } -------------------------------------------------------------------------------- /dist/aws-sdk-mobile-analytics.min.js: -------------------------------------------------------------------------------- 1 | !function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g50}function c(b){return a.attributes[b]&&a.attributes[b].length>200}function d(a){return e.logger.error(a),null}var e=this,f=[];return f=Object.keys(a.metrics).filter(function(b){return"number"!=typeof a.metrics[b]}),"v2.0"!==a.version?d("Event must have version v2.0"):"string"!=typeof a.eventType?d("Event Type must be a string"):f.length>0?d("Event Metrics must be numeric ("+f[0]+")"):Object.keys(a.metrics).length+Object.keys(a.attributes).length>40?d("Event Metric and Attribute Count cannot exceed 40"):Object.keys(a.attributes).filter(b).length?d("Event Attribute names must be 1-50 characters"):Object.keys(a.metrics).filter(b).length?d("Event Metric names must be 1-50 characters"):Object.keys(a.attributes).filter(c).length?d("Event Attribute values cannot be longer than 200 characters"):a},a.prototype.createEvent=function(a,b,c,e){var f=this;this.logger.log("[Function:(AMA.Client).createEvent]"+(a?"\neventType:"+a:"")+(b?"\nsession:"+b:"")+(c?"\nattributes:"+JSON.stringify(c):"")+(e?"\nmetrics:"+JSON.stringify(e):"")),c=c||{},e=e||{},d.Util.mergeObjects(c,this.options.globalAttributes),d.Util.mergeObjects(e,this.options.globalMetrics),Object.keys(c).forEach(function(a){if("string"!=typeof c[a])try{c[a]=JSON.stringify(c[a])}catch(b){f.logger.warn("Error parsing attribute "+a)}});var g={eventType:a,timestamp:(new Date).toISOString(),session:{id:b.id,startTimestamp:b.startTimestamp},version:"v2.0",attributes:c,metrics:e};return b.stopTimestamp&&(g.session.stopTimestamp=b.stopTimestamp,g.session.duration=new Date(g.stopTimestamp).getTime()-new Date(g.startTimestamp).getTime()),this.validateEvent(g)},a.prototype.pushEvent=function(a){if(!a)return-1;this.logger.log("[Function:(AMA.Client).pushEvent]"+(a?"\nevent:"+JSON.stringify(a):""));var b=this.outputs.events.push(a);return this.storage.set(this.StorageKeys.EVENTS,this.outputs.events),b-1},a.prototype.recordEvent=function(a,b,c,e){this.logger.log("[Function:(AMA.Client).recordEvent]"+(a?"\neventType:"+a:"")+(b?"\nsession:"+b:"")+(c?"\nattributes:"+JSON.stringify(c):"")+(e?"\nmetrics:"+JSON.stringify(e):""));var f,g=this.createEvent(a,b,c,e);return g?(f=this.pushEvent(g),d.Util.getRequestBodySize(this.outputs.events)>=this.options.batchSizeLimit&&this.submitEvents(),this.outputs.events[f]):null},a.prototype.recordMonetizationEvent=function(a,b,c,d){return this.logger.log("[Function:(AMA.Client).recordMonetizationEvent]"+(a?"\nsession:"+a:"")+(b?"\nmonetizationDetails:"+JSON.stringify(b):"")+(c?"\nattributes:"+JSON.stringify(c):"")+(d?"\nmetrics:"+JSON.stringify(d):"")),c=c||{},d=d||{},c._currency=b.currency||c._currency,c._product_id=b.productId||c._product_id,d._quantity=b.quantity||d._quantity,"number"==typeof b.price?d._item_price=b.price||d._item_price:c._item_price_formatted=b.price||c._item_price_formatted,this.recordEvent("_monetization.purchase",a,c,d)},a.prototype.submitEvents=function(a){a=a||{},a.submitCallback=a.submitCallback||this.options.submitCallback,this.logger.log("[Function:(AMA.Client).submitEvents]"+(a?"\noptions:"+JSON.stringify(a):"")),this.options.autoSubmitEvents&&(clearTimeout(this.outputs.timeoutReference),this.outputs.timeoutReference=setTimeout(this.submitEvents.bind(this),this.options.autoSubmitInterval));var b;return this.outputs.isThrottled&&this.throttlingSuppressionFunction()0?b="Prevented submission while batches are in flight":0===this.outputs.batches.length&&0===this.outputs.events.length?b="No batches or events to be submitted":this.outputs.lastSubmitTimestamp&&d.Util.timestamp()-this.outputs.lastSubmitTimestamp<1e3&&(b="Prevented multiple submissions in under a second"),b?(this.logger.warn(b),[]):(this.generateBatches(),this.outputs.lastSubmitTimestamp=d.Util.timestamp(),this.outputs.isThrottled?(this.logger.warn("Is throttled submitting first batch"),a.batchId=this.outputs.batchIndex[0],[this.submitBatchById(a)]):this.submitAllBatches(a))},a.prototype.throttlingSuppressionFunction=function(a){return a=a||d.Util.timestamp(),Math.pow(a-this.outputs.lastSubmitTimestamp,2)/Math.pow(6e4,2)},a.prototype.generateBatches=function(){for(;this.outputs.events.length>0;){var a=this.outputs.events.length;for(this.logger.log(this.outputs.events.length+" events to be submitted");a>1&&d.Util.getRequestBodySize(this.outputs.events.slice(0,a))>this.options.batchSizeLimit;)this.logger.log("Finding Batch Size ("+this.options.batchSizeLimit+"): "+a+"("+d.Util.getRequestBodySize(this.outputs.events.slice(0,a))+")"),a-=1;this.persistBatch(this.outputs.events.slice(0,a))&&(this.outputs.events.splice(0,a),this.storage.set(this.StorageKeys.EVENTS,this.outputs.events))}},a.prototype.persistBatch=function(a){if(this.logger.log(a.length+" events in batch"),d.Util.getRequestBodySize(a)<512e3){var b=d.Util.GUID();return this.outputs.batches[b]=a,this.storage.set(this.StorageKeys.BATCHES,this.outputs.batches),this.outputs.batchIndex.push(b),this.storage.set(this.StorageKeys.BATCH_INDEX,this.outputs.batchIndex),!0}return this.logger.error("Events too large"),!1},a.prototype.submitAllBatches=function(a){a.submitCallback=a.submitCallback||this.options.submitCallback,this.logger.log("[Function:(AMA.Client).submitAllBatches]"+(a?"\noptions:"+JSON.stringify(a):""));var b=[],c=this;return this.outputs.batchIndex.forEach(function(d){a.batchId=d,a.clientContext=a.clientContext||c.options.clientContext,c.outputs.batchesInFlight[d]||b.push(c.submitBatchById(a))}),b},a.NON_RETRYABLE_EXCEPTIONS=["BadRequestException","SerializationException","ValidationException"],a.prototype.submitBatchById=function(a){if("object"!=typeof a||!a.batchId)return void this.logger.error("Invalid Options passed to submitBatchById");a.submitCallback=a.submitCallback||this.options.submitCallback,this.logger.log("[Function:(AMA.Client).submitBatchById]"+(a?"\noptions:"+JSON.stringify(a):""));var b={events:this.outputs.batches[a.batchId],clientContext:JSON.stringify(a.clientContext||this.options.clientContext)};return this.outputs.batchesInFlight[a.batchId]=d.Util.timestamp(),this.outputs.MobileAnalytics.putEvents(b,this.handlePutEventsResponse(a.batchId,a.submitCallback)),a.batchId},a.prototype.handlePutEventsResponse=function(b,c){var d=this;return function(e,f){var g=!0,h=d.outputs.isThrottled;e?(d.logger.error(e,f),(void 0===e.statusCode||400===e.statusCode)&&(a.NON_RETRYABLE_EXCEPTIONS.indexOf(e.code)<0&&(g=!1),d.outputs.isThrottled="ThrottlingException"===e.code,d.outputs.isThrottled&&d.logger.warn("Application is currently throttled"))):(d.logger.info("Events Submitted Successfully"),d.outputs.isThrottled=!1),g&&d.clearBatchById(b),delete d.outputs.batchesInFlight[b],c(e,f,b),h&&!d.outputs.isThrottled&&(d.logger.warn("Was throttled flushing remaining batches",c),d.submitAllBatches({submitCallback:c}))}},a.prototype.clearBatchById=function(a){this.logger.log("[Function:(AMA.Client).clearBatchById]"+(a?"\nbatchId:"+a:"")),-1!==this.outputs.batchIndex.indexOf(a)&&(delete this.outputs.batches[a],this.outputs.batchIndex.splice(this.outputs.batchIndex.indexOf(a),1),this.storage.set(this.StorageKeys.BATCH_INDEX,this.outputs.batchIndex),this.storage.set(this.StorageKeys.BATCHES,this.outputs.batches))},a}(),b.exports=d.Client}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./MobileAnalyticsUtilities.js":4,"./StorageClients/LocalStorage.js":5,"./StorageClients/StorageKeys.js":6}],2:[function(a,b){(function(c){var d=c.AMA;d.Storage=a("./StorageClients/LocalStorage.js"),d.StorageKeys=a("./StorageClients/StorageKeys.js"),d.Util=a("./MobileAnalyticsUtilities.js"),d.Session=function(){"use strict";var a=function(a){this.options=a||{},this.options.logger=this.options.logger||{},this.logger={log:this.options.logger.log||d.Util.NOP,info:this.options.logger.info||d.Util.NOP,warn:this.options.logger.warn||d.Util.NOP,error:this.options.logger.error||d.Util.NOP},this.logger.log=this.logger.log.bind(this.options.logger),this.logger.info=this.logger.info.bind(this.options.logger),this.logger.warn=this.logger.warn.bind(this.options.logger),this.logger.error=this.logger.error.bind(this.options.logger),this.logger.log("[Function:(AWS.MobileAnalyticsClient)Session Constructor]"+(a?"\noptions:"+JSON.stringify(a):"")),this.options.expirationCallback=this.options.expirationCallback||d.Util.NOP,this.id=this.options.sessionId||d.Util.GUID(),this.sessionLength=this.options.sessionLength||6e5,this.StorageKeys={SESSION_ID:d.StorageKeys.SESSION_ID+this.id,SESSION_EXPIRATION:d.StorageKeys.SESSION_EXPIRATION+this.id,SESSION_START_TIMESTAMP:d.StorageKeys.SESSION_START_TIMESTAMP+this.id},this.startTimestamp=this.options.startTime||this.options.storage.get(this.StorageKeys.SESSION_START_TIMESTAMP)||(new Date).toISOString(),this.expirationDate=parseInt(this.options.storage.get(this.StorageKeys.SESSION_EXPIRATION),10),isNaN(this.expirationDate)&&(this.expirationDate=(new Date).getTime()+this.sessionLength),this.options.storage.set(this.StorageKeys.SESSION_ID,this.id),this.options.storage.set(this.StorageKeys.SESSION_EXPIRATION,this.expirationDate),this.options.storage.set(this.StorageKeys.SESSION_START_TIMESTAMP,this.startTimestamp),this.sessionTimeoutReference=setTimeout(this.expireSession.bind(this),this.sessionLength)};return a.prototype.expireSession=function(a){this.logger.log("[Function:(Session).expireSession]"),a=a||this.options.expirationCallback;var b=a(this);"boolean"==typeof b&&b&&(b=this.options.sessionLength),"number"==typeof b?this.extendSession(b):this.clearSession()},a.prototype.clearSession=function(){this.logger.log("[Function:(Session).clearSession]"),clearTimeout(this.sessionTimeoutReference),this.options.storage.delete(this.StorageKeys.SESSION_ID),this.options.storage.delete(this.StorageKeys.SESSION_EXPIRATION),this.options.storage.delete(this.StorageKeys.SESSION_START_TIMESTAMP)},a.prototype.extendSession=function(a){this.logger.log("[Function:(Session).extendSession]"+(a?"\nsessionExtensionLength:"+a:"")),a=a||this.sessionLength,this.setSessionTimeout(this.expirationDate+parseInt(a,10))},a.prototype.stopSession=function(a){this.logger.log("[Function:(Session).stopSession]"+(a?"\nstopDate:"+a:"")),this.stopTimestamp=a||(new Date).toISOString()},a.prototype.resetSessionTimeout=function(a){this.logger.log("[Function:(Session).resetSessionTimeout]"+(a?"\nmilliseconds:"+a:"")),a=a||this.sessionLength,this.setSessionTimeout((new Date).getTime()+a)},a.prototype.setSessionTimeout=function(a){this.logger.log("[Function:(Session).setSessionTimeout]"+(a?"\ntimeout:"+a:"")),clearTimeout(this.sessionTimeoutReference),this.expirationDate=a,this.options.storage.set(this.StorageKeys.SESSION_EXPIRATION,this.expirationDate),this.sessionTimeoutReference=setTimeout(this.expireSession.bind(this),this.expirationDate-(new Date).getTime())},a}(),b.exports=d.Session}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./MobileAnalyticsUtilities.js":4,"./StorageClients/LocalStorage.js":5,"./StorageClients/StorageKeys.js":6}],3:[function(a,b){(function(c){var d=c.AMA;d.Storage=a("./StorageClients/LocalStorage.js"),d.StorageKeys=a("./StorageClients/StorageKeys.js"),d.Session=a("./MobileAnalyticsSession.js"),d.Client=a("./MobileAnalyticsClient.js"),d.Manager=function(){"use strict";var a=function(a){function b(a){a.client.storage.each(function(b){0===b.indexOf(d.StorageKeys.SESSION_ID)&&(a.outputs.session=new d.Session({storage:a.client.storage,sessionId:a.client.storage.get(b),sessionLength:a.options.sessionLength,expirationCallback:function(b){var c=a.options.expirationCallback(b);return c===!0||"number"==typeof c?c:void a.stopSession()}}),(new Date).getTime()>a.outputs.session.expirationDate&&(a.outputs.session.expireSession(),delete a.outputs.session))})}a instanceof d.Client?this.client=a:(a._autoSubmitEvents=a.autoSubmitEvents,a.autoSubmitEvents=!1,this.client=new d.Client(a),a.autoSubmitEvents=a._autoSubmitEvents!==!1,delete a._autoSubmitEvents),this.options=this.client.options,this.outputs=this.client.outputs,this.options.expirationCallback=this.options.expirationCallback||d.Util.NOP,b(this),this.outputs.session||this.startSession(),this.options.autoSubmitEvents&&this.client.submitEvents()};return a.prototype.submitEvents=function(a){return this.client.submitEvents(a)},a.prototype.startSession=function(){return this.client.logger.log("[Function:(AMA.Manager).startSession]"),this.outputs.session&&this.outputs.session.clearSession(),this.outputs.session=new d.Session({storage:this.client.storage,logger:this.client.options.logger,sessionLength:this.options.sessionLength,expirationCallback:function(a){var b=this.options.expirationCallback(a);return b===!0||"number"==typeof b?b:void this.stopSession()}.bind(this)}),this.recordEvent("_session.start")},a.prototype.extendSession=function(a){return this.outputs.session.extendSession(a||this.options.sessionLength)},a.prototype.stopSession=function(){return this.client.logger.log("[Function:(AMA.Manager).stopSession]"),this.outputs.session.stopSession(),this.outputs.session.expireSession(d.Util.NOP),this.recordEvent("_session.stop")},a.prototype.renewSession=function(){return this.stopSession(),this.startSession(),this.outputs.session},a.prototype.createEvent=function(a,b,c){return this.client.createEvent(a,this.outputs.session,b,c)},a.prototype.recordEvent=function(a,b,c){return this.client.recordEvent(a,this.outputs.session,b,c)},a.prototype.recordMonetizationEvent=function(a,b,c){return this.client.recordMonetizationEvent(this.outputs.session,a,b,c)},a}(),b.exports=d.Manager}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./MobileAnalyticsClient.js":1,"./MobileAnalyticsSession.js":2,"./StorageClients/LocalStorage.js":5,"./StorageClients/StorageKeys.js":6}],4:[function(a,b){(function(a){var c=a.AMA;c.Util=function(){"use strict";function a(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}function b(a){"string"!=typeof a&&(a=JSON.stringify(a));var b,c,d=a.length;for(b=a.length-1;b>=0;b-=1)c=a.charCodeAt(b),c>127&&2047>=c?d+=1:c>2047&&65535>=c&&(d+=2),c>=56320&&57343>=c&&(b-=1);return d}function c(){return a()+a()+"-"+a()+"-"+a()+"-"+a()+"-"+a()+a()+a()}function d(a,b){return Object.keys(b).forEach(function(c){b.hasOwnProperty(c)&&(a[c]=a[c]||b[c])}),a}function e(a,b){return d(JSON.parse(JSON.stringify(a)),b||{})}function f(){return void 0}function g(){return(new Date).getTime()}return{copy:e,GUID:c,getRequestBodySize:b,mergeObjects:d,NOP:f,timestamp:g}}(),b.exports=c.Util}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],5:[function(a,b){(function(c){var d=c.AMA;d.Util=a("../MobileAnalyticsUtilities.js"),d.Storage=function(){"use strict";var a=function(a){this.storageKey="AWSMobileAnalyticsStorage-"+a,c[this.storageKey]=c[this.storageKey]||{},this.cache=c[this.storageKey],this.cache.id=this.cache.id||d.Util.GUID(),this.logger={log:d.Util.NOP,info:d.Util.NOP,warn:d.Util.NOP,error:d.Util.NOP},this.reload()};if("object"==typeof localStorage&&"object"===Storage)try{localStorage.setItem("TestLocalStorage",1),localStorage.removeItem("TestLocalStorage")}catch(b){Storage.prototype._setItem=Storage.prototype.setItem,Storage.prototype.setItem=d.Util.NOP,console.warn('Your web browser does not support storing settings locally. In Safari, the most common cause of this is using "Private Browsing Mode". Some settings may not save or some features may not work properly for you.')}return a.prototype.type="LOCAL_STORAGE",a.prototype.get=function(a){return this.cache[a]},a.prototype.set=function(a,b){return this.cache[a]=b,this.saveToLocalStorage()},a.prototype.delete=function(a){delete this.cache[a],this.saveToLocalStorage()},a.prototype.each=function(a){var b;for(b in this.cache)this.cache.hasOwnProperty(b)&&a(b,this.cache[b])},a.prototype.saveToLocalStorage=function(){if(this.supportsLocalStorage())try{this.logger.log("[Function:(AWS.MobileAnalyticsClient.Storage).saveToLocalStorage]"),window.localStorage.setItem(this.storageKey,JSON.stringify(this.cache)),this.logger.log("LocalStorage Cache: "+JSON.stringify(this.cache))}catch(a){this.logger.log("Error saving to LocalStorage: "+JSON.stringify(a))}else this.logger.log("LocalStorage is not available")},a.prototype.reload=function(){if(this.supportsLocalStorage()){var a;try{if(this.logger.log("[Function:(AWS.MobileAnalyticsClient.Storage).loadLocalStorage]"),a=window.localStorage.getItem(this.storageKey),this.logger.log("LocalStorage Cache: "+a),a)try{this.cache=JSON.parse(a)}catch(b){this.clearLocalStorage()}}catch(c){this.logger.log("Error loading LocalStorage: "+JSON.stringify(c)),this.clearLocalStorage()}}else this.logger.log("LocalStorage is not available")},a.prototype.setLogger=function(a){this.logger=a},a.prototype.supportsLocalStorage=function(){try{return window&&window.localStorage}catch(a){return!1}},a.prototype.clearLocalStorage=function(){if(this.cache={},this.supportsLocalStorage())try{this.logger.log("[Function:(AWS.MobileAnalyticsClient.Storage).clearLocalStorage]"),window.localStorage.removeItem(this.storageKey),c[this.storageKey]={}}catch(a){this.logger.log("Error clearing LocalStorage: "+JSON.stringify(a))}else this.logger.log("LocalStorage is not available")},a}(),b.exports=d.Storage}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../MobileAnalyticsUtilities.js":4}],6:[function(a,b){(function(a){var c=a.AMA;c.StorageKeys={CLIENT_ID:"AWSMobileAnalyticsClientId",GLOBAL_ATTRIBUTES:"AWSMobileAnalyticsGlobalAttributes",GLOBAL_METRICS:"AWSMobileAnalyticsGlobalMetrics",SESSION_ID:"MobileAnalyticsSessionId",SESSION_EXPIRATION:"MobileAnalyticsSessionExpiration",SESSION_START_TIMESTAMP:"MobileAnalyticsSessionStartTimeStamp"},b.exports=c.StorageKeys}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],7:[function(a,b){(function(c){c.AMA=c.AMA||{},a("./MobileAnalyticsClient.js"),a("./MobileAnalyticsUtilities.js"),a("./StorageClients/StorageKeys.js"),a("./StorageClients/LocalStorage.js"),a("./MobileAnalyticsSession.js"),a("./MobileAnalyticsSessionManager.js"),b.exports=c.AMA}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./MobileAnalyticsClient.js":1,"./MobileAnalyticsSession.js":2,"./MobileAnalyticsSessionManager.js":3,"./MobileAnalyticsUtilities.js":4,"./StorageClients/LocalStorage.js":5,"./StorageClients/StorageKeys.js":6}]},{},[7]); -------------------------------------------------------------------------------- /doc/MobileAnalyticsSession.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: MobileAnalyticsSession.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: MobileAnalyticsSession.js

21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
var AMA = global.AMA;
 29 | AMA.Storage = require('./StorageClients/LocalStorage.js');
 30 | AMA.StorageKeys = require('./StorageClients/StorageKeys.js');
 31 | AMA.Util = require('./MobileAnalyticsUtilities.js');
 32 | /**
 33 |  * @name AMA.Session
 34 |  * @namespace AMA.Session
 35 |  * @constructor
 36 |  * @param {Object=}     [options=] - A configuration map for the Session
 37 |  * @param {string=}     [options.sessionId=Utilities.GUID()]- A sessionId for session.
 38 |  * @param {string=}     [options.appId=new Date().toISOString()] - The start Timestamp (default now).
 39 |  * @param {number=}     [options.sessionLength=600000] - Length of session in Milliseconds (default 10 minutes).
 40 |  * @param {AMA.Session.ExpirationCallback=}   [options.expirationCallback] - Callback Function for when a session expires
 41 |  * @param {AMA.Client.Logger=} [options.logger=] - Object containing javascript style logger functions (passing console
 42 |  *                                                 will output to browser dev consoles)
 43 |  */
 44 | /**
 45 |  * @callback AMA.Session.ExpirationCallback
 46 |  * @param {AMA.Session} session
 47 |  * @returns {boolean|int} - Returns either true to extend the session by the sessionLength or an int with the number of
 48 |  *                          seconds to extend the session.  All other values will clear the session from storage.
 49 |  */
 50 | AMA.Session = (function () {
 51 |     'use strict';
 52 |     /**
 53 |      * @lends AMA.Session
 54 |      */
 55 |     var Session = function (options) {
 56 |         this.options = options || {};
 57 |         this.options.logger = this.options.logger || {};
 58 |         this.logger = {
 59 |             log: this.options.logger.log || AMA.Util.NOP,
 60 |             info: this.options.logger.info || AMA.Util.NOP,
 61 |             warn: this.options.logger.warn || AMA.Util.NOP,
 62 |             error: this.options.logger.error || AMA.Util.NOP
 63 |         };
 64 |         this.logger.log = this.logger.log.bind(this.options.logger);
 65 |         this.logger.info = this.logger.info.bind(this.options.logger);
 66 |         this.logger.warn = this.logger.warn.bind(this.options.logger);
 67 |         this.logger.error = this.logger.error.bind(this.options.logger);
 68 |         this.logger.log('[Function:(AWS.MobileAnalyticsClient)Session Constructor]' +
 69 |             (options ? '\noptions:' + JSON.stringify(options) : ''));
 70 |         this.options.expirationCallback = this.options.expirationCallback || AMA.Util.NOP;
 71 |         this.id = this.options.sessionId || AMA.Util.GUID();
 72 |         this.sessionLength = this.options.sessionLength || 600000; //Default session length is 10 minutes
 73 |         //Suffix the AMA.Storage Keys with Session Id to ensure proper scope
 74 |         this.StorageKeys = {
 75 |             'SESSION_ID': AMA.StorageKeys.SESSION_ID + this.id,
 76 |             'SESSION_EXPIRATION': AMA.StorageKeys.SESSION_EXPIRATION + this.id,
 77 |             'SESSION_START_TIMESTAMP': AMA.StorageKeys.SESSION_START_TIMESTAMP + this.id
 78 |         };
 79 |         this.startTimestamp = this.options.startTime ||
 80 |             this.options.storage.get(this.StorageKeys.SESSION_START_TIMESTAMP) ||
 81 |             new Date().toISOString();
 82 |         this.expirationDate = parseInt(this.options.storage.get(this.StorageKeys.SESSION_EXPIRATION), 10);
 83 |         if (isNaN(this.expirationDate)) {
 84 |             this.expirationDate = (new Date().getTime() + this.sessionLength);
 85 |         }
 86 |         this.options.storage.set(this.StorageKeys.SESSION_ID, this.id);
 87 |         this.options.storage.set(this.StorageKeys.SESSION_EXPIRATION, this.expirationDate);
 88 |         this.options.storage.set(this.StorageKeys.SESSION_START_TIMESTAMP, this.startTimestamp);
 89 |         this.sessionTimeoutReference = setTimeout(this.expireSession.bind(this), this.sessionLength);
 90 |     };
 91 | 
 92 |     /**
 93 |      * Expire session and clear session
 94 |      * @param {expirationCallback=} Callback function to call when sessions expire
 95 |      */
 96 |     Session.prototype.expireSession = function (expirationCallback) {
 97 |         this.logger.log('[Function:(Session).expireSession]');
 98 |         expirationCallback = expirationCallback || this.options.expirationCallback;
 99 |         var shouldExtend = expirationCallback(this);
100 |         if (typeof shouldExtend === 'boolean' && shouldExtend) {
101 |             shouldExtend = this.options.sessionLength;
102 |         }
103 |         if (typeof shouldExtend === 'number') {
104 |             this.extendSession(shouldExtend);
105 |         } else {
106 |             this.clearSession();
107 |         }
108 |     };
109 | 
110 |     /**
111 |      * Clear session from storage system
112 |      */
113 |     Session.prototype.clearSession = function () {
114 |         this.logger.log('[Function:(Session).clearSession]');
115 |         clearTimeout(this.sessionTimeoutReference);
116 |         this.options.storage.delete(this.StorageKeys.SESSION_ID);
117 |         this.options.storage.delete(this.StorageKeys.SESSION_EXPIRATION);
118 |         this.options.storage.delete(this.StorageKeys.SESSION_START_TIMESTAMP);
119 |     };
120 | 
121 | 
122 | 
123 |     /**
124 |      * Extend session by adding to the expiration timestamp
125 |      * @param {int} [sessionExtensionLength=sessionLength] - The number of milliseconds to add to the expiration date
126 |      *                                                       (session length by default).
127 |      */
128 |     Session.prototype.extendSession = function (sessionExtensionLength) {
129 |         this.logger.log('[Function:(Session).extendSession]' +
130 |                         (sessionExtensionLength ? '\nsessionExtensionLength:' + sessionExtensionLength : ''));
131 |         sessionExtensionLength = sessionExtensionLength || this.sessionLength;
132 |         this.setSessionTimeout(this.expirationDate + parseInt(sessionExtensionLength, 10));
133 |     };
134 | 
135 |     /**
136 |      * @param {string} [stopDate=now] - The ISO Date String to set the stopTimestamp to (now for default).
137 |      */
138 |     Session.prototype.stopSession = function (stopDate) {
139 |         this.logger.log('[Function:(Session).stopSession]' +  (stopDate ? '\nstopDate:' + stopDate : ''));
140 |         this.stopTimestamp = stopDate || new Date().toISOString();
141 |     };
142 | 
143 |     /**
144 |      * Reset session timeout to expire in a given number of seconds
145 |      * @param {int} [milliseconds=sessionLength] - The number of milliseconds until the session should expire (from now). 
146 |      */
147 |     Session.prototype.resetSessionTimeout = function (milliseconds) {
148 |         this.logger.log('[Function:(Session).resetSessionTimeout]' +
149 |                         (milliseconds ? '\nmilliseconds:' + milliseconds : ''));
150 |         milliseconds = milliseconds || this.sessionLength;
151 |         this.setSessionTimeout(new Date().getTime() + milliseconds);
152 |     };
153 | 
154 |     /**
155 |      * Setter for the session timeout
156 |      * @param {int} timeout - epoch timestamp
157 |      */
158 |     Session.prototype.setSessionTimeout = function (timeout) {
159 |         this.logger.log('[Function:(Session).setSessionTimeout]' +  (timeout ? '\ntimeout:' + timeout : ''));
160 |         clearTimeout(this.sessionTimeoutReference);
161 |         this.expirationDate = timeout;
162 |         this.options.storage.set(this.StorageKeys.SESSION_EXPIRATION, this.expirationDate);
163 |         this.sessionTimeoutReference = setTimeout(this.expireSession.bind(this),
164 |             this.expirationDate - (new Date()).getTime());
165 |     };
166 |     return Session;
167 | }());
168 | 
169 | module.exports = AMA.Session;
170 | 
171 |
172 |
173 | 174 | 175 | 176 | 177 |
178 | 179 | 182 | 183 |
184 | 185 |
186 | Documentation generated by JSDoc 3.2.2 on Tue Jul 19 2016 13:22:17 GMT-0700 (PDT) 187 |
188 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /doc/MobileAnalyticsSessionManager.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: MobileAnalyticsSessionManager.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: MobileAnalyticsSessionManager.js

21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
var AMA = global.AMA;
 29 | AMA.Storage = require('./StorageClients/LocalStorage.js');
 30 | AMA.StorageKeys = require('./StorageClients/StorageKeys.js');
 31 | AMA.Session = require('./MobileAnalyticsSession.js');
 32 | AMA.Client = require('./MobileAnalyticsClient.js');
 33 | 
 34 | /**
 35 |  * @typedef AMA.Manager.Options
 36 |  * @augments AMA.Client.Options
 37 |  * @property {AMA.Session.ExpirationCallback} [expirationCallback=] - Callback function to call when sessions expire
 38 |  */
 39 | 
 40 | /**
 41 |  * @name AMA.Manager
 42 |  * @namespace AMA.Manager
 43 |  * @constructor
 44 |  * @param {AMA.Client.Options|AMA.Client} options - A configuration map for the AMA.Client or an instantiated AMA.Client
 45 |  * @see AMA.Client
 46 |  */
 47 | AMA.Manager = (function () {
 48 |     'use strict';
 49 |     /**
 50 |      * @lends AMA.Manager
 51 |      */
 52 |     var Manager = function (options) {
 53 |         if (options instanceof AMA.Client) {
 54 |             this.client = options;
 55 |         } else {
 56 |             options._autoSubmitEvents = options.autoSubmitEvents;
 57 |             options.autoSubmitEvents = false;
 58 |             this.client = new AMA.Client(options);
 59 |             options.autoSubmitEvents = options._autoSubmitEvents !== false;
 60 |             delete options._autoSubmitEvents;
 61 |         }
 62 |         this.options = this.client.options;
 63 |         this.outputs = this.client.outputs;
 64 | 
 65 |         this.options.expirationCallback = this.options.expirationCallback || AMA.Util.NOP;
 66 |         function checkForStoredSessions(context) {
 67 |             context.client.storage.each(function (key) {
 68 |                 if (key.indexOf(AMA.StorageKeys.SESSION_ID) === 0) {
 69 |                     context.outputs.session = new AMA.Session({
 70 |                         storage           : context.client.storage,
 71 |                         sessionId         : context.client.storage.get(key),
 72 |                         sessionLength     : context.options.sessionLength,
 73 |                         expirationCallback: function (session) {
 74 |                             var shouldExtend = context.options.expirationCallback(session);
 75 |                             if (shouldExtend === true || typeof shouldExtend === 'number') {
 76 |                                 return shouldExtend;
 77 |                             }
 78 |                             context.stopSession();
 79 |                         }
 80 |                     });
 81 |                     if (new Date().getTime() > context.outputs.session.expirationDate) {
 82 |                         context.outputs.session.expireSession();
 83 |                         delete context.outputs.session;
 84 |                     }
 85 |                 }
 86 |             });
 87 |         }
 88 | 
 89 |         checkForStoredSessions(this);
 90 |         if (!this.outputs.session) {
 91 |             this.startSession();
 92 |         }
 93 |         if (this.options.autoSubmitEvents) {
 94 |             this.client.submitEvents();
 95 |         }
 96 |     };
 97 | 
 98 |     /**
 99 |      * submitEvents
100 |      * @param {Object} [options=] - options for submitting events
101 |      * @param {Object} [options.clientContext=this.options.clientContext] - clientContext to submit with defaults to
102 |      *                                                                      options.clientContext
103 |      * @returns {Array} Array of batch indices that were submitted
104 |      */
105 |     Manager.prototype.submitEvents = function (options) {
106 |         return this.client.submitEvents(options);
107 |     };
108 | 
109 |     /**
110 |      * Function to start a session
111 |      * @returns {AMA.Client.Event} The start session event recorded
112 |      */
113 |     Manager.prototype.startSession = function () {
114 |         this.client.logger.log('[Function:(AMA.Manager).startSession]');
115 |         if (this.outputs.session) {
116 |             //Clear Session
117 |             this.outputs.session.clearSession();
118 |         }
119 |         this.outputs.session = new AMA.Session({
120 |             storage: this.client.storage,
121 |             logger: this.client.options.logger,
122 |             sessionLength: this.options.sessionLength,
123 |             expirationCallback: function (session) {
124 |                 var shouldExtend = this.options.expirationCallback(session);
125 |                 if (shouldExtend === true || typeof shouldExtend === 'number') {
126 |                     return shouldExtend;
127 |                 }
128 |                 this.stopSession();
129 |             }.bind(this)
130 |         });
131 |         return this.recordEvent('_session.start');
132 |     };
133 | 
134 |     /**
135 |      * Function to extend the current session.
136 |      * @param {int} [milliseconds=options.sessionLength] - Milliseconds to extend the session by, will default
137 |      *                                                     to another session length
138 |      * @returns {int} The Session expiration (in Milliseconds)
139 |      */
140 |     Manager.prototype.extendSession = function (milliseconds) {
141 |         return this.outputs.session.extendSession(milliseconds || this.options.sessionLength);
142 |     };
143 | 
144 |     /**
145 |      * Function to stop the current session
146 |      * @returns {AMA.Client.Event} The stop session event recorded
147 |      */
148 |     Manager.prototype.stopSession = function () {
149 |         this.client.logger.log('[Function:(AMA.Manager).stopSession]');
150 |         this.outputs.session.stopSession();
151 |         this.outputs.session.expireSession(AMA.Util.NOP);
152 |         return this.recordEvent('_session.stop');
153 |     };
154 | 
155 |     /**
156 |      * Function to stop the current session and start a new one
157 |      * @returns {AMA.Session} The new Session Object for the SessionManager
158 |      */
159 |     Manager.prototype.renewSession = function () {
160 |         this.stopSession();
161 |         this.startSession();
162 |         return this.outputs.session;
163 |     };
164 | 
165 |     /**
166 |      * Function that constructs a Mobile Analytics Event
167 |      * @param {string} eventType - Custom Event Type to be displayed in Console
168 |      * @param {AMA.Client.Attributes} [attributes=] - Map of String attributes
169 |      * @param {AMA.Client.Metrics} [metrics=] - Map of numeric values
170 |      * @returns {AMA.Client.Event}
171 |      */
172 |     Manager.prototype.createEvent = function (eventType, attributes, metrics) {
173 |         return this.client.createEvent(eventType, this.outputs.session, attributes, metrics);
174 |     };
175 | 
176 |     /**
177 |      * Function to record a custom event
178 |      * @param eventType - Custom event type name
179 |      * @param {AMA.Client.Attributes} [attributes=] - Custom attributes
180 |      * @param {AMA.Client.Metrics} [metrics=] - Custom metrics
181 |      * @returns {AMA.Client.Event} The event that was recorded
182 |      */
183 |     Manager.prototype.recordEvent = function (eventType, attributes, metrics) {
184 |         return this.client.recordEvent(eventType, this.outputs.session, attributes, metrics);
185 |     };
186 | 
187 |     /**
188 |      * Function to record a monetization event
189 |      * @param {Object} monetizationDetails - Details about Monetization Event
190 |      * @param {string} monetizationDetails.currency - ISO Currency of event
191 |      * @param {string} monetizationDetails.productId - Product Id of monetization event
192 |      * @param {number} monetizationDetails.quantity - Quantity of product in transaction
193 |      * @param {string|number} monetizationDetails.price - Price of product either ISO formatted string, or number
194 |      *                                                    with associated ISO Currency
195 |      * @param {AMA.Client.Attributes} [attributes=] - Custom attributes
196 |      * @param {AMA.Client.Metrics} [metrics=] - Custom metrics
197 |      * @returns {AMA.Client.Event} The event that was recorded
198 |      */
199 |     Manager.prototype.recordMonetizationEvent = function (monetizationDetails, attributes, metrics) {
200 |         return this.client.recordMonetizationEvent(this.outputs.session, monetizationDetails, attributes, metrics);
201 |     };
202 |     return Manager;
203 | }());
204 | 
205 | module.exports = AMA.Manager;
206 | 
207 |
208 |
209 | 210 | 211 | 212 | 213 |
214 | 215 | 218 | 219 |
220 | 221 |
222 | Documentation generated by JSDoc 3.2.2 on Tue Jul 19 2016 13:22:17 GMT-0700 (PDT) 223 |
224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /doc/ama.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: ama.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: ama.js

21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
/**
29 |  * @module AMA
30 |  * @description The global namespace for Amazon Mobile Analytics
31 |  * @see AMA.Manager
32 |  * @see AMA.Client
33 |  * @see AMA.Session
34 |  */
35 | global.AMA = global.AMA || {};
36 | require('./MobileAnalyticsClient.js');
37 | require('./MobileAnalyticsUtilities.js');
38 | require('./StorageClients/StorageKeys.js');
39 | require('./StorageClients/LocalStorage.js');
40 | require('./MobileAnalyticsSession.js');
41 | require('./MobileAnalyticsSessionManager.js');
42 | module.exports = global.AMA;
43 | 
44 |
45 |
46 | 47 | 48 | 49 | 50 |
51 | 52 | 55 | 56 |
57 | 58 |
59 | Documentation generated by JSDoc 3.2.2 on Tue Jul 19 2016 13:22:17 GMT-0700 (PDT) 60 |
61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Index 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Index

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |

29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |

Amazon Mobile Analytics SDK for JavaScript

45 |

Developer Preview: We welcome developer feedback on this project. You can reach us by creating an issue on the 46 | GitHub repository or posting to the Amazon Mobile Analytics forums: 47 | https://github.com/aws/aws-sdk-mobile-analytics-js 48 | https://forums.aws.amazon.com/forum.jspa?forumID=174

49 |

Introduction

50 |

The Mobile Analytics SDK for JavaScript allows JavaScript enabled applications to create and submit events for analysis in the AWS Console and through Auto Export to S3 and Redshift. The library uses the browser's local storage API to create a local cache for the data, allowing your web application to batch and record events even when the app is offline.

51 |

Setup

52 |
    53 |
  1. Download and include the AWS JavaScript SDK (minimum version 2.1.18):

    54 | 57 |
  2. 58 |
  3. Download and include the Amazon Mobile Analytics SDK for JavaScript:

    59 | 62 |
  4. 63 |
64 |
 65 |     <script src="/js/aws-sdk.min.js"></script>
 66 |     <script src="/js/aws-sdk-mobile-analytics.min.js"></script>
 67 | 
68 | 69 |

Usage

70 |

Step 1. Log in to the Amazon Mobile Analytics management console and create a new app. Be sure to note your App Id and Cognito Identity Pool Id. 71 | * https://console.aws.amazon.com/mobileanalytics/home/?region=us-east-1

72 |

Step 2. Initialize the credentials provider using a Cognito Identity Pool ID. This is necessary for the AWS SDK to manage authentication to the Amazon Mobile Analytics REST API.

73 |
 74 |     AWS.config.region = 'us-east-1';
 75 |     AWS.config.credentials = new AWS.CognitoIdentityCredentials({
 76 |         IdentityPoolId: COGNITO_IDENTITY_POOL_ID   //Required e.g. 'us-east-1:12345678-c1ab-4122-913b-22d16971337b'
 77 |     });
 78 | 
79 | 80 |

Step 3. Instantiate the Mobile Analytics Manager, including your App ID generated in Step 1, above. Session events will be automatically recorded and the client will batch and automatically submit events to Amazon Mobile Analytics every 10 seconds.

81 |
 82 |     var options = {
 83 |         appId : MOBILE_ANALYTICS_APP_ID   //Required e.g. 'c5d69c75a92646b8953126437d92c007'
 84 |     };
 85 |     mobileAnalyticsClient = new AMA.Manager(options);
 86 | 
87 | 88 |

To manually force an event submission you can call:

89 |
 90 |     mobileAnalyticsClient.submitEvents();
 91 | 
92 | 93 |

Additional Options

94 |

Custom Events

95 |

You can optionally add custom events to capture additional information you find valuable.

96 |
 97 |     mobileAnalyticsClient.recordEvent('CUSTOM EVENT NAME', {
 98 |             'ATTRIBUTE_1_NAME': 'ATTRIBUTE_1_VALUE',
 99 |             'ATTRIBUTE_2_NAME': 'ATTRIBUTE_2_VALUE'
100 |             /* ... */
101 |         }, {
102 |             'METRIC_1_NAME': 1,
103 |             'METRIC_2_NAME': 99.3
104 |             /* ... */
105 |         });
106 | 
107 | 108 | 109 |

Session Settings

110 |

By default a session lasts 10 minutes. You can override this default setting when initializing the Mobile Analytics Manager by including "sessionLength" in the "options" object.

111 |
112 |     var options = {
113 |         appId : MOBILE_ANALYTICS_APP_ID, 
114 |         sessionLength: 300000            //Session Length in milliseconds.  This will evaluate to 5min.
115 |     };
116 |     mobileAnalyticsClient = new AMA.Manager(options);
117 | 
118 | 119 |

A session's timeout can also be updated to allow for continuation of a session.

120 |
121 |     //This will set the current session to expire in 5 seconds from now.
122 |     mobileAnalyticsClient.resetSessionTimeout(5000); 
123 | 
124 |     //This will reset the current session's expiration time using the time specified during initialization. 
125 |     //If the default setting was used (10 minutes) then the session will expire 10 minutes from now. 
126 |     mobileAnalyticsClient.resetSessionTimeout();
127 | 
128 | 129 |

Record Monetization Event

130 |

You can record monetization events to enable reports such as Average Revenue Per User (ARPU) and more.

131 |
132 |     mobileAnalyticsClient.recordMonetizationEvent(
133 |         {
134 |             productId : PRODUCT_ID,   //Required e.g. 'My Example Product'
135 |             price : PRICE,            //Required e.g. 1.99
136 |             quantity : QUANTITY,      //Required e.g. 1
137 |             currency : CURRENCY_CODE  //Optional ISO currency code e.g. 'USD'
138 |         }, 
139 |         {/* Custom Attributes */}, 
140 |         {/* Custom Metrics */}
141 |     );
142 | 
143 | 144 |

Add App Details to Events

145 |

Additional app and environment details can be added to the "options" object when initializing the SDK. These details will be captured and applied to all events and can be useful if using Auto Export for custom analysis of your data.

146 |
147 |     var options = {
148 |         appId : MOBILE_ANALYTICS_APP_ID,       //Required e.g. 'c5d69c75a92646b8953126437d92c007'
149 |         appTitle : APP_TITLE,                  //Optional e.g. 'Example App'
150 |         appVersionName : APP_VERSION_NAME,     //Optional e.g. '1.4.1'
151 |         appVersionCode : APP_VERSION_CODE,     //Optional e.g. '42'
152 |         appPackageName : APP_PACKAGE_NAME,     //Optional e.g. 'com.amazon.example'
153 |         make : DEVICE_MAKE,                    //Optional e.g. 'Amazon'
154 |         model : DEVICE_MODEL,                  //Optional e.g. 'KFTT'
155 |         platform : DEVICE_PLATFORM,            //Required valid values: 'Android', 'iPhoneOS', 'WindowsPhone', 'Blackberry', 'Windows', 'MacOS', 'Linux'
156 |         platformVersion : DEVICE_PLATFORM_VER  //Optional e.g. '4.4'
157 |     };
158 |     mobileAnalyticsClient = new AMA.Manager(options);
159 | 
160 | 161 |

Please note, if device details are not specified Amazon Mobile Analytics will make best efforts to determine these values based on the User-Agent header value. It is always better to specify these values during initialization if they are available.

162 |

Further Documentation

163 |

Further documentation and advanced configurations can be found here:

164 |

https://aws.github.io/aws-sdk-mobile-analytics-js/doc/AMA.Manager.html

165 |

Network Configuration

166 |

The Amazon Mobile Analytics JavaScript SDK will make requests to the following endpoints 167 | For Event Submission: "https://mobileanalytics.us-east-1.amazonaws.com" 168 | For Cognito Authentication: "https://cognito-identity.us-east-1.amazonaws.com" 169 | * This endpoint may change based on which region your Identity Pool was created in.

170 |

For most frameworks you can whitelist both domains by whitelisting all AWS endpoints with "*.amazonaws.com".

171 |

Change Log

172 |

v0.9.2: 173 | Bug Fixes: 174 | Fixed data loss issue on migration from previous versions

175 |

v0.9.1: 176 | Updated Dependency: aws-sdk-js v2.2.37 177 | Increase base delay between retries from 30ms to 3s 178 | Allow passing of configurations to the low level client via clientOptions attribute 179 | Local events from different apps are stored in different locations 180 | Improved retry strategies 181 | CorrectClockSkew is enabled by default for Sigv4 Signatures per Issue#7 182 | Bug Fixes: 183 | Fixed timer from being killed in cases where multiple submissions happened in under a second 184 | Fixed duplicate batch re-submission to the Mobile Analytics service 185 | Fixed delayed auto-submission of first _session.start event 186 | * Fixed Safari throwing exception when in private browsing mode

187 |

v0.9.0: 188 | * Initial release. Developer preview.

189 |
190 | 191 | 192 | 193 | 194 | 195 | 196 |
197 | 198 | 201 | 202 |
203 | 204 |
205 | Documentation generated by JSDoc 3.2.2 on Tue Jul 19 2016 13:22:17 GMT-0700 (PDT) 206 |
207 | 208 | 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /doc/module-AMA.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Module: AMA 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Module: AMA

21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 |

30 | AMA 31 |

32 | 33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 |
The global namespace for Amazon Mobile Analytics
42 | 43 | 44 | 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
Source:
66 |
69 | 70 | 71 | 72 | 73 | 74 |
See:
75 |
76 | 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 | 123 | 124 |
125 | 126 |
127 | Documentation generated by JSDoc 3.2.2 on Tue Jul 19 2016 13:22:17 GMT-0700 (PDT) 128 |
129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /doc/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | */ 9 | 10 | (function() { 11 | var counter = 0; 12 | var numbered; 13 | var source = document.getElementsByClassName('prettyprint source'); 14 | 15 | if (source && source[0]) { 16 | source = source[0].getElementsByTagName('code')[0]; 17 | 18 | numbered = source.innerHTML.split('\n'); 19 | numbered = numbered.map(function(item) { 20 | counter++; 21 | return '' + item; 22 | }); 23 | 24 | source.innerHTML = numbered.join('\n'); 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /doc/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /doc/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | */ 9 | 10 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 11 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 12 | -------------------------------------------------------------------------------- /doc/scripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | */ 9 | 10 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 11 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 12 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 18 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 19 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 20 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 21 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 22 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 23 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 27 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 28 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 29 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 30 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 31 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 32 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 33 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 34 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 35 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p context.outputs.session.expirationDate) { 64 | context.outputs.session.expireSession(); 65 | delete context.outputs.session; 66 | } 67 | } 68 | }); 69 | } 70 | 71 | checkForStoredSessions(this); 72 | if (!this.outputs.session) { 73 | this.startSession(); 74 | } 75 | if (this.options.autoSubmitEvents) { 76 | this.client.submitEvents(); 77 | } 78 | }; 79 | 80 | /** 81 | * submitEvents 82 | * @param {Object} [options=] - options for submitting events 83 | * @param {Object} [options.clientContext=this.options.clientContext] - clientContext to submit with defaults to 84 | * options.clientContext 85 | * @returns {Array} Array of batch indices that were submitted 86 | */ 87 | Manager.prototype.submitEvents = function (options) { 88 | return this.client.submitEvents(options); 89 | }; 90 | 91 | /** 92 | * Function to start a session 93 | * @returns {AMA.Client.Event} The start session event recorded 94 | */ 95 | Manager.prototype.startSession = function () { 96 | this.client.logger.log('[Function:(AMA.Manager).startSession]'); 97 | if (this.outputs.session) { 98 | //Clear Session 99 | this.outputs.session.clearSession(); 100 | } 101 | this.outputs.session = new AMA.Session({ 102 | storage: this.client.storage, 103 | logger: this.client.options.logger, 104 | sessionLength: this.options.sessionLength, 105 | expirationCallback: function (session) { 106 | var shouldExtend = this.options.expirationCallback(session); 107 | if (shouldExtend === true || typeof shouldExtend === 'number') { 108 | return shouldExtend; 109 | } 110 | this.stopSession(); 111 | }.bind(this) 112 | }); 113 | return this.recordEvent('_session.start'); 114 | }; 115 | 116 | /** 117 | * Function to extend the current session. 118 | * @param {int} [milliseconds=options.sessionLength] - Milliseconds to extend the session by, will default 119 | * to another session length 120 | * @returns {int} The Session expiration (in Milliseconds) 121 | */ 122 | Manager.prototype.extendSession = function (milliseconds) { 123 | return this.outputs.session.extendSession(milliseconds || this.options.sessionLength); 124 | }; 125 | 126 | /** 127 | * Function to stop the current session 128 | * @returns {AMA.Client.Event} The stop session event recorded 129 | */ 130 | Manager.prototype.stopSession = function () { 131 | this.client.logger.log('[Function:(AMA.Manager).stopSession]'); 132 | this.outputs.session.stopSession(); 133 | this.outputs.session.expireSession(AMA.Util.NOP); 134 | return this.recordEvent('_session.stop'); 135 | }; 136 | 137 | /** 138 | * Function to stop the current session and start a new one 139 | * @returns {AMA.Session} The new Session Object for the SessionManager 140 | */ 141 | Manager.prototype.renewSession = function () { 142 | this.stopSession(); 143 | this.startSession(); 144 | return this.outputs.session; 145 | }; 146 | 147 | /** 148 | * Function that constructs a Mobile Analytics Event 149 | * @param {string} eventType - Custom Event Type to be displayed in Console 150 | * @param {AMA.Client.Attributes} [attributes=] - Map of String attributes 151 | * @param {AMA.Client.Metrics} [metrics=] - Map of numeric values 152 | * @returns {AMA.Client.Event} 153 | */ 154 | Manager.prototype.createEvent = function (eventType, attributes, metrics) { 155 | return this.client.createEvent(eventType, this.outputs.session, attributes, metrics); 156 | }; 157 | 158 | /** 159 | * Function to record a custom event 160 | * @param eventType - Custom event type name 161 | * @param {AMA.Client.Attributes} [attributes=] - Custom attributes 162 | * @param {AMA.Client.Metrics} [metrics=] - Custom metrics 163 | * @returns {AMA.Client.Event} The event that was recorded 164 | */ 165 | Manager.prototype.recordEvent = function (eventType, attributes, metrics) { 166 | return this.client.recordEvent(eventType, this.outputs.session, attributes, metrics); 167 | }; 168 | 169 | /** 170 | * Function to record a monetization event 171 | * @param {Object} monetizationDetails - Details about Monetization Event 172 | * @param {string} monetizationDetails.currency - ISO Currency of event 173 | * @param {string} monetizationDetails.productId - Product Id of monetization event 174 | * @param {number} monetizationDetails.quantity - Quantity of product in transaction 175 | * @param {string|number} monetizationDetails.price - Price of product either ISO formatted string, or number 176 | * with associated ISO Currency 177 | * @param {AMA.Client.Attributes} [attributes=] - Custom attributes 178 | * @param {AMA.Client.Metrics} [metrics=] - Custom metrics 179 | * @returns {AMA.Client.Event} The event that was recorded 180 | */ 181 | Manager.prototype.recordMonetizationEvent = function (monetizationDetails, attributes, metrics) { 182 | return this.client.recordMonetizationEvent(this.outputs.session, monetizationDetails, attributes, metrics); 183 | }; 184 | return Manager; 185 | }()); 186 | 187 | module.exports = AMA.Manager; 188 | -------------------------------------------------------------------------------- /lib/MobileAnalyticsUtilities.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | */ 9 | 10 | var AMA = global.AMA; 11 | 12 | AMA.Util = (function () { 13 | 'use strict'; 14 | function s4() { 15 | return Math.floor((1 + Math.random()) * 0x10000) 16 | .toString(16) 17 | .substring(1); 18 | } 19 | function utf8ByteLength(str) { 20 | if (typeof str !== 'string') { 21 | str = JSON.stringify(str); 22 | } 23 | var s = str.length, i, code; 24 | for (i = str.length - 1; i >= 0; i -= 1) { 25 | code = str.charCodeAt(i); 26 | if (code > 0x7f && code <= 0x7ff) { 27 | s += 1; 28 | } else if (code > 0x7ff && code <= 0xffff) { 29 | s += 2; 30 | } 31 | if (code >= 0xDC00 && code <= 0xDFFF) { /*trail surrogate*/ 32 | i -= 1; 33 | } 34 | } 35 | return s; 36 | } 37 | function guid() { 38 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); 39 | } 40 | function mergeObjects(override, initial) { 41 | Object.keys(initial).forEach(function (key) { 42 | if (initial.hasOwnProperty(key)) { 43 | override[key] = override[key] || initial[key]; 44 | } 45 | }); 46 | return override; 47 | } 48 | function copy(original, extension) { 49 | return mergeObjects(JSON.parse(JSON.stringify(original)), extension || {}); 50 | } 51 | function NOP() { 52 | return undefined; 53 | } 54 | 55 | function timestamp() { 56 | return new Date().getTime(); 57 | } 58 | return { 59 | copy: copy, 60 | GUID: guid, 61 | getRequestBodySize: utf8ByteLength, 62 | mergeObjects: mergeObjects, 63 | NOP: NOP, 64 | timestamp: timestamp 65 | }; 66 | }()); 67 | 68 | module.exports = AMA.Util; 69 | -------------------------------------------------------------------------------- /lib/StorageClients/LocalStorage.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | */ 9 | 10 | var AMA = global.AMA; 11 | AMA.Util = require('../MobileAnalyticsUtilities.js'); 12 | 13 | AMA.Storage = (function () { 14 | 'use strict'; 15 | var LocalStorageClient = function (appId) { 16 | this.storageKey = 'AWSMobileAnalyticsStorage-' + appId; 17 | global[this.storageKey] = global[this.storageKey] || {}; 18 | this.cache = global[this.storageKey]; 19 | this.cache.id = this.cache.id || AMA.Util.GUID(); 20 | this.logger = { 21 | log: AMA.Util.NOP, 22 | info: AMA.Util.NOP, 23 | warn: AMA.Util.NOP, 24 | error: AMA.Util.NOP 25 | }; 26 | this.reload(); 27 | }; 28 | // Safari, in Private Browsing Mode, looks like it supports localStorage but all calls to setItem 29 | // throw QuotaExceededError. We're going to detect this and just silently drop any calls to setItem 30 | // to avoid the entire page breaking, without having to do a check at each usage of Storage. 31 | /*global Storage*/ 32 | if (typeof localStorage === 'object' && Storage === 'object') { 33 | try { 34 | localStorage.setItem('TestLocalStorage', 1); 35 | localStorage.removeItem('TestLocalStorage'); 36 | } catch (e) { 37 | Storage.prototype._setItem = Storage.prototype.setItem; 38 | Storage.prototype.setItem = AMA.Util.NOP; 39 | console.warn('Your web browser does not support storing settings locally. In Safari, the most common cause of this is using "Private Browsing Mode". Some settings may not save or some features may not work properly for you.'); 40 | } 41 | } 42 | 43 | LocalStorageClient.prototype.type = 'LOCAL_STORAGE'; 44 | LocalStorageClient.prototype.get = function (key) { 45 | return this.cache[key]; 46 | }; 47 | LocalStorageClient.prototype.set = function (key, value) { 48 | this.cache[key] = value; 49 | return this.saveToLocalStorage(); 50 | }; 51 | LocalStorageClient.prototype.delete = function (key) { 52 | delete this.cache[key]; 53 | this.saveToLocalStorage(); 54 | }; 55 | LocalStorageClient.prototype.each = function (callback) { 56 | var key; 57 | for (key in this.cache) { 58 | if (this.cache.hasOwnProperty(key)) { 59 | callback(key, this.cache[key]); 60 | } 61 | } 62 | }; 63 | LocalStorageClient.prototype.saveToLocalStorage = function saveToLocalStorage() { 64 | if (this.supportsLocalStorage()) { 65 | try { 66 | this.logger.log('[Function:(AWS.MobileAnalyticsClient.Storage).saveToLocalStorage]'); 67 | window.localStorage.setItem(this.storageKey, JSON.stringify(this.cache)); 68 | this.logger.log('LocalStorage Cache: ' + JSON.stringify(this.cache)); 69 | } catch (saveToLocalStorageError) { 70 | this.logger.log('Error saving to LocalStorage: ' + JSON.stringify(saveToLocalStorageError)); 71 | } 72 | } else { 73 | this.logger.log('LocalStorage is not available'); 74 | } 75 | }; 76 | LocalStorageClient.prototype.reload = function loadLocalStorage() { 77 | if (this.supportsLocalStorage()) { 78 | var storedCache; 79 | try { 80 | this.logger.log('[Function:(AWS.MobileAnalyticsClient.Storage).loadLocalStorage]'); 81 | storedCache = window.localStorage.getItem(this.storageKey); 82 | this.logger.log('LocalStorage Cache: ' + storedCache); 83 | if (storedCache) { 84 | //Try to parse, if corrupt delete 85 | try { 86 | this.cache = JSON.parse(storedCache); 87 | } catch (parseJSONError) { 88 | //Corrupted stored cache, delete it 89 | this.clearLocalStorage(); 90 | } 91 | } 92 | } catch (loadLocalStorageError) { 93 | this.logger.log('Error loading LocalStorage: ' + JSON.stringify(loadLocalStorageError)); 94 | this.clearLocalStorage(); 95 | } 96 | } else { 97 | this.logger.log('LocalStorage is not available'); 98 | } 99 | }; 100 | LocalStorageClient.prototype.setLogger = function (logFunction) { 101 | this.logger = logFunction; 102 | }; 103 | LocalStorageClient.prototype.supportsLocalStorage = function supportsLocalStorage() { 104 | try { 105 | return window && window.localStorage; 106 | } catch (supportsLocalStorageError) { 107 | return false; 108 | } 109 | }; 110 | LocalStorageClient.prototype.clearLocalStorage = function clearLocalStorage() { 111 | this.cache = {}; 112 | if (this.supportsLocalStorage()) { 113 | try { 114 | this.logger.log('[Function:(AWS.MobileAnalyticsClient.Storage).clearLocalStorage]'); 115 | window.localStorage.removeItem(this.storageKey); 116 | //Clear Cache 117 | global[this.storageKey] = {}; 118 | } catch (clearLocalStorageError) { 119 | this.logger.log('Error clearing LocalStorage: ' + JSON.stringify(clearLocalStorageError)); 120 | } 121 | } else { 122 | this.logger.log('LocalStorage is not available'); 123 | } 124 | }; 125 | return LocalStorageClient; 126 | }()); 127 | 128 | module.exports = AMA.Storage; 129 | -------------------------------------------------------------------------------- /lib/StorageClients/StorageKeys.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | */ 9 | 10 | var AMA = global.AMA; 11 | 12 | AMA.StorageKeys = { 13 | 'CLIENT_ID': 'AWSMobileAnalyticsClientId', 14 | 'GLOBAL_ATTRIBUTES': 'AWSMobileAnalyticsGlobalAttributes', 15 | 'GLOBAL_METRICS': 'AWSMobileAnalyticsGlobalMetrics', 16 | 'SESSION_ID': 'MobileAnalyticsSessionId', 17 | 'SESSION_EXPIRATION': 'MobileAnalyticsSessionExpiration', 18 | 'SESSION_START_TIMESTAMP': 'MobileAnalyticsSessionStartTimeStamp' 19 | }; 20 | 21 | module.exports = AMA.StorageKeys; 22 | -------------------------------------------------------------------------------- /lib/ama.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | */ 9 | 10 | /** 11 | * @module AMA 12 | * @description The global namespace for Amazon Mobile Analytics 13 | * @see AMA.Manager 14 | * @see AMA.Client 15 | * @see AMA.Session 16 | */ 17 | global.AMA = global.AMA || {}; 18 | require('./MobileAnalyticsClient.js'); 19 | require('./MobileAnalyticsUtilities.js'); 20 | require('./StorageClients/StorageKeys.js'); 21 | require('./StorageClients/LocalStorage.js'); 22 | require('./MobileAnalyticsSession.js'); 23 | require('./MobileAnalyticsSessionManager.js'); 24 | module.exports = global.AMA; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-sdk-mobile-analytics", 3 | "description": "AWS Mobile Analytics SDK for JavaScript", 4 | "version": "0.9.2", 5 | "author": { 6 | "name": "Amazon Web Services", 7 | "email": "", 8 | "url": "http://aws.amazon.com/" 9 | }, 10 | "homepage": "https://github.com/aws/aws-sdk-mobile-analytics-js", 11 | "contributors": [], 12 | "devDependencies": { 13 | "chai": "*", 14 | "coffee-script": "~1.9.1", 15 | "coffeeify": "1.0.0", 16 | "grunt": "~0.4.5", 17 | "grunt-blanket": "~0.0.8", 18 | "grunt-browserify": "~3.5.0", 19 | "grunt-contrib-clean": "~0.6.0", 20 | "grunt-contrib-copy": "~0.8.0", 21 | "grunt-contrib-jshint": "~0.11.0", 22 | "grunt-contrib-uglify": "~0.8.0", 23 | "grunt-mocha": "~0.4.12", 24 | "grunt-mocha-test": "~0.12.7", 25 | "load-grunt-tasks": "3.5.0", 26 | "mocha-cobertura-reporter": "~1.0.4", 27 | "semver": "*", 28 | "travis-cov": "~0.2.5", 29 | "xml2js": "0.2.6" 30 | }, 31 | "dependencies": { 32 | "aws-sdk": ">=2.2.37" 33 | }, 34 | "main": "lib/ama.js", 35 | "directories": { 36 | "lib": "lib" 37 | }, 38 | "engines": { 39 | "node": ">= 0.8.0" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "git://github.com/aws/aws-sdk-mobile-analytics-js" 44 | }, 45 | "bugs": { 46 | "url": "http://github.com/aws/aws-sdk-mobile-analytics-js/issues", 47 | "mail": "" 48 | }, 49 | "licenses": [ 50 | { 51 | "type": "Apache 2.0", 52 | "url": "http://github.com/aws/aws-sdk-mobile-analytics-js/raw/master/LICENSE.txt" 53 | } 54 | ], 55 | "keywords": [ 56 | "api", 57 | "amazon", 58 | "aws", 59 | "mobileanalytics" 60 | ], 61 | "scripts": { 62 | "test": "grunt test", 63 | "unit": "grunt test", 64 | "coverage": "grunt test-nodejs", 65 | "browsertest": "grunt test-phantomjs", 66 | "distribution": "grunt dist" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/browser/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin:0; 5 | } 6 | 7 | #mocha { 8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | margin: 60px 50px; 10 | } 11 | 12 | #mocha ul, 13 | #mocha li { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | #mocha ul { 19 | list-style: none; 20 | } 21 | 22 | #mocha h1, 23 | #mocha h2 { 24 | margin: 0; 25 | } 26 | 27 | #mocha h1 { 28 | margin-top: 15px; 29 | font-size: 1em; 30 | font-weight: 200; 31 | } 32 | 33 | #mocha h1 a { 34 | text-decoration: none; 35 | color: inherit; 36 | } 37 | 38 | #mocha h1 a:hover { 39 | text-decoration: underline; 40 | } 41 | 42 | #mocha .suite .suite h1 { 43 | margin-top: 0; 44 | font-size: .8em; 45 | } 46 | 47 | #mocha .hidden { 48 | display: none; 49 | } 50 | 51 | #mocha h2 { 52 | font-size: 12px; 53 | font-weight: normal; 54 | cursor: pointer; 55 | } 56 | 57 | #mocha .suite { 58 | margin-left: 15px; 59 | } 60 | 61 | #mocha .test { 62 | margin-left: 15px; 63 | overflow: hidden; 64 | } 65 | 66 | #mocha .test.pending:hover h2::after { 67 | content: '(pending)'; 68 | font-family: arial, sans-serif; 69 | } 70 | 71 | #mocha .test.pass.medium .duration { 72 | background: #c09853; 73 | } 74 | 75 | #mocha .test.pass.slow .duration { 76 | background: #b94a48; 77 | } 78 | 79 | #mocha .test.pass::before { 80 | content: '✓'; 81 | font-size: 12px; 82 | display: block; 83 | float: left; 84 | margin-right: 5px; 85 | color: #00d6b2; 86 | } 87 | 88 | #mocha .test.pass .duration { 89 | font-size: 9px; 90 | margin-left: 5px; 91 | padding: 2px 5px; 92 | color: #fff; 93 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 95 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 96 | -webkit-border-radius: 5px; 97 | -moz-border-radius: 5px; 98 | -ms-border-radius: 5px; 99 | -o-border-radius: 5px; 100 | border-radius: 5px; 101 | } 102 | 103 | #mocha .test.pass.fast .duration { 104 | display: none; 105 | } 106 | 107 | #mocha .test.pending { 108 | color: #0b97c4; 109 | } 110 | 111 | #mocha .test.pending::before { 112 | content: '◦'; 113 | color: #0b97c4; 114 | } 115 | 116 | #mocha .test.fail { 117 | color: #c00; 118 | } 119 | 120 | #mocha .test.fail pre { 121 | color: black; 122 | } 123 | 124 | #mocha .test.fail::before { 125 | content: '✖'; 126 | font-size: 12px; 127 | display: block; 128 | float: left; 129 | margin-right: 5px; 130 | color: #c00; 131 | } 132 | 133 | #mocha .test pre.error { 134 | color: #c00; 135 | max-height: 300px; 136 | overflow: auto; 137 | } 138 | 139 | /** 140 | * (1): approximate for browsers not supporting calc 141 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) 142 | * ^^ seriously 143 | */ 144 | #mocha .test pre { 145 | display: block; 146 | float: left; 147 | clear: left; 148 | font: 12px/1.5 monaco, monospace; 149 | margin: 5px; 150 | padding: 15px; 151 | border: 1px solid #eee; 152 | max-width: 85%; /*(1)*/ 153 | max-width: calc(100% - 42px); /*(2)*/ 154 | word-wrap: break-word; 155 | border-bottom-color: #ddd; 156 | -webkit-border-radius: 3px; 157 | -webkit-box-shadow: 0 1px 3px #eee; 158 | -moz-border-radius: 3px; 159 | -moz-box-shadow: 0 1px 3px #eee; 160 | border-radius: 3px; 161 | } 162 | 163 | #mocha .test h2 { 164 | position: relative; 165 | } 166 | 167 | #mocha .test a.replay { 168 | position: absolute; 169 | top: 3px; 170 | right: 0; 171 | text-decoration: none; 172 | vertical-align: middle; 173 | display: block; 174 | width: 15px; 175 | height: 15px; 176 | line-height: 15px; 177 | text-align: center; 178 | background: #eee; 179 | font-size: 15px; 180 | -moz-border-radius: 15px; 181 | border-radius: 15px; 182 | -webkit-transition: opacity 200ms; 183 | -moz-transition: opacity 200ms; 184 | transition: opacity 200ms; 185 | opacity: 0.3; 186 | color: #888; 187 | } 188 | 189 | #mocha .test:hover a.replay { 190 | opacity: 1; 191 | } 192 | 193 | #mocha-report.pass .test.fail { 194 | display: none; 195 | } 196 | 197 | #mocha-report.fail .test.pass { 198 | display: none; 199 | } 200 | 201 | #mocha-report.pending .test.pass, 202 | #mocha-report.pending .test.fail { 203 | display: none; 204 | } 205 | #mocha-report.pending .test.pass.pending { 206 | display: block; 207 | } 208 | 209 | #mocha-error { 210 | color: #c00; 211 | font-size: 1.5em; 212 | font-weight: 100; 213 | letter-spacing: 1px; 214 | } 215 | 216 | #mocha-stats { 217 | position: fixed; 218 | top: 15px; 219 | right: 10px; 220 | font-size: 12px; 221 | margin: 0; 222 | color: #888; 223 | z-index: 1; 224 | } 225 | 226 | #mocha-stats .progress { 227 | float: right; 228 | padding-top: 0; 229 | } 230 | 231 | #mocha-stats em { 232 | color: black; 233 | } 234 | 235 | #mocha-stats a { 236 | text-decoration: none; 237 | color: inherit; 238 | } 239 | 240 | #mocha-stats a:hover { 241 | border-bottom: 1px solid #eee; 242 | } 243 | 244 | #mocha-stats li { 245 | display: inline-block; 246 | margin: 0 5px; 247 | list-style: none; 248 | padding-top: 11px; 249 | } 250 | 251 | #mocha-stats canvas { 252 | width: 40px; 253 | height: 40px; 254 | } 255 | 256 | #mocha code .comment { color: #ddd; } 257 | #mocha code .init { color: #2f6fad; } 258 | #mocha code .string { color: #5890ad; } 259 | #mocha code .keyword { color: #8a6343; } 260 | #mocha code .number { color: #2f6fad; } 261 | 262 | @media screen and (max-device-width: 480px) { 263 | #mocha { 264 | margin: 60px 0px; 265 | } 266 | 267 | #mocha #stats { 268 | position: absolute; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /test/browser/unittests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example Mocha Test 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/helpers.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | ### 9 | 10 | AWS = null 11 | global = null 12 | ignoreRequire = require 13 | if typeof window == 'undefined' 14 | AWS = require('aws-sdk') 15 | AMA = require('../lib/ama.js'); 16 | global = GLOBAL 17 | else 18 | AWS = window.AWS 19 | AMA = window.AMA 20 | global = window 21 | 22 | if global.jasmine 23 | global.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000 24 | 25 | _it = it 26 | global.it = (label, fn) -> 27 | if label.match(/\(no phantomjs\)/) and navigator and navigator.userAgent.match(/phantomjs/i) 28 | return 29 | _it(label, fn) 30 | 31 | EventEmitter = require('events').EventEmitter 32 | Buffer = AWS.util.Buffer 33 | semver = require('semver') 34 | 35 | require('util').print = (data) -> 36 | process.stdout.write(data) 37 | 38 | # Mock credentials 39 | AWS.config.update 40 | paramValidation: false 41 | region: 'mock-region' 42 | credentials: 43 | accessKeyId: 'akid' 44 | secretAccessKey: 'secret' 45 | sessionToken: 'session' 46 | 47 | spies = null 48 | beforeEach -> 49 | spies = [] 50 | 51 | afterEach -> 52 | while spies.length > 0 53 | spy = spies.pop() 54 | spy.object[spy.methodName] = spy.origMethod 55 | 56 | _createSpy = (name) -> 57 | spy = -> 58 | spy.calls.push 59 | object: this 60 | arguments: Array.prototype.slice.call(arguments) 61 | if spy.callFn 62 | return spy.callFn.apply(spy.object, arguments) 63 | if spy.shouldReturn 64 | return spy.returnValue 65 | spy.object = this 66 | spy.methodName = name 67 | spy.callFn = null 68 | spy.shouldReturn = false 69 | spy.returnValue = null 70 | spy.calls = [] 71 | spy.andReturn = (value) -> spy.shouldReturn = true; spy.returnValue = value; spy 72 | spy.andCallFake = (fn) -> spy.callFn = fn; spy 73 | spy 74 | 75 | _spyOn = (obj, methodName) -> 76 | spy = _createSpy.call(obj, methodName) 77 | spy.origMethod = obj[methodName] 78 | spy.andCallThrough = -> spy.callFn = spy.origMethod; spy 79 | obj[methodName] = spy 80 | spies.push(spy) 81 | spy 82 | 83 | # Disable setTimeout for tests 84 | # Warning: this might cause unpredictable results 85 | # TODO: refactor this out. 86 | #global.setTimeout = (fn) -> console.log(fn.name + ' called') 87 | 88 | global.expect = require('chai').expect 89 | 90 | matchXML = (xml1, xml2) -> 91 | results = [] 92 | parser = new (require('xml2js').Parser)() 93 | [xml1, xml2].forEach (xml) -> 94 | parser.parseString xml, (e,r) -> 95 | if e then throw e 96 | results.push(r) 97 | expect(results[0]).to.eql(results[1]) 98 | 99 | MockService = AWS.Service.defineService 'mockService', 100 | serviceIdentifier: 'mock' 101 | initialize: (config) -> 102 | AWS.Service.prototype.initialize.call(this, config) 103 | @config.credentials = accessKeyId: 'akid', secretAccessKey: 'secret' 104 | @config.region = 'mock-region' 105 | setupRequestListeners: (request) -> 106 | request.on 'extractData', (resp) -> 107 | resp.data = (resp.httpResponse.body||'').toString() 108 | request.on 'extractError', (resp) -> 109 | resp.error = 110 | code: (resp.httpResponse.body||'').toString() || resp.httpResponse.statusCode 111 | message: null 112 | api: new AWS.Model.Api metadata: 113 | endpointPrefix: 'mockservice' 114 | signatureVersion: 'v4' 115 | 116 | mockHttpSuccessfulResponse = (status, headers, data, cb) -> 117 | if !Array.isArray(data) 118 | data = [data] 119 | 120 | httpResp = new EventEmitter() 121 | httpResp.statusCode = status 122 | httpResp.headers = headers 123 | 124 | cb(httpResp) 125 | httpResp.emit('headers', status, headers) 126 | 127 | if AWS.util.isNode() && httpResp._events.readable 128 | httpResp.read = -> 129 | if data.length > 0 130 | chunk = data.shift() 131 | if chunk is null 132 | null 133 | else 134 | new Buffer(chunk) 135 | else 136 | null 137 | 138 | AWS.util.arrayEach data.slice(), (str) -> 139 | if AWS.util.isNode() && (httpResp._events.readable || semver.gt(process.version, 'v0.11.3')) 140 | httpResp.emit('readable') 141 | else 142 | httpResp.emit('data', new Buffer(str)) 143 | 144 | if httpResp._events['readable'] || httpResp._events['data'] 145 | httpResp.emit('end') 146 | else 147 | httpResp.emit('aborted') 148 | 149 | mockHttpResponse = (status, headers, data) -> 150 | stream = new EventEmitter() 151 | stream.setMaxListeners(0) 152 | _spyOn(AWS.HttpClient, 'getInstance') 153 | AWS.HttpClient.getInstance.andReturn handleRequest: (req, opts, cb, errCb) -> 154 | if typeof status == 'number' 155 | mockHttpSuccessfulResponse status, headers, data, cb 156 | else 157 | errCb(status) 158 | stream 159 | 160 | return stream 161 | 162 | mockIntermittentFailureResponse = (numFailures, status, headers, data) -> 163 | retryCount = 0 164 | _spyOn(AWS.HttpClient, 'getInstance') 165 | AWS.HttpClient.getInstance.andReturn handleRequest: (req, opts, cb, errCb) -> 166 | if retryCount < numFailures 167 | retryCount += 1 168 | errCb code: 'NetworkingError', message: 'FAIL!' 169 | else 170 | statusCode = retryCount < numFailures ? 500 : status 171 | mockHttpSuccessfulResponse statusCode, headers, data, cb 172 | new EventEmitter() 173 | 174 | globalEvents = null 175 | beforeEach -> globalEvents = AWS.events 176 | afterEach -> AWS.events = globalEvents 177 | 178 | setupMockResponse = (cb) -> 179 | AWS.events = new AWS.SequentialExecutor() 180 | AWS.events.on 'validate', (req) -> 181 | ['sign', 'send'].forEach (evt) -> req.removeAllListeners(evt) 182 | req.removeListener('extractData', AWS.EventListeners.CorePost.EXTRACT_REQUEST_ID) 183 | Object.keys(AWS.EventListeners).forEach (ns) -> 184 | if AWS.EventListeners[ns].EXTRACT_DATA 185 | req.removeListener('extractData', AWS.EventListeners[ns].EXTRACT_DATA) 186 | if AWS.EventListeners[ns].EXTRACT_ERROR 187 | req.removeListener('extractError', AWS.EventListeners[ns].EXTRACT_ERROR) 188 | req.response.httpResponse.statusCode = 200 189 | req.removeListener('validateResponse', AWS.EventListeners.Core.VALIDATE_RESPONSE) 190 | req.on('validateResponse', cb) 191 | 192 | mockResponse = (resp) -> 193 | reqs = [] 194 | setupMockResponse (response) -> 195 | reqs.push(response.request) 196 | AWS.util.update response, resp 197 | reqs 198 | 199 | mockResponses = (resps) -> 200 | index = 0 201 | reqs = [] 202 | setupMockResponse (response) -> 203 | reqs.push(response.request) 204 | resp = resps[index] 205 | AWS.util.update response, resp 206 | index += 1 207 | 208 | reqs 209 | 210 | operationsForRequests = (reqs) -> 211 | reqs.map (req) -> 212 | req.service.serviceIdentifier + '.' + req.operation 213 | 214 | module.exports = 215 | AWS: AWS 216 | AMA: AMA 217 | util: AWS.util 218 | spyOn: _spyOn 219 | createSpy: _createSpy 220 | matchXML: matchXML 221 | mockHttpResponse: mockHttpResponse 222 | mockIntermittentFailureResponse: mockIntermittentFailureResponse 223 | mockHttpSuccessfulResponse: mockHttpSuccessfulResponse 224 | mockResponse: mockResponse 225 | mockResponses: mockResponses 226 | operationsForRequests: operationsForRequests 227 | MockService: MockService 228 | 229 | -------------------------------------------------------------------------------- /test/mobileAnalyticsClient.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | ### 9 | 10 | helpers = require('./helpers') 11 | AMA = helpers.AMA 12 | lastError = null 13 | lastLog = null 14 | clientConfig = { 15 | appTitle : '活跃的应用 (Active App)', 16 | appId : 'e123411e2fe34eaf9ba257bbd94b44af', 17 | appPackageName : 'com.amazonAMA..console', 18 | appVersionName : '', 19 | appVersionCode : '', 20 | locale : '', 21 | platform: 'android', 22 | globalAttributes: { 23 | context: 'node' 24 | }, 25 | logger: { 26 | log: -> 27 | lastLog = arguments 28 | error: -> 29 | lastError = arguments 30 | } 31 | } 32 | 33 | emptyAppIDClientConfig = { 34 | platform: 'myplatform', 35 | logger: { 36 | log: -> 37 | lastLog = arguments 38 | error: -> 39 | lastError = arguments 40 | } 41 | } 42 | 43 | emptyPlatformClientConfig = { 44 | appId : 'testID1', 45 | logger: { 46 | log: -> 47 | lastLog = arguments 48 | error: -> 49 | lastError = arguments 50 | } 51 | } 52 | 53 | validClientConfig = { 54 | appId : 'testID2', 55 | platform: 'android' 56 | } 57 | 58 | autoSubmitClientConfig = { 59 | appId : 'testID3', 60 | platform: 'ios', 61 | autoSubmitEvents: false 62 | } 63 | 64 | mobileAnalyticsClient = null 65 | 66 | describe 'AMA.Client', -> 67 | describe 'Initialize Client', -> 68 | before -> 69 | mobileAnalyticsClient = new AMA.Client(clientConfig) 70 | it 'should not be initialized', -> 71 | expect(mobileAnalyticsClient).not.to.be.null 72 | expect(mobileAnalyticsClient).not.to.be.undefined 73 | it 'should be an instance of AMA.Client', -> 74 | expect(mobileAnalyticsClient).to.be.an.instanceOf(AMA.Client) 75 | it 'should have a not null client id', -> 76 | expect(mobileAnalyticsClient.options.clientContext.client.client_id).not.to.be.null 77 | expect(mobileAnalyticsClient.options.clientContext.client.client_id).not.to.be.undefined 78 | describe 'Initialize Client with incorrect parameters', -> 79 | it 'should throw no appId error', -> 80 | mobileAnalyticsClient = new AMA.Client(emptyAppIDClientConfig) 81 | expect(lastError[0]).to.eql('AMA.Client must be initialized with an appId') 82 | it 'should throw no platform error', -> 83 | mobileAnalyticsClient = new AMA.Client(emptyPlatformClientConfig) 84 | expect(lastError[0]).to.eql('AMA.Client must be initialized with a platform') 85 | describe 'Initialize Client with correct parameters', -> 86 | before -> 87 | mobileAnalyticsClient = new AMA.Client(validClientConfig) 88 | it 'should be an instance of AMA.Client', -> 89 | expect(mobileAnalyticsClient).to.be.an.instanceOf(AMA.Client) 90 | it 'should have correct appId', -> 91 | expect(mobileAnalyticsClient.options.appId).to.eql('testID2') 92 | it 'should have correct platform', -> 93 | expect(mobileAnalyticsClient.options.platform).to.eql('android') 94 | describe 'Initialize Client with autoSubmit set to false', -> 95 | before -> 96 | mobileAnalyticsClient = new AMA.Client(autoSubmitClientConfig) 97 | it 'should be an instance of AMA.Client', -> 98 | expect(mobileAnalyticsClient).to.be.an.instanceOf(AMA.Client) 99 | it 'should have correct appId', -> 100 | expect(mobileAnalyticsClient.options.appId).to.eql('testID3') 101 | it 'should have correct platform', -> 102 | expect(mobileAnalyticsClient.options.platform).to.eql('ios') 103 | it 'should have autoSubmit set to false', -> 104 | expect(mobileAnalyticsClient.options.autoSubmitEvents).to.be.false 105 | describe 'Clear a batch', -> 106 | before -> 107 | mobileAnalyticsClient = new AMA.Client(clientConfig) 108 | mobileAnalyticsClient.outputs.batchIndex.push('clearABatch') 109 | mobileAnalyticsClient.outputs.batches['clearABatch'] = 'test' 110 | mobileAnalyticsClient.clearBatchById('clearABatch') 111 | it 'should clear batch', -> 112 | expect(mobileAnalyticsClient.outputs.batches['clearABatch']).to.be.undefined 113 | it 'should clear batchIndex', -> 114 | expect(mobileAnalyticsClient.outputs.batchIndex.length).to.eql(0) 115 | describe 'Client ID', -> 116 | it 'should have same clientId when init-d repeatedly', -> 117 | expect(new AMA.Client(clientConfig).options.clientContext.client.client_id).to.eql(new AMA.Client(clientConfig).options.clientContext.client.client_id) -------------------------------------------------------------------------------- /test/mobileAnalyticsSessionClient.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | ### 9 | 10 | helpers = require('./helpers') 11 | AWS = helpers.AWS 12 | AWS.VERSION = 'AWS_VERSION' 13 | AMA = helpers.AMA 14 | mobileAnalyticsClient = null 15 | lastLog = null 16 | lastError = null 17 | lastWarn = null 18 | lastClientContext = null 19 | monetizationEvent = null 20 | m1 = null 21 | m2 = null 22 | m1_2 = null 23 | batchIds = null 24 | submissions = 0 25 | expectedSubmissions = 0 26 | countSubmissionCallback = (err, results) -> 27 | if (!err) 28 | console.log(submissions++, err, results) 29 | # expect(submissions).to.eql(expectedSubmissions) 30 | 31 | logMessage = (message) -> 32 | lastLog = arguments 33 | #console.log(JSON.stringify(message)) 34 | 35 | errorMessage = (message) -> 36 | lastError = arguments 37 | #console.error(JSON.stringify(message)) 38 | 39 | warnMessage = (message) -> 40 | lastWarn = arguments 41 | # console.log(JSON.stringify(message)) 42 | 43 | clientConfig = { 44 | appTitle : 'appTitle', 45 | appId : 'appId', 46 | appPackageName : 'appPackageName', 47 | appVersionName : 'appVersionName', 48 | appVersionCode : 'appVersionCode', 49 | locale : 'locale', 50 | platform: 'platform', 51 | model:"model", 52 | make:"make", 53 | platformVersion:"platformVersion", 54 | globalAttributes: { 55 | context: 'node' 56 | }, 57 | logger: { 58 | log: logMessage 59 | error: errorMessage, 60 | warn: warnMessage, 61 | info: logMessage 62 | } 63 | } 64 | s = new AMA.Storage('appId') 65 | s.clearLocalStorage() 66 | 67 | createErrorResponse = (code, message) -> 68 | message = message || '' 69 | if code 70 | { 71 | "code": code, 72 | "message": message, 73 | "time":"2015-02-26T19:43:20.424Z", 74 | "statusCode":400, 75 | "retryable":false, 76 | "retryDelay":30 77 | } 78 | else 79 | null 80 | currentError = null 81 | lastEventRequest = null 82 | AWS.MobileAnalytics = (options) -> 83 | this.putEvents = (eventRequest, callbackFunc) -> 84 | #console.log('submitting' + currentError) 85 | lastEventRequest = eventRequest 86 | callbackFunc(currentError, null) 87 | this 88 | 89 | testBatchNotDeleted = (errorType) -> 90 | describe 'Test Response Code Handling (' + errorType + ')', -> 91 | beforeEach -> 92 | mobileAnalyticsClient = new AMA.Client(clientConfig) 93 | currentError = createErrorResponse(errorType) 94 | mobileAnalyticsClient.outputs.lastSubmitTimestamp = 0 95 | it 'should not delete the batchIndex', -> 96 | mobileAnalyticsClient.recordEvent('should not delete batchIndex ' + errorType, {testAttribute: 'invalid'}, {testMetric: 100}) 97 | indices = mobileAnalyticsClient.submitEvents() 98 | expect(indices.length).to.eql(1) 99 | expect(mobileAnalyticsClient.outputs.batchIndex.indexOf(indices[0])).to.eql(0) 100 | it 'should not delete the batch', -> 101 | mobileAnalyticsClient.recordEvent('should not delete batchIndex other ' + errorType, {testAttribute: 'invalid'}, {testMetric: 100}) 102 | indices = mobileAnalyticsClient.submitEvents() 103 | expect(indices.length).to.eql(2) 104 | expect(mobileAnalyticsClient.outputs.batches[indices[0]]).not.to.be.undefined 105 | it 'should not delete the batch index from storage', -> 106 | mobileAnalyticsClient.recordEvent('should not delete batchIndex other ' + errorType, {testAttribute: 'invalid'}, {testMetric: 100}) 107 | indices = mobileAnalyticsClient.submitEvents() 108 | expect(indices.length).to.eql(3) 109 | expect(mobileAnalyticsClient.storage.get(mobileAnalyticsClient.StorageKeys.BATCH_INDEX).indexOf(indices[0])).to.eql(0) 110 | it 'should not delete the batch from storage', -> 111 | mobileAnalyticsClient.recordEvent('should not delete batchIndex other ' + errorType, {testAttribute: 'invalid'}, {testMetric: 100}) 112 | indices = mobileAnalyticsClient.submitEvents() 113 | expect(indices.length).to.eql(4) 114 | expect(mobileAnalyticsClient.storage.get(mobileAnalyticsClient.StorageKeys.BATCHES)[indices[0]]).not.to.be.undefined 115 | 116 | 117 | testBatchDeleted = (errorType) -> 118 | describe 'Test Response Code Handling (' + errorType + ')', -> 119 | beforeEach -> 120 | mobileAnalyticsClient = new AMA.Client(clientConfig) 121 | currentError = createErrorResponse(errorType) 122 | mobileAnalyticsClient.outputs.lastSubmitTimestamp = 0 123 | it 'should delete the batchIndex', -> 124 | mobileAnalyticsClient.recordEvent('should delete batchIndex ' + errorType, {testAttribute: 'invalid'}, {testMetric: 100}) 125 | indices = mobileAnalyticsClient.submitEvents() 126 | expect(indices.length).to.eql(1) 127 | expect(mobileAnalyticsClient.outputs.batchIndex.indexOf(indices[0])).to.eql(-1) 128 | it 'should delete the batch', -> 129 | mobileAnalyticsClient.recordEvent('should delete batch ' + errorType, {testAttribute: 'invalid'}, {testMetric: 100}) 130 | indices=mobileAnalyticsClient.submitEvents() 131 | expect(indices.length).to.eql(1) 132 | expect(mobileAnalyticsClient.outputs.batches[indices[0]]).to.be.undefined 133 | it 'should delete the batch index from storage', -> 134 | mobileAnalyticsClient.recordEvent('should delete index storage ' + errorType, {testAttribute: 'invalid'}, {testMetric: 100}) 135 | indices=mobileAnalyticsClient.submitEvents() 136 | expect(indices.length).to.eql(1) 137 | expect(mobileAnalyticsClient.storage.get(mobileAnalyticsClient.StorageKeys.BATCH_INDEX).indexOf(indices[0])).to.eql(-1) 138 | it 'should delete the batch from storage', -> 139 | mobileAnalyticsClient.recordEvent('should delete batch storage ' + errorType, {testAttribute: 'invalid'}, {testMetric: 100}) 140 | indices=mobileAnalyticsClient.submitEvents() 141 | expect(indices.length).to.eql(1) 142 | expect(mobileAnalyticsClient.storage.get(mobileAnalyticsClient.StorageKeys.BATCHES)[indices[0]]).to.be.undefined 143 | 144 | 145 | describe 'AMA.Manager', -> 146 | describe 'Initialize Client with Options', -> 147 | before -> 148 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 149 | it 'should be initialized', -> 150 | expect(mobileAnalyticsClient).not.to.be.null 151 | expect(mobileAnalyticsClient).not.to.be.undefined 152 | it 'should be an instance of AMA.Client', -> 153 | expect(mobileAnalyticsClient).to.be.an.instanceOf(AMA.Manager) 154 | describe 'Initalize Client with client', -> 155 | before -> 156 | subclient = new AMA.Client(clientConfig) 157 | mobileAnalyticsClient = new AMA.Manager(subclient) 158 | it 'should be initialized', -> 159 | expect(mobileAnalyticsClient).not.to.be.null 160 | expect(mobileAnalyticsClient).not.to.be.undefined 161 | it 'should be an instance of AMA.Client', -> 162 | expect(mobileAnalyticsClient).to.be.an.instanceOf(AMA.Manager) 163 | describe 'Renewing Session', -> 164 | before -> 165 | #Clear Events 166 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 167 | currentError = null 168 | mobileAnalyticsClient.submitEvents() 169 | mobileAnalyticsClient.renewSession() 170 | it 'should increase event count', -> 171 | # expect(mobileAnalyticsClient.outputs.events.length).to.eql(2) 172 | it 'should have a new session id', -> 173 | expect(mobileAnalyticsClient.outputs.events[0].eventType).to.eql('_session.stop') 174 | expect(mobileAnalyticsClient.outputs.events[1].eventType).to.eql('_session.start') 175 | expect(mobileAnalyticsClient.outputs.events[0].session.id).not.to.eql(mobileAnalyticsClient.outputs.events[1].session.id) 176 | describe 'Client Context Values', -> 177 | before -> 178 | #Clear Events 179 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 180 | lastClientContext = JSON.parse(lastEventRequest.clientContext) 181 | it 'should have appTitle', -> 182 | expect(lastClientContext.client.app_title).to.eql('appTitle') 183 | it 'should have appPackageName', -> 184 | expect(lastClientContext.client.app_package_name).to.eql('appPackageName') 185 | it 'should have appVersionName', -> 186 | expect(lastClientContext.client.app_version_name).to.eql('appVersionName') 187 | it 'should have appVersionCode', -> 188 | expect(lastClientContext.client.app_version_code).to.eql('appVersionCode') 189 | it 'should have platform', -> 190 | expect(lastClientContext.env.platform).to.eql('platform') 191 | it 'should have model', -> 192 | expect(lastClientContext.env.model).to.eql('model') 193 | it 'should have make', -> 194 | expect(lastClientContext.env.make).to.eql('make') 195 | it 'should have platform_version', -> 196 | expect(lastClientContext.env.platform_version).to.eql('platformVersion') 197 | it 'should have locale', -> 198 | expect(lastClientContext.env.locale).to.eql('locale') 199 | it 'should have appId', -> 200 | expect(lastClientContext.services.mobile_analytics.app_id).to.eql('appId') 201 | it 'should have sdk', -> 202 | expect(lastClientContext.services.mobile_analytics.sdk_name).to.eql('aws-sdk-mobile-analytics-js') 203 | it 'should have sdk version', -> 204 | expect(lastClientContext.services.mobile_analytics.sdk_version).to.eql('0.9.2:AWS_VERSION') 205 | describe 'Record Monetization Event (Currency Specified)', -> 206 | before -> 207 | #Clear Events 208 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 209 | currentError = null 210 | mobileAnalyticsClient.submitEvents() 211 | monetizationEvent = mobileAnalyticsClient.recordMonetizationEvent({ 212 | 'currency': 'USD', 213 | 'productId': 'MyFirstProduct', 214 | 'quantity': 1, 215 | 'price': 4.99 216 | }) 217 | it 'should return a monetization event object', -> 218 | expect(monetizationEvent).to.not.be.undefined 219 | it 'should increase event count', -> 220 | # expect(mobileAnalyticsClient.outputs.events.length).to.eql(1) 221 | it 'should specify a currency (USD)', -> 222 | expect(mobileAnalyticsClient.outputs.events[0].attributes._currency).to.eql('USD') 223 | it 'should specify a product id', -> 224 | expect(mobileAnalyticsClient.outputs.events[0].attributes._product_id).to.eql('MyFirstProduct') 225 | it 'should cost 4.99', -> 226 | expect(mobileAnalyticsClient.outputs.events[0].metrics._item_price).to.eql(4.99) 227 | it 'should have a quantity of 1', -> 228 | expect(mobileAnalyticsClient.outputs.events[0].metrics._quantity).to.eql(1) 229 | describe 'Record Monetization Event (Currency Formatted)', -> 230 | before -> 231 | #Clear Events 232 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 233 | currentError = null 234 | mobileAnalyticsClient.submitEvents() 235 | mobileAnalyticsClient.recordMonetizationEvent({'productId': 'MyFirstProduct','quantity': 1,'price': '$4.99'}) 236 | it 'should increase event count', -> 237 | # expect(mobileAnalyticsClient.outputs.events.length).to.eql(1) 238 | it 'should specify a product id', -> 239 | expect(mobileAnalyticsClient.outputs.events[0].attributes._product_id).to.eql('MyFirstProduct') 240 | it 'should cost $4.99', -> 241 | expect(mobileAnalyticsClient.outputs.events[0].attributes._item_price_formatted).to.eql('$4.99') 242 | it 'should have a quantity of 1', -> 243 | expect(mobileAnalyticsClient.outputs.events[0].metrics._quantity).to.eql(1) 244 | describe 'Record invalid Metric Event', -> 245 | before -> 246 | #Clear Events 247 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 248 | currentError = null 249 | mobileAnalyticsClient.submitEvents() 250 | it 'should not record an Event', -> 251 | mobileAnalyticsClient.recordEvent('invalidMetricEvent', {testAttribute: 'test'},{testMetric: 'invalid'}) 252 | expect(mobileAnalyticsClient.outputs.events.length).to.eql(0) 253 | describe 'Record a numeric attribute', -> 254 | before -> 255 | #Clear Events 256 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 257 | currentError = null 258 | mobileAnalyticsClient.submitEvents() 259 | mobileAnalyticsClient.recordEvent('numericAttributeEvent', {testAttribute: 100},{testMetric: 100}) 260 | it 'should specify a product id', -> 261 | expect(mobileAnalyticsClient.outputs.events[0].eventType).to.eql('numericAttributeEvent') 262 | describe 'Validate Event Attribute Key Length', -> 263 | before -> 264 | #Clear Events 265 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 266 | currentError = null 267 | mobileAnalyticsClient.submitEvents() 268 | mobileAnalyticsClient.recordEvent('invalidEvent', {invalidAttributeKeyLongStringABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 100},{}) 269 | it 'should return a null event', -> 270 | expect(mobileAnalyticsClient.outputs.events.length).to.be.eql(0) 271 | describe 'Validate Event Attribute Value Length', -> 272 | before -> 273 | #Clear Events 274 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 275 | currentError = null 276 | mobileAnalyticsClient.submitEvents() 277 | mobileAnalyticsClient.recordEvent('invalidEvent', {invalidAttributeValue:'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'},{}) 278 | it 'should return a null event', -> 279 | expect(mobileAnalyticsClient.outputs.events.length).to.be.eql(0) 280 | describe 'Validate Event Metric Key Length', -> 281 | before -> 282 | #Clear Events 283 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 284 | currentError = null 285 | mobileAnalyticsClient.submitEvents() 286 | mobileAnalyticsClient.recordEvent('invalidEvent', {},{invalidMetricKeyLongStringABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ:100}) 287 | it 'should return a null event', -> 288 | expect(mobileAnalyticsClient.outputs.events.length).to.be.eql(0) 289 | describe 'Record an event with Unicode attribute / metric names', -> 290 | it 'should specify a product id', -> 291 | mobileAnalyticsClient = new AMA.Manager(clientConfig) 292 | currentError = null 293 | mobileAnalyticsClient.recordEvent('numericAttributeEvent', {'活跃': 'attribute'},{'的应用': 100}) 294 | mobileAnalyticsClient.outputs.lastSubmitTimestamp = 0 295 | mobileAnalyticsClient.submitEvents() 296 | expect(mobileAnalyticsClient.outputs.events.length).to.eql(0) 297 | expect(mobileAnalyticsClient.outputs.batchIndex.length).to.eql(0) 298 | 299 | describe 'Large amount of events (greater than batchSize)', -> 300 | it 'should autosubmit', -> 301 | myConfig = JSON.parse(JSON.stringify(clientConfig)) 302 | myConfig.batchSizeLimit = 3026 303 | mobileAnalyticsClient = new AMA.Manager(myConfig) 304 | currentError = null 305 | for x in [0..10] 306 | do (x) -> 307 | mobileAnalyticsClient.outputs.lastSubmitTimestamp = 0 308 | mobileAnalyticsClient.recordEvent('autoSubmitByBatch', {testAttribute: 'valid'}, {testMetric: 100}) 309 | expect(mobileAnalyticsClient.outputs.events.length).to.eql(0) 310 | expect(mobileAnalyticsClient.outputs.batchIndex.length).to.eql(0) 311 | describe 'Test Response Code Handling', -> 312 | testBatchDeleted(null) 313 | testBatchDeleted('ValidationException') 314 | testBatchDeleted('SerializationException') 315 | testBatchDeleted('BadRequestException') 316 | testBatchNotDeleted('OtherException') 317 | 318 | describe 'Test Event Order (Retryable) Handling', -> 319 | batchSet1 = [] 320 | batchSet2 = [] 321 | batchSet3 = [] 322 | batchSet4 = [] 323 | beforeEach -> 324 | mobileAnalyticsClient = new AMA.Client(clientConfig) 325 | mobileAnalyticsClient.storage.delete(mobileAnalyticsClient.StorageKeys.BATCHES) 326 | mobileAnalyticsClient.storage.delete(mobileAnalyticsClient.StorageKeys.BATCH_INDEX) 327 | mobileAnalyticsClient = new AMA.Client(clientConfig) 328 | currentError = createErrorResponse('OtherException') 329 | mobileAnalyticsClient.outputs.lastSubmitTimestamp = 0 330 | mobileAnalyticsClient.recordEvent('orderEvent0', {testAttribute: 0}) 331 | mobileAnalyticsClient.recordEvent('orderEvent1', {testAttribute: 1}) 332 | batchSet1 = mobileAnalyticsClient.submitEvents() 333 | mobileAnalyticsClient.outputs.lastSubmitTimestamp = 0 334 | mobileAnalyticsClient.recordEvent('orderEvent2', {testAttribute: 2}) 335 | mobileAnalyticsClient.recordEvent('orderEvent3', {testAttribute: 3}) 336 | batchSet2 = mobileAnalyticsClient.submitEvents() 337 | mobileAnalyticsClient.outputs.lastSubmitTimestamp = 0 338 | mobileAnalyticsClient.recordEvent('orderEvent4', {testAttribute: 4}) 339 | mobileAnalyticsClient.recordEvent('orderEvent5', {testAttribute: 5}) 340 | batchSet3 = mobileAnalyticsClient.submitEvents() 341 | mobileAnalyticsClient.outputs.lastSubmitTimestamp = 0 342 | mobileAnalyticsClient.recordEvent('orderEvent6', {testAttribute: 6}) 343 | mobileAnalyticsClient.recordEvent('orderEvent7', {testAttribute: 7}) 344 | batchSet4 = mobileAnalyticsClient.submitEvents() 345 | it 'should have 4 batches', -> 346 | expect(mobileAnalyticsClient.outputs.batchIndex.length).to.eql(4) 347 | expect(Object.keys(mobileAnalyticsClient.outputs.batches).length).to.eql(4) 348 | it 'should persist 4 batches', -> 349 | expect(mobileAnalyticsClient.storage.get(mobileAnalyticsClient.StorageKeys.BATCH_INDEX).length).to.eql(4) 350 | expect(Object.keys(mobileAnalyticsClient.storage.get(mobileAnalyticsClient.StorageKeys.BATCHES)).length).to.eql(4) 351 | it 'should have batches in same order as submissions', -> 352 | expect(mobileAnalyticsClient.outputs.batchIndex[0]).to.eql(batchSet1[0]) 353 | expect(mobileAnalyticsClient.outputs.batchIndex[1]).to.eql(batchSet2[1]) 354 | expect(mobileAnalyticsClient.outputs.batchIndex[2]).to.eql(batchSet3[2]) 355 | expect(mobileAnalyticsClient.outputs.batchIndex[3]).to.eql(batchSet4[3]) 356 | it 'should have events in the correct order in the batches', -> 357 | checkBatch = (batchId) -> 358 | batch = mobileAnalyticsClient.outputs.batches[batchId] 359 | console.log(JSON.stringify(batch)) 360 | expect(parseInt(batch[0].eventType.replace('orderEvent', ''))).to.be.below(parseInt(batch[1].eventType.replace('orderEvent', ''))) 361 | checkBatch batchId for batchId in mobileAnalyticsClient.outputs.batchIndex 362 | 363 | describe 'Multiple SDK Instances', -> 364 | before -> 365 | s1 = new AMA.Storage('id1') 366 | s1.clearLocalStorage() 367 | s2 = new AMA.Storage('id2') 368 | s2.clearLocalStorage() 369 | m1 = new AMA.Manager(AMA.Util.copy({appId: 'id1'}, clientConfig)) 370 | m2 = new AMA.Manager(AMA.Util.copy({appId: 'id2'}, clientConfig)) 371 | m1_2 = new AMA.Manager(AMA.Util.copy({appId: 'id1'}, clientConfig)) 372 | m1.recordEvent('m1event1') 373 | m1.recordEvent('m1event2') 374 | m2.recordEvent('m2event1') 375 | it 'm1 should have only m1 events', -> 376 | expect(m1.client.storage.cache.AWSMobileAnalyticsEventStorage.length).to.eql(2); 377 | expect(m1.client.storage.cache.AWSMobileAnalyticsEventStorage[0].eventType).to.eql('m1event1'); 378 | expect(m1.client.storage.cache.AWSMobileAnalyticsEventStorage[1].eventType).to.eql('m1event2'); 379 | it 'm2 should have only m2 event', -> 380 | expect(m2.client.storage.cache.AWSMobileAnalyticsEventStorage.length).to.eql(1); 381 | expect(m2.client.storage.cache.AWSMobileAnalyticsEventStorage[0].eventType).to.eql('m2event1'); 382 | it 'm1_2 should have same client id as m1', -> 383 | expect(m1.client.options.clientContext.client.client_id).to.eql(m1_2.client.options.clientContext.client.client_id) 384 | describe 'Throttling Interval Backoff', -> 385 | before -> 386 | s1 = new AMA.Storage('throttlingId') 387 | s1.clearLocalStorage() 388 | m1 = new AMA.Manager(AMA.Util.copy({ 389 | appId: 'throttlingId', 390 | autoSubmitEvents: false 391 | }, clientConfig)) 392 | currentError = createErrorResponse('OtherException') 393 | m1.outputs.lastSubmitTimestamp = 0 394 | lastWarn = undefined 395 | lastLog = undefined 396 | batchIds = m1.submitEvents() 397 | currentError = createErrorResponse('ThrottlingException') 398 | m1.recordEvent('custom_event') 399 | m1.outputs.lastSubmitTimestamp = 0 400 | lastWarn = undefined 401 | lastLog = undefined 402 | m1.submitEvents() 403 | expect(m1.outputs.isThrottled).to.eql(true); 404 | Math._random = Math.random 405 | Math.random = () -> 1 406 | after -> 407 | Math.random = Math._random 408 | it 'm1 should not submit if at 30s', -> 409 | m1.outputs.lastSubmitTimestamp = (new Date()).getTime() - (30 * 1000) 410 | expect(m1.outputs.isThrottled).to.eql(true); 411 | expect(Object.keys(m1.outputs.batchesInFlight).length).to.eql(0); 412 | expect(Object.keys(m1.outputs.batches).length).to.eql(2); 413 | tmp = m1.submitEvents() 414 | expect(tmp).to.eql([]); 415 | it 'm1 should submit if at 60s', -> 416 | m1.outputs.lastSubmitTimestamp = (new Date()).getTime() - (60 * 1001) 417 | currentError = null 418 | expect(m1.outputs.isThrottled).to.eql(true); 419 | expect(Object.keys(m1.outputs.batchesInFlight).length).to.eql(0); 420 | expect(Object.keys(m1.outputs.batches).length).to.eql(2); 421 | phoneHomeBatch = m1.submitEvents({submitCallback: countSubmissionCallback}) 422 | console.log(lastWarn) 423 | expect(phoneHomeBatch.length).to.eql(1); 424 | expect(phoneHomeBatch).to.eql([batchIds[0]]); 425 | expect(submissions).to.eql(2) -------------------------------------------------------------------------------- /test/session.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | ### 9 | 10 | helpers = require('./helpers') 11 | AWS = helpers.AWS 12 | AMA = helpers.AMA 13 | expiration = null 14 | session = null 15 | storage = new AMA.Storage('AppID') 16 | 17 | describe 'AMA.Session', -> 18 | describe 'Initialize Session (Default Values)', -> 19 | before -> 20 | session = new AMA.Session({storage: storage}) 21 | it 'should be initialized', -> 22 | expect(session).not.to.be.null 23 | expect(session).not.to.be.undefined 24 | it 'should have scoped sessionId storage key', -> 25 | expect(session.StorageKeys.SESSION_ID).to.not.eql('MobileAnalyticsSessionId') 26 | expect(session.StorageKeys.SESSION_ID).to.have.string('MobileAnalyticsSessionId') 27 | expect(session.StorageKeys.SESSION_ID).to.have.string(session.id) 28 | it 'should have scoped sessionExpiration storage key', -> 29 | expect(session.StorageKeys.SESSION_EXPIRATION).to.not.eql('MobileAnalyticsSessionExpiration') 30 | expect(session.StorageKeys.SESSION_EXPIRATION).to.have.string('MobileAnalyticsSessionExpiration') 31 | expect(session.StorageKeys.SESSION_EXPIRATION).to.have.string(session.id) 32 | it 'should persist session id', -> 33 | expect(storage.get(session.StorageKeys.SESSION_ID)).not.to.be.null 34 | expect(storage.get(session.StorageKeys.SESSION_ID)).to.eql(session.id) 35 | it 'should persist expiration', -> 36 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION)).not.to.be.null 37 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION)).to.eql(session.expirationDate) 38 | it 'should have a number expiration', -> 39 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION)).to.be.a('number') 40 | expect(session.expirationDate).to.be.a('number') 41 | it 'should have an integer expiration', -> 42 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION) % 1).to.eql(0) 43 | expect(session.expirationDate % 1).to.eql(0) 44 | describe 'Clear Session', -> 45 | before -> 46 | session = new AMA.Session({storage: storage}) 47 | session.expireSession() 48 | it 'should clear session id', -> 49 | expect(storage.get(session.StorageKeys.SESSION_ID)).to.be.undefined 50 | it 'should clear session expiration', -> 51 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION)).to.be.undefined 52 | ### 53 | Needs duplicate session/expiration definitions due to Storage getting reloaded in another file. 54 | ### 55 | describe 'Extend Session (Default Values)', -> 56 | before -> 57 | session = new AMA.Session({storage: storage}) 58 | expiration = session.expirationDate 59 | session.extendSession() 60 | it 'should not be original expiration date', -> 61 | expect(expiration).to.not.eql(session.expirationDate) 62 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION)).to.not.eql(expiration) 63 | it 'should persist new expiration date', -> 64 | session = new AMA.Session({storage: storage}) 65 | expiration = session.expirationDate 66 | session.extendSession() 67 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION)).to.eql(session.expirationDate) 68 | it 'should be 30min later', -> 69 | expect(session.expirationDate).to.eql(expiration + session.sessionLength) 70 | describe 'Extend Session (1 min later)', -> 71 | before -> 72 | session = new AMA.Session({storage: storage}) 73 | expiration = session.expirationDate 74 | session.extendSession(60000) 75 | it 'should not be original expiration date', -> 76 | expect(expiration).to.not.eql(session.expirationDate) 77 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION)).to.not.eql(expiration) 78 | it 'should persist new expiration date', -> 79 | session = new AMA.Session({storage: storage}) 80 | expiration = session.expirationDate 81 | session.extendSession(60000) 82 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION)).to.eql(session.expirationDate) 83 | it 'should be 60 sec later', -> 84 | expect(session.expirationDate).to.eql(expiration + 60000) 85 | describe 'Reset Session Timeout (1 min from now)', -> 86 | beforeEach -> 87 | session = new AMA.Session({storage: storage}) 88 | expiration = session.expirationDate 89 | session.resetSessionTimeout(60000) 90 | it 'should not be original expiration date', -> 91 | expect(expiration).to.not.eql(session.expirationDate) 92 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION)).to.not.eql(expiration) 93 | it 'should persist new expiration date', -> 94 | expect(storage.get(session.StorageKeys.SESSION_EXPIRATION)).to.eql(session.expirationDate) 95 | -------------------------------------------------------------------------------- /test/storage.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | ### 9 | 10 | helpers = require('./helpers') 11 | AWS = helpers.AWS 12 | AMA = helpers.AMA 13 | 14 | describe 'AMA.Storage (In Memory)', -> 15 | storage = new AMA.Storage('MyAppId') 16 | it 'should set ascii', -> 17 | storage.set('_test123', '_test123') 18 | expect(storage.get('_test123')).to.equal('_test123') 19 | it 'should delete stored ascii', -> 20 | storage.delete('_test123') 21 | expect(storage.get('_test123')).to.equal() 22 | 23 | describe 'AMA.Storage (LocalStorage)', -> 24 | storage = new AMA.Storage('MyAppId') 25 | it 'should set ascii', -> 26 | storage.set('_test123', '_test123') 27 | expect(storage.get('_test123')).to.equal('_test123') 28 | it 'should delete stored ascii', -> 29 | storage.delete('_test123') 30 | expect(storage.get('_test123')).to.equal() 31 | 32 | describe 'AMA.Storage cache singleton', -> 33 | it 'should be same cache instance', -> 34 | s1 = new AMA.Storage('MyAppId') 35 | s2 = new AMA.Storage('MyAppId') 36 | expect(s1.cache.id).to.eql(s2.cache.id) 37 | -------------------------------------------------------------------------------- /test/utils.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | ### 9 | 10 | helpers = require('./helpers') 11 | AWS = helpers.AWS 12 | AMA = helpers.AMA 13 | a = 14 | b = 15 | describe 'AMA.Util', -> 16 | describe 'GUID', -> 17 | it 'should not be equal', -> 18 | expect(AMA.Util.GUID()).to.not.eql(AMA.Util.GUID()) 19 | describe 'mergeObjects', -> 20 | beforeEach -> 21 | a = {a: 1, b:2} 22 | b = {b:1, c:3} 23 | it 'should merge with overlapping keys', -> 24 | expect(AMA.Util.mergeObjects(a, b)).to.eql({a:1, b:2, c:3}) 25 | it 'should mutate original', -> 26 | expect(AMA.Util.mergeObjects(a, b)).to.equal(a) 27 | describe 'utf8ByteLength', -> 28 | it 'should test char codes > 127', -> 29 | expect(AMA.Util.getRequestBodySize('©‰')).to.eql(5) 30 | it 'should test trail surrogate', -> 31 | expect(AMA.Util.getRequestBodySize('𝌆')).to.eql(4) 32 | describe 'copy', -> 33 | beforeEach -> 34 | a = {a: 1, b:2} 35 | b = {c: 3} 36 | it 'should copy with new keys', -> 37 | expect(AMA.Util.copy(a, b)).to.eql({a:1, b:2, c:3}) 38 | it 'should not mutate original', -> 39 | expect(AMA.Util.copy(a, b)).to.not.equal(a) -------------------------------------------------------------------------------- /test/v090_migration_v091.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 7 | and limitations under the License. 8 | ### 9 | 10 | helpers = require('./helpers') 11 | AMA = helpers.AMA 12 | mobileAnalyticsClient = null 13 | lastError = null 14 | lastLog = null 15 | testAppId = 'e123411e2fe34eaf9ba257bbd94b44af' 16 | localStorage = new AMA.Storage(testAppId) 17 | 18 | 19 | appStorageKey = 'AWSMobileAnalyticsStorage' 20 | clientId1 = 'v0.9.0' 21 | clientId2 = 'v0.9.1' 22 | 23 | v090Data = 24 | AWSMobileAnalyticsClientId: clientId1 25 | v091Data = 26 | AWSMobileAnalyticsClientId: clientId2 27 | 28 | try 29 | window = window 30 | catch 31 | window = false 32 | 33 | describe 'Migration SDK v0.9.0 to v0.9.1', -> 34 | try 35 | if (window && window.localStorage) 36 | describe 'Has v0.9.0 clientID and no v0.9.1 clientId', -> 37 | before -> 38 | clearStorage() 39 | window.localStorage.setItem(appStorageKey, JSON.stringify(v090Data)) 40 | mobileAnalyticsClient = new AMA.Client(createConfig()) 41 | it 'should use clientId from global scope', -> 42 | expect(mobileAnalyticsClient.options.clientContext.client.client_id).to.eql(clientId1) 43 | it 'should not have migration clientId', -> 44 | expect(mobileAnalyticsClient.options.globalAttributes.migrationId).to.be.undefined 45 | describe 'Has different v0.9.0 clientID and v0.9.1 clientId', -> 46 | before -> 47 | window.localStorage.set(appStorageKey, JSON.stringify(v090Data)) 48 | localStorage.set(AMA.StorageKeys.CLIENT_ID, clientId2) 49 | mobileAnalyticsClient = new AMA.Client(createConfig()) 50 | it 'should use clientId from global scope', -> 51 | expect(mobileAnalyticsClient.options.clientContext.client.client_id).to.eql(clientId1) 52 | it 'should have migration clientId', -> 53 | expect(mobileAnalyticsClient.options.globalAttributes.migrationId).to.eql(clientId2) 54 | describe 'Has only v0.9.1 clientId', -> 55 | before -> 56 | window.localStorage.setItem(appStorageKey, JSON.stringify(v091Data)) 57 | mobileAnalyticsClient = new AMA.Client(createConfig()) 58 | it 'should use clientId from global scope', -> 59 | expect(mobileAnalyticsClient.options.clientContext.client.client_id).to.eql(clientId2) 60 | it 'should not have migration clientId', -> 61 | expect(mobileAnalyticsClient.options.globalAttributes.migrationId).to.be.undefined 62 | catch 63 | console.log('Migration coffeescripts not running, window is undefined') 64 | 65 | clearStorage = -> 66 | localStorage.clearLocalStorage() 67 | window.localStorage.removeItem(appStorageKey); 68 | window.localStorage.removeItem(AMA.StorageKeys.CLIENT_ID); 69 | 70 | #Need to create a config for each test, because the act of creating a Mobile Analytics Client modifies the config that is passed to it 71 | createConfig = -> 72 | return{ 73 | appTitle : '活跃的应用 (Active App)', 74 | appId : testAppId, 75 | appPackageName : 'com.amazonAMA..console', 76 | appVersionName : '', 77 | appVersionCode : '', 78 | locale : '', 79 | platform: 'android', 80 | storage: localStorage, 81 | globalAttributes: { 82 | context: 'node' 83 | }, 84 | logger: { 85 | log: -> 86 | lastLog = arguments 87 | error: -> 88 | lastError = arguments 89 | } 90 | } --------------------------------------------------------------------------------