├── .github └── dependabot.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── app └── index.html ├── dist ├── GameAnalytics.construct.js ├── GameAnalytics.d.ts ├── GameAnalytics.debug.js ├── GameAnalytics.js ├── GameAnalytics.jspre ├── GameAnalytics.min.js └── GameAnalytics.node.js ├── gulpfile.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── Enums.ts ├── GameAnalytics.ts ├── PublicEnums.ts ├── device │ └── GADevice.ts ├── events │ └── GAEvents.ts ├── http │ └── GAHTTPApi.ts ├── logging │ └── GALogger.ts ├── state │ └── GAState.ts ├── store │ └── GAStore.ts ├── tasks │ └── SdkErrorTask.ts ├── threading │ ├── GAThreading.ts │ ├── PriorityQueue.ts │ └── TimedBlock.ts ├── utilities │ └── GAUtilities.ts └── validators │ └── GAValidator.ts ├── test ├── testEvents.js ├── testGAState.js ├── testUtilities.js └── testValidator.js ├── tsconfig.json └── vendor ├── bundle.min.js ├── cryptojs.d.ts ├── enc-base64-min.js ├── enc-base64.js ├── hmac-sha256-min.js └── hmac-sha256.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: gulp-replace 11 | versions: 12 | - 1.1.0 13 | - dependency-name: karma 14 | versions: 15 | - 6.0.4 16 | - 6.1.0 17 | - 6.1.1 18 | - 6.1.2 19 | - 6.2.0 20 | - 6.3.0 21 | - dependency-name: typescript 22 | versions: 23 | - 4.1.4 24 | - 4.1.5 25 | - 4.2.2 26 | - dependency-name: ws 27 | versions: 28 | - 7.4.3 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.mem 3 | vendor/bundle.js 4 | vendor/bundle.min.js 5 | dist/*.map 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | app 3 | test 4 | vendor 5 | gulpfile.js 6 | karma.conf.js 7 | tsconfig.json 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "12" 4 | 5 | notifications: 6 | slack: 7 | rooms: 8 | - gameanalytics:hDYD7nkOTe3tkrvb68WEpyqw#ask-sdk 9 | on_success: change 10 | on_failure: change 11 | email: 12 | - sdk@gameanalytics.com 13 | 14 | before_install: npm install -g gulp 15 | install: npm install 16 | before_script: gulp debug 17 | 18 | branches: 19 | only: 20 | - develop 21 | - master 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Game Analytics 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/gameanalytics.svg)](https://www.npmjs.com/package/gameanalytics) 2 | [![npm](https://img.shields.io/npm/dt/gameanalytics.svg?label=npm%20downloads)](https://www.npmjs.com/package/gameanalytics) 3 | [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) 4 | 5 | GA-SDK-JAVASCRIPT 6 | ================= 7 | 8 | Official repository for GameAnalytics JavaScript SDK. Written in Typescript. 9 | 10 | Documentation can be found [here](https://gameanalytics.com/docs/javascript-sdk). 11 | 12 | Changelog 13 | --------- 14 | 15 | **4.4.6** 16 | * fixed timestamps for daylight saving time 17 | 18 | **4.4.5** 19 | * fixed enums in typescript decleration file 20 | 21 | **4.4.4** 22 | * changed event uuid field name 23 | 24 | **4.4.3** 25 | * added event uuid to events sent 26 | 27 | **4.4.2** 28 | * added error events to be sent for invalid custom event fields used 29 | * added optional mergeFields argument to event methods to merge with global custom fields instead of overwrite them 30 | 31 | **4.4.1** 32 | * fixed missing custom event fields for when trying to fix missing session end events 33 | 34 | **4.4.0** 35 | * added global custom event fields function to allow to add custom fields to events sent automatically by the SDK 36 | 37 | **4.3.1** 38 | * added functionality to force a new user in a/b testing without having to uninstall app first, simply use custom user id function to set a new user id which hasn't been used yet 39 | 40 | **4.3.0** 41 | * added custom event fields feature 42 | 43 | **4.2.3** 44 | * added flutter to sdk version validator 45 | 46 | **4.2.2** 47 | * fixed addProgressionEvent score bug 48 | 49 | **4.2.1** 50 | * fixed ad event bug 51 | 52 | **4.2.0** 53 | * added before unload listener functions 54 | 55 | **4.1.6** 56 | * removed unused logs 57 | 58 | **4.1.5** 59 | * updated client ts validator 60 | 61 | **4.1.4** 62 | * corrected ad event annotation 63 | 64 | **4.1.3** 65 | * added godot to version validator 66 | 67 | **4.1.2** 68 | * small correction for support for KaiOS 69 | 70 | **4.1.1** 71 | * added support for KaiOS 72 | 73 | **4.1.0** 74 | * added ad event 75 | 76 | **4.0.10** 77 | * fixed bug to not send stored events from previous sessions (offline events or session end events not sent yet) by games on the same domain 78 | * this bug fix can potentially affect metrics slightly so be aware of this as old stored events (offline events and session end events not sent yet) in games will not be sent with this new fix because internal keys for storing events in localstorage have changed now 79 | 80 | **4.0.9** 81 | * added better internal error reporting 82 | 83 | **4.0.8** 84 | * fixed cryptojs bug 85 | 86 | **4.0.7** 87 | * added session_num to init request 88 | 89 | **4.0.6** 90 | * removed gender, facebook and birthyear methods 91 | 92 | **4.0.5** 93 | * A/B testing fixes 94 | 95 | **4.0.4** 96 | * remote configs fixes 97 | 98 | **4.0.3** 99 | * small remote configs fix 100 | 101 | **4.0.2** 102 | * fixed events bug 103 | 104 | **4.0.1** 105 | * small bug fix for http requests 106 | 107 | **4.0.0** 108 | * Remote Config calls have been updated and the old calls have deprecated. Please see GA documentation for the new SDK calls and migration guide 109 | * A/B testing support added 110 | 111 | **3.1.2** 112 | * declaration file fix 113 | 114 | **3.1.1** 115 | * typescript definition file fixed 116 | 117 | **3.1.0** 118 | * aded enable/disable event submission function 119 | 120 | **3.0.3** 121 | * fixed business event validation 122 | 123 | **3.0.2** 124 | * removed manual session handling check for startsession and endsession 125 | 126 | **3.0.1** 127 | * added missing function mappings 128 | 129 | **3.0.0** 130 | * added command center functionality 131 | 132 | **2.1.5** 133 | * fix to getbrowserversion for webviews on ios 134 | 135 | **2.1.4** 136 | * added custom dimensions to design and error events 137 | 138 | **2.1.3** 139 | * fixed not allowing to add events when session is not started 140 | * fixed session length bug 141 | 142 | **2.1.2** 143 | * fixed browser version fetch to support facebook 144 | 145 | **2.1.1** 146 | * fixed null error on property 'running' 147 | 148 | **2.1.0** 149 | * fixed sending events request when no events to send 150 | * added possiblity to change event process interval 151 | 152 | **2.0.1** 153 | * scoped javascript sdk namespace 154 | 155 | **2.0.0** 156 | * changed root namespace from 'ga' to 'gameanalytics' 157 | * it is now possible to async load library on websites to avoid any delays when loading the website (just like it is possible with Google Analytics) 158 | 159 | **1.1.11** 160 | * added 'construct' to version validator 161 | 162 | **1.0.10** 163 | * bug fix for end session when using manual session handling 164 | 165 | **1.0.9** 166 | * bug fix for sending events straight after initializing sdk 167 | 168 | **1.0.8** 169 | * removed debug log messages for release distribution versions 170 | 171 | **1.0.7** 172 | * version validator updated with gamemaker 173 | 174 | **1.0.6** 175 | * small bug fix in validator 176 | 177 | **1.0.5** 178 | * added os version 179 | 180 | **1.0.4** 181 | * bug fix for GAEvents.fixMissingSessionEndEvents 182 | 183 | **1.0.3** 184 | * minor dependency fixes 185 | 186 | **1.0.2** 187 | * enabled to use sdk via npm 188 | 189 | **1.0.1** 190 | * fixed debug log messages to use console.log when console.debug is not available 191 | 192 | **1.0.0** 193 | * bumped to v1.0.0 194 | 195 | **0.1.0** 196 | * initial release 197 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GameAnalytics JavaScript SDK Test 5 | 6 | 13 | 22 | 23 | 24 | 25 | 26 | 61 |

GA JavaScript SDK test app

62 | 63 | 64 | -------------------------------------------------------------------------------- /dist/GameAnalytics.d.ts: -------------------------------------------------------------------------------- 1 | declare module gameanalytics { 2 | enum EGAErrorSeverity { 3 | Undefined = 0, 4 | Debug = 1, 5 | Info = 2, 6 | Warning = 3, 7 | Error = 4, 8 | Critical = 5 9 | } 10 | enum EGAProgressionStatus { 11 | Undefined = 0, 12 | Start = 1, 13 | Complete = 2, 14 | Fail = 3 15 | } 16 | enum EGAResourceFlowType { 17 | Undefined = 0, 18 | Source = 1, 19 | Sink = 2 20 | } 21 | enum EGAAdAction { 22 | Undefined = 0, 23 | Clicked = 1, 24 | Show = 2, 25 | FailedShow = 3, 26 | RewardReceived = 4 27 | } 28 | enum EGAAdError { 29 | Undefined = 0, 30 | Unknown = 1, 31 | Offline = 2, 32 | NoFill = 3, 33 | InternalError = 4, 34 | InvalidRequest = 5, 35 | UnableToPrecache = 6 36 | } 37 | enum EGAAdType { 38 | Undefined = 0, 39 | Video = 1, 40 | RewardedVideo = 2, 41 | Playable = 3, 42 | Interstitial = 4, 43 | OfferWall = 5, 44 | Banner = 6 45 | } 46 | module http { 47 | enum EGAHTTPApiResponse { 48 | NoResponse = 0, 49 | BadResponse = 1, 50 | RequestTimeout = 2, 51 | JsonEncodeFailed = 3, 52 | JsonDecodeFailed = 4, 53 | InternalServerError = 5, 54 | BadRequest = 6, 55 | Unauthorized = 7, 56 | UnknownResponseCode = 8, 57 | Ok = 9, 58 | Created = 10 59 | } 60 | } 61 | module events { 62 | enum EGASdkErrorCategory { 63 | Undefined = 0, 64 | EventValidation = 1, 65 | Database = 2, 66 | Init = 3, 67 | Http = 4, 68 | Json = 5 69 | } 70 | enum EGASdkErrorArea { 71 | Undefined = 0, 72 | BusinessEvent = 1, 73 | ResourceEvent = 2, 74 | ProgressionEvent = 3, 75 | DesignEvent = 4, 76 | ErrorEvent = 5, 77 | InitHttp = 9, 78 | EventsHttp = 10, 79 | ProcessEvents = 11, 80 | AddEventsToStore = 12, 81 | AdEvent = 20 82 | } 83 | enum EGASdkErrorAction { 84 | Undefined = 0, 85 | InvalidCurrency = 1, 86 | InvalidShortString = 2, 87 | InvalidEventPartLength = 3, 88 | InvalidEventPartCharacters = 4, 89 | InvalidStore = 5, 90 | InvalidFlowType = 6, 91 | StringEmptyOrNull = 7, 92 | NotFoundInAvailableCurrencies = 8, 93 | InvalidAmount = 9, 94 | NotFoundInAvailableItemTypes = 10, 95 | WrongProgressionOrder = 11, 96 | InvalidEventIdLength = 12, 97 | InvalidEventIdCharacters = 13, 98 | InvalidProgressionStatus = 15, 99 | InvalidSeverity = 16, 100 | InvalidLongString = 17, 101 | DatabaseTooLarge = 18, 102 | DatabaseOpenOrCreate = 19, 103 | JsonError = 25, 104 | FailHttpJsonDecode = 29, 105 | FailHttpJsonEncode = 30, 106 | InvalidAdAction = 31, 107 | InvalidAdType = 32, 108 | InvalidString = 33 109 | } 110 | enum EGASdkErrorParameter { 111 | Undefined = 0, 112 | Currency = 1, 113 | CartType = 2, 114 | ItemType = 3, 115 | ItemId = 4, 116 | Store = 5, 117 | FlowType = 6, 118 | Amount = 7, 119 | Progression01 = 8, 120 | Progression02 = 9, 121 | Progression03 = 10, 122 | EventId = 11, 123 | ProgressionStatus = 12, 124 | Severity = 13, 125 | Message = 14, 126 | AdAction = 15, 127 | AdType = 16, 128 | AdSdkName = 17, 129 | AdPlacement = 18 130 | } 131 | } 132 | } 133 | export module gameanalytics { 134 | enum EGAErrorSeverity { 135 | Undefined = 0, 136 | Debug = 1, 137 | Info = 2, 138 | Warning = 3, 139 | Error = 4, 140 | Critical = 5 141 | } 142 | enum EGAProgressionStatus { 143 | Undefined = 0, 144 | Start = 1, 145 | Complete = 2, 146 | Fail = 3 147 | } 148 | enum EGAResourceFlowType { 149 | Undefined = 0, 150 | Source = 1, 151 | Sink = 2 152 | } 153 | enum EGAAdAction { 154 | Undefined = 0, 155 | Clicked = 1, 156 | Show = 2, 157 | FailedShow = 3, 158 | RewardReceived = 4 159 | } 160 | enum EGAAdError { 161 | Undefined = 0, 162 | Unknown = 1, 163 | Offline = 2, 164 | NoFill = 3, 165 | InternalError = 4, 166 | InvalidRequest = 5, 167 | UnableToPrecache = 6 168 | } 169 | enum EGAAdType { 170 | Undefined = 0, 171 | Video = 1, 172 | RewardedVideo = 2, 173 | Playable = 3, 174 | Interstitial = 4, 175 | OfferWall = 5, 176 | Banner = 6 177 | } 178 | } 179 | declare module gameanalytics { 180 | module logging { 181 | class GALogger { 182 | private static readonly instance; 183 | private infoLogEnabled; 184 | private infoLogVerboseEnabled; 185 | private static debugEnabled; 186 | private static readonly Tag; 187 | private constructor(); 188 | static setInfoLog(value: boolean): void; 189 | static setVerboseLog(value: boolean): void; 190 | static i(format: string): void; 191 | static w(format: string): void; 192 | static e(format: string): void; 193 | static ii(format: string): void; 194 | static d(format: string): void; 195 | private sendNotificationMessage; 196 | } 197 | } 198 | } 199 | declare module gameanalytics { 200 | module utilities { 201 | class GAUtilities { 202 | static getHmac(key: string, data: string): string; 203 | static stringMatch(s: string, pattern: RegExp): boolean; 204 | static joinStringArray(v: Array, delimiter: string): string; 205 | static stringArrayContainsString(array: Array, search: string): boolean; 206 | private static readonly keyStr; 207 | static encode64(input: string): string; 208 | static decode64(input: string): string; 209 | static timeIntervalSince1970(): number; 210 | static createGuid(): string; 211 | } 212 | } 213 | } 214 | declare module gameanalytics { 215 | module validators { 216 | import EGASdkErrorCategory = gameanalytics.events.EGASdkErrorCategory; 217 | import EGASdkErrorArea = gameanalytics.events.EGASdkErrorArea; 218 | import EGASdkErrorAction = gameanalytics.events.EGASdkErrorAction; 219 | import EGASdkErrorParameter = gameanalytics.events.EGASdkErrorParameter; 220 | class ValidationResult { 221 | category: EGASdkErrorCategory; 222 | area: EGASdkErrorArea; 223 | action: EGASdkErrorAction; 224 | parameter: EGASdkErrorParameter; 225 | reason: string; 226 | constructor(category: EGASdkErrorCategory, area: EGASdkErrorArea, action: EGASdkErrorAction, parameter: EGASdkErrorParameter, reason: string); 227 | } 228 | class GAValidator { 229 | static validateBusinessEvent(currency: string, amount: number, cartType: string, itemType: string, itemId: string): ValidationResult; 230 | static validateResourceEvent(flowType: EGAResourceFlowType, currency: string, amount: number, itemType: string, itemId: string, availableCurrencies: Array, availableItemTypes: Array): ValidationResult; 231 | static validateProgressionEvent(progressionStatus: EGAProgressionStatus, progression01: string, progression02: string, progression03: string): ValidationResult; 232 | static validateDesignEvent(eventId: string): ValidationResult; 233 | static validateErrorEvent(severity: EGAErrorSeverity, message: string): ValidationResult; 234 | static validateAdEvent(adAction: EGAAdAction, adType: EGAAdType, adSdkName: string, adPlacement: string): ValidationResult; 235 | static validateSdkErrorEvent(gameKey: string, gameSecret: string, category: EGASdkErrorCategory, area: EGASdkErrorArea, action: EGASdkErrorAction): boolean; 236 | static validateKeys(gameKey: string, gameSecret: string): boolean; 237 | static validateCurrency(currency: string): boolean; 238 | static validateEventPartLength(eventPart: string, allowNull: boolean): boolean; 239 | static validateEventPartCharacters(eventPart: string): boolean; 240 | static validateEventIdLength(eventId: string): boolean; 241 | static validateEventIdCharacters(eventId: string): boolean; 242 | static validateAndCleanInitRequestResponse(initResponse: { 243 | [key: string]: any; 244 | }, configsCreated: boolean): { 245 | [key: string]: any; 246 | }; 247 | static validateBuild(build: string): boolean; 248 | static validateSdkWrapperVersion(wrapperVersion: string): boolean; 249 | static validateEngineVersion(engineVersion: string): boolean; 250 | static validateUserId(uId: string): boolean; 251 | static validateShortString(shortString: string, canBeEmpty: boolean): boolean; 252 | static validateString(s: string, canBeEmpty: boolean): boolean; 253 | static validateLongString(longString: string, canBeEmpty: boolean): boolean; 254 | static validateConnectionType(connectionType: string): boolean; 255 | static validateCustomDimensions(customDimensions: Array): boolean; 256 | static validateResourceCurrencies(resourceCurrencies: Array): boolean; 257 | static validateResourceItemTypes(resourceItemTypes: Array): boolean; 258 | static validateDimension01(dimension01: string, availableDimensions: Array): boolean; 259 | static validateDimension02(dimension02: string, availableDimensions: Array): boolean; 260 | static validateDimension03(dimension03: string, availableDimensions: Array): boolean; 261 | static validateArrayOfStrings(maxCount: number, maxStringLength: number, allowNoValues: boolean, logTag: string, arrayOfStrings: Array): boolean; 262 | static validateClientTs(clientTs: number): boolean; 263 | } 264 | } 265 | } 266 | declare module gameanalytics { 267 | module device { 268 | class NameValueVersion { 269 | name: string; 270 | value: string; 271 | version: string; 272 | constructor(name: string, value: string, version: string); 273 | } 274 | class NameVersion { 275 | name: string; 276 | version: string; 277 | constructor(name: string, version: string); 278 | } 279 | class GADevice { 280 | private static readonly sdkWrapperVersion; 281 | private static readonly osVersionPair; 282 | static readonly buildPlatform: string; 283 | static readonly deviceModel: string; 284 | static readonly deviceManufacturer: string; 285 | static readonly osVersion: string; 286 | static readonly browserVersion: string; 287 | static sdkGameEngineVersion: string; 288 | static gameEngineVersion: string; 289 | private static connectionType; 290 | static touch(): void; 291 | static getRelevantSdkVersion(): string; 292 | static getConnectionType(): string; 293 | static updateConnectionType(): void; 294 | private static getOSVersionString; 295 | private static runtimePlatformToString; 296 | private static getBrowserVersionString; 297 | private static getDeviceModel; 298 | private static getDeviceManufacturer; 299 | private static matchItem; 300 | } 301 | } 302 | } 303 | declare module gameanalytics { 304 | module threading { 305 | class TimedBlock { 306 | readonly deadline: Date; 307 | block: () => void; 308 | readonly id: number; 309 | ignore: boolean; 310 | async: boolean; 311 | running: boolean; 312 | private static idCounter; 313 | constructor(deadline: Date); 314 | } 315 | } 316 | } 317 | declare module gameanalytics { 318 | module threading { 319 | interface IComparer { 320 | compare(x: T, y: T): number; 321 | } 322 | class PriorityQueue { 323 | _subQueues: { 324 | [key: number]: Array; 325 | }; 326 | _sortedKeys: Array; 327 | private comparer; 328 | constructor(priorityComparer: IComparer); 329 | enqueue(priority: number, item: TItem): void; 330 | private addQueueOfPriority; 331 | peek(): TItem; 332 | hasItems(): boolean; 333 | dequeue(): TItem; 334 | private dequeueFromHighPriorityQueue; 335 | } 336 | } 337 | } 338 | declare module gameanalytics { 339 | module store { 340 | enum EGAStoreArgsOperator { 341 | Equal = 0, 342 | LessOrEqual = 1, 343 | NotEqual = 2 344 | } 345 | enum EGAStore { 346 | Events = 0, 347 | Sessions = 1, 348 | Progression = 2 349 | } 350 | class GAStore { 351 | private static readonly instance; 352 | private static storageAvailable; 353 | private static readonly MaxNumberOfEntries; 354 | private eventsStore; 355 | private sessionsStore; 356 | private progressionStore; 357 | private storeItems; 358 | private static readonly StringFormat; 359 | private static readonly KeyFormat; 360 | private static readonly EventsStoreKey; 361 | private static readonly SessionsStoreKey; 362 | private static readonly ProgressionStoreKey; 363 | private static readonly ItemsStoreKey; 364 | private constructor(); 365 | static isStorageAvailable(): boolean; 366 | static isStoreTooLargeForEvents(): boolean; 367 | static select(store: EGAStore, args?: Array<[string, EGAStoreArgsOperator, any]>, sort?: boolean, maxCount?: number): Array<{ 368 | [key: string]: any; 369 | }>; 370 | static update(store: EGAStore, setArgs: Array<[string, any]>, whereArgs?: Array<[string, EGAStoreArgsOperator, any]>): boolean; 371 | static delete(store: EGAStore, args: Array<[string, EGAStoreArgsOperator, any]>): void; 372 | static insert(store: EGAStore, newEntry: { 373 | [key: string]: any; 374 | }, replace?: boolean, replaceKey?: string): void; 375 | static save(gameKey: string): void; 376 | static load(gameKey: string): void; 377 | static setItem(gameKey: string, key: string, value: string): void; 378 | static getItem(gameKey: string, key: string): string; 379 | private static getStore; 380 | } 381 | } 382 | } 383 | declare module gameanalytics { 384 | module state { 385 | class GAState { 386 | private static readonly CategorySdkError; 387 | private static readonly MAX_CUSTOM_FIELDS_COUNT; 388 | private static readonly MAX_CUSTOM_FIELDS_KEY_LENGTH; 389 | private static readonly MAX_CUSTOM_FIELDS_VALUE_STRING_LENGTH; 390 | static readonly instance: GAState; 391 | private constructor(); 392 | private userId; 393 | static setUserId(userId: string): void; 394 | private identifier; 395 | static getIdentifier(): string; 396 | private initialized; 397 | static isInitialized(): boolean; 398 | static setInitialized(value: boolean): void; 399 | sessionStart: number; 400 | static getSessionStart(): number; 401 | private sessionNum; 402 | static getSessionNum(): number; 403 | isUnloading: boolean; 404 | private transactionNum; 405 | static getTransactionNum(): number; 406 | sessionId: string; 407 | static getSessionId(): string; 408 | private currentCustomDimension01; 409 | static getCurrentCustomDimension01(): string; 410 | private currentCustomDimension02; 411 | static getCurrentCustomDimension02(): string; 412 | private currentCustomDimension03; 413 | static getCurrentCustomDimension03(): string; 414 | private gameKey; 415 | static getGameKey(): string; 416 | private gameSecret; 417 | static getGameSecret(): string; 418 | private availableCustomDimensions01; 419 | static getAvailableCustomDimensions01(): Array; 420 | static setAvailableCustomDimensions01(value: Array): void; 421 | private availableCustomDimensions02; 422 | static getAvailableCustomDimensions02(): Array; 423 | static setAvailableCustomDimensions02(value: Array): void; 424 | private availableCustomDimensions03; 425 | static getAvailableCustomDimensions03(): Array; 426 | static setAvailableCustomDimensions03(value: Array): void; 427 | currentGlobalCustomEventFields: { 428 | [key: string]: any; 429 | }; 430 | private availableResourceCurrencies; 431 | static getAvailableResourceCurrencies(): Array; 432 | static setAvailableResourceCurrencies(value: Array): void; 433 | private availableResourceItemTypes; 434 | static getAvailableResourceItemTypes(): Array; 435 | static setAvailableResourceItemTypes(value: Array): void; 436 | private build; 437 | static getBuild(): string; 438 | static setBuild(value: string): void; 439 | private useManualSessionHandling; 440 | static getUseManualSessionHandling(): boolean; 441 | private _isEventSubmissionEnabled; 442 | static isEventSubmissionEnabled(): boolean; 443 | sdkConfigCached: { 444 | [key: string]: any; 445 | }; 446 | private configurations; 447 | private remoteConfigsIsReady; 448 | private remoteConfigsListeners; 449 | private beforeUnloadListeners; 450 | initAuthorized: boolean; 451 | clientServerTimeOffset: number; 452 | configsHash: string; 453 | abId: string; 454 | static getABTestingId(): string; 455 | abVariantId: string; 456 | static getABTestingVariantId(): string; 457 | private defaultUserId; 458 | private setDefaultId; 459 | static getDefaultId(): string; 460 | sdkConfigDefault: { 461 | [key: string]: string; 462 | }; 463 | sdkConfig: { 464 | [key: string]: any; 465 | }; 466 | static getSdkConfig(): { 467 | [key: string]: any; 468 | }; 469 | private progressionTries; 470 | static readonly DefaultUserIdKey: string; 471 | static readonly SessionNumKey: string; 472 | static readonly TransactionNumKey: string; 473 | private static readonly Dimension01Key; 474 | private static readonly Dimension02Key; 475 | private static readonly Dimension03Key; 476 | static readonly SdkConfigCachedKey: string; 477 | static readonly LastUsedIdentifierKey: string; 478 | static isEnabled(): boolean; 479 | static setCustomDimension01(dimension: string): void; 480 | static setCustomDimension02(dimension: string): void; 481 | static setCustomDimension03(dimension: string): void; 482 | static incrementSessionNum(): void; 483 | static incrementTransactionNum(): void; 484 | static incrementProgressionTries(progression: string): void; 485 | static getProgressionTries(progression: string): number; 486 | static clearProgressionTries(progression: string): void; 487 | static setKeys(gameKey: string, gameSecret: string): void; 488 | static setManualSessionHandling(flag: boolean): void; 489 | static setEnabledEventSubmission(flag: boolean): void; 490 | static getEventAnnotations(): { 491 | [key: string]: any; 492 | }; 493 | static getSdkErrorEventAnnotations(): { 494 | [key: string]: any; 495 | }; 496 | static getInitAnnotations(): { 497 | [key: string]: any; 498 | }; 499 | static getClientTsAdjusted(): number; 500 | static sessionIsStarted(): boolean; 501 | private static cacheIdentifier; 502 | static ensurePersistedStates(): void; 503 | static calculateServerTimeOffset(serverTs: number): number; 504 | private static formatString; 505 | static validateAndCleanCustomFields(fields: { 506 | [id: string]: any; 507 | }, errorCallback?: (baseMessage: string, message: string) => void): { 508 | [id: string]: any; 509 | }; 510 | static validateAndFixCurrentDimensions(): void; 511 | static getConfigurationStringValue(key: string, defaultValue: string): string; 512 | static isRemoteConfigsReady(): boolean; 513 | static addRemoteConfigsListener(listener: { 514 | onRemoteConfigsUpdated: () => void; 515 | }): void; 516 | static removeRemoteConfigsListener(listener: { 517 | onRemoteConfigsUpdated: () => void; 518 | }): void; 519 | static getRemoteConfigsContentAsString(): string; 520 | static populateConfigurations(sdkConfig: { 521 | [key: string]: any; 522 | }): void; 523 | static addOnBeforeUnloadListener(listener: { 524 | onBeforeUnload: () => void; 525 | }): void; 526 | static removeOnBeforeUnloadListener(listener: { 527 | onBeforeUnload: () => void; 528 | }): void; 529 | static notifyBeforeUnloadListeners(): void; 530 | } 531 | } 532 | } 533 | declare module gameanalytics { 534 | module tasks { 535 | class SdkErrorTask { 536 | private static readonly MaxCount; 537 | private static readonly countMap; 538 | private static readonly timestampMap; 539 | static execute(url: string, type: string, payloadData: string, secretKey: string): void; 540 | } 541 | } 542 | } 543 | declare module gameanalytics { 544 | module http { 545 | import EGASdkErrorCategory = gameanalytics.events.EGASdkErrorCategory; 546 | import EGASdkErrorArea = gameanalytics.events.EGASdkErrorArea; 547 | import EGASdkErrorAction = gameanalytics.events.EGASdkErrorAction; 548 | import EGASdkErrorParameter = gameanalytics.events.EGASdkErrorParameter; 549 | class GAHTTPApi { 550 | static readonly instance: GAHTTPApi; 551 | private protocol; 552 | private hostName; 553 | private version; 554 | private remoteConfigsVersion; 555 | private baseUrl; 556 | private remoteConfigsBaseUrl; 557 | private initializeUrlPath; 558 | private eventsUrlPath; 559 | private useGzip; 560 | private static readonly MAX_ERROR_MESSAGE_LENGTH; 561 | private constructor(); 562 | requestInit(configsHash: string, callback: (response: EGAHTTPApiResponse, json: { 563 | [key: string]: any; 564 | }) => void): void; 565 | sendEventsInArray(eventArray: Array<{ 566 | [key: string]: any; 567 | }>, requestId: string, callback: (response: EGAHTTPApiResponse, json: { 568 | [key: string]: any; 569 | }, requestId: string, eventCount: number) => void): void; 570 | sendSdkErrorEvent(category: EGASdkErrorCategory, area: EGASdkErrorArea, action: EGASdkErrorAction, parameter: EGASdkErrorParameter, reason: string, gameKey: string, secretKey: string): void; 571 | private static sendEventInArrayRequestCallback; 572 | private static sendRequest; 573 | private static initRequestCallback; 574 | private createPayloadData; 575 | private processRequestResponse; 576 | private static sdkErrorCategoryString; 577 | private static sdkErrorAreaString; 578 | private static sdkErrorActionString; 579 | private static sdkErrorParameterString; 580 | } 581 | } 582 | } 583 | declare module gameanalytics { 584 | module events { 585 | class GAEvents { 586 | private static readonly CategorySessionStart; 587 | private static readonly CategorySessionEnd; 588 | private static readonly CategoryDesign; 589 | private static readonly CategoryBusiness; 590 | private static readonly CategoryProgression; 591 | private static readonly CategoryResource; 592 | private static readonly CategoryError; 593 | private static readonly CategoryAds; 594 | private static readonly MaxEventCount; 595 | private static readonly MAX_ERROR_COUNT; 596 | private static readonly countMap; 597 | private static readonly timestampMap; 598 | private constructor(); 599 | private static customEventFieldsErrorCallback; 600 | static addSessionStartEvent(): void; 601 | static addSessionEndEvent(): void; 602 | static addBusinessEvent(currency: string, amount: number, itemType: string, itemId: string, cartType: string, fields: { 603 | [id: string]: any; 604 | }, mergeFields: boolean): void; 605 | static addResourceEvent(flowType: EGAResourceFlowType, currency: string, amount: number, itemType: string, itemId: string, fields: { 606 | [id: string]: any; 607 | }, mergeFields: boolean): void; 608 | static addProgressionEvent(progressionStatus: EGAProgressionStatus, progression01: string, progression02: string, progression03: string, score: number, sendScore: boolean, fields: { 609 | [id: string]: any; 610 | }, mergeFields: boolean): void; 611 | static addDesignEvent(eventId: string, value: number, sendValue: boolean, fields: { 612 | [id: string]: any; 613 | }, mergeFields: boolean): void; 614 | static addErrorEvent(severity: EGAErrorSeverity, message: string, fields: { 615 | [id: string]: any; 616 | }, mergeFields: boolean, skipAddingFields?: boolean): void; 617 | static addAdEvent(adAction: EGAAdAction, adType: EGAAdType, adSdkName: string, adPlacement: string, noAdReason: EGAAdError, duration: number, sendDuration: boolean, fields: { 618 | [id: string]: any; 619 | }, mergeFields: boolean): void; 620 | static processEvents(category: string, performCleanUp: boolean): void; 621 | private static processEventsCallback; 622 | private static cleanupEvents; 623 | private static fixMissingSessionEndEvents; 624 | private static addEventToStore; 625 | private static updateSessionStore; 626 | private static addDimensionsToEvent; 627 | private static addCustomFieldsToEvent; 628 | private static resourceFlowTypeToString; 629 | private static progressionStatusToString; 630 | private static errorSeverityToString; 631 | private static adActionToString; 632 | private static adErrorToString; 633 | private static adTypeToString; 634 | } 635 | } 636 | } 637 | declare module gameanalytics { 638 | module threading { 639 | class GAThreading { 640 | private static readonly instance; 641 | readonly blocks: PriorityQueue; 642 | private readonly id2TimedBlockMap; 643 | private static runTimeoutId; 644 | private static readonly ThreadWaitTimeInMs; 645 | private static ProcessEventsIntervalInSeconds; 646 | private keepRunning; 647 | private isRunning; 648 | private constructor(); 649 | static createTimedBlock(delayInSeconds?: number): TimedBlock; 650 | static performTaskOnGAThread(taskBlock: () => void, delayInSeconds?: number): void; 651 | static performTimedBlockOnGAThread(timedBlock: TimedBlock): void; 652 | static scheduleTimer(interval: number, callback: () => void): number; 653 | static getTimedBlockById(blockIdentifier: number): TimedBlock; 654 | static ensureEventQueueIsRunning(): void; 655 | static endSessionAndStopQueue(): void; 656 | static stopEventQueue(): void; 657 | static ignoreTimer(blockIdentifier: number): void; 658 | static setEventProcessInterval(interval: number): void; 659 | private addTimedBlock; 660 | private static run; 661 | private static startThread; 662 | private static getNextBlock; 663 | private static processEventQueue; 664 | } 665 | } 666 | } 667 | declare module gameanalytics { 668 | class GameAnalytics { 669 | private static initTimedBlockId; 670 | static methodMap: { 671 | [id: string]: (...args: any[]) => void; 672 | }; 673 | private static getGlobalObject; 674 | static init(): void; 675 | static gaCommand(...args: any[]): void; 676 | static configureAvailableCustomDimensions01(customDimensions?: Array): void; 677 | static configureAvailableCustomDimensions02(customDimensions?: Array): void; 678 | static configureAvailableCustomDimensions03(customDimensions?: Array): void; 679 | static configureAvailableResourceCurrencies(resourceCurrencies?: Array): void; 680 | static configureAvailableResourceItemTypes(resourceItemTypes?: Array): void; 681 | static configureBuild(build?: string): void; 682 | static configureSdkGameEngineVersion(sdkGameEngineVersion?: string): void; 683 | static configureGameEngineVersion(gameEngineVersion?: string): void; 684 | static configureUserId(uId?: string): void; 685 | static initialize(gameKey?: string, gameSecret?: string): void; 686 | static addBusinessEvent(currency?: string, amount?: number, itemType?: string, itemId?: string, cartType?: string, customFields?: { 687 | [id: string]: any; 688 | }, mergeFields?: boolean): void; 689 | static addResourceEvent(flowType?: EGAResourceFlowType, currency?: string, amount?: number, itemType?: string, itemId?: string, customFields?: { 690 | [id: string]: any; 691 | }, mergeFields?: boolean): void; 692 | static addProgressionEvent(progressionStatus?: EGAProgressionStatus, progression01?: string, progression02?: string, progression03?: string, score?: number, customFields?: { 693 | [id: string]: any; 694 | }, mergeFields?: boolean): void; 695 | static addDesignEvent(eventId: string, value?: number, customFields?: { 696 | [id: string]: any; 697 | }, mergeFields?: boolean): void; 698 | static addErrorEvent(severity?: EGAErrorSeverity, message?: string, customFields?: { 699 | [id: string]: any; 700 | }, mergeFields?: boolean): void; 701 | static addAdEventWithNoAdReason(adAction?: EGAAdAction, adType?: EGAAdType, adSdkName?: string, adPlacement?: string, noAdReason?: EGAAdError, customFields?: { 702 | [id: string]: any; 703 | }, mergeFields?: boolean): void; 704 | static addAdEventWithDuration(adAction?: EGAAdAction, adType?: EGAAdType, adSdkName?: string, adPlacement?: string, duration?: number, customFields?: { 705 | [id: string]: any; 706 | }, mergeFields?: boolean): void; 707 | static addAdEvent(adAction?: EGAAdAction, adType?: EGAAdType, adSdkName?: string, adPlacement?: string, customFields?: { 708 | [id: string]: any; 709 | }, mergeFields?: boolean): void; 710 | static setEnabledInfoLog(flag?: boolean): void; 711 | static setEnabledVerboseLog(flag?: boolean): void; 712 | static setEnabledManualSessionHandling(flag?: boolean): void; 713 | static setEnabledEventSubmission(flag?: boolean): void; 714 | static setCustomDimension01(dimension?: string): void; 715 | static setCustomDimension02(dimension?: string): void; 716 | static setCustomDimension03(dimension?: string): void; 717 | static setGlobalCustomEventFields(customFields?: { 718 | [id: string]: any; 719 | }): void; 720 | static setEventProcessInterval(intervalInSeconds: number): void; 721 | static startSession(): void; 722 | static endSession(): void; 723 | static onStop(): void; 724 | static onResume(): void; 725 | static getRemoteConfigsValueAsString(key: string, defaultValue?: string): string; 726 | static isRemoteConfigsReady(): boolean; 727 | static addRemoteConfigsListener(listener: { 728 | onRemoteConfigsUpdated: () => void; 729 | }): void; 730 | static removeRemoteConfigsListener(listener: { 731 | onRemoteConfigsUpdated: () => void; 732 | }): void; 733 | static getRemoteConfigsContentAsString(): string; 734 | static getABTestingId(): string; 735 | static getABTestingVariantId(): string; 736 | static addOnBeforeUnloadListener(listener: { 737 | onBeforeUnload: () => void; 738 | }): void; 739 | static removeOnBeforeUnloadListener(listener: { 740 | onBeforeUnload: () => void; 741 | }): void; 742 | private static internalInitialize; 743 | private static newSession; 744 | private static startNewSessionCallback; 745 | private static resumeSessionAndStartQueue; 746 | private static isSdkReady; 747 | } 748 | } 749 | declare var GameAnalyticsCommand: typeof gameanalytics.GameAnalytics.gaCommand; 750 | export declare var GameAnalytics: typeof gameanalytics.GameAnalytics; 751 | export default GameAnalytics; 752 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var ts = require('gulp-typescript'); 3 | var sourcemaps = require('gulp-sourcemaps'); 4 | var uglify = require('gulp-uglify'); 5 | var Server = require('karma').Server; 6 | var argv = require('yargs').argv; 7 | var gulpif = require('gulp-if'); 8 | var tsProject = ts.createProject('tsconfig.json'); 9 | var tsProjectMini = ts.createProject('tsconfig.json', { outFile: "./dist/GameAnalytics.min.js" }); 10 | var tsProjectDebug = ts.createProject('tsconfig.json', { outFile: "./dist/GameAnalytics.debug.js" }); 11 | var tsDeclaration = ts.createProject('tsconfig.json'); 12 | var replace = require('gulp-replace'); 13 | var concat = require('gulp-concat'); 14 | var insert = require('gulp-insert'); 15 | 16 | gulp.task('build_debug', function() { 17 | var tsResult = tsProjectDebug.src() 18 | .pipe(sourcemaps.init()) 19 | .pipe(tsProjectDebug()); 20 | 21 | return tsResult.js 22 | .pipe(sourcemaps.write()) 23 | .pipe(gulp.dest('.')); 24 | }); 25 | 26 | gulp.task('declaration', function() { 27 | var tsResult = tsDeclaration.src() 28 | .pipe(tsDeclaration()); 29 | 30 | return tsResult.dts 31 | .pipe(replace('declare module public_enums', 'export module gameanalytics')) 32 | .pipe(replace('declare var GameAnalytics', 'declare var GameAnalyticsCommand')) 33 | .pipe(insert.wrap("", "export declare var GameAnalytics: typeof gameanalytics.GameAnalytics;\n")) 34 | .pipe(insert.wrap("", "export default GameAnalytics;\n")) 35 | .pipe(gulp.dest('.')); 36 | }); 37 | 38 | gulp.task('bundle_min_js', function() { 39 | return gulp.src(['./vendor/hmac-sha256-min.js', './vendor/enc-base64-min.js']) 40 | .pipe(concat('bundle.min.js')) 41 | .pipe(gulp.dest('./vendor')); 42 | }); 43 | 44 | gulp.task('bundle_js', function() { 45 | return gulp.src(['./vendor/hmac-sha256.js', './vendor/enc-base64.js']) 46 | .pipe(concat('bundle.js')) 47 | .pipe(gulp.dest('./vendor')); 48 | }); 49 | 50 | gulp.task('test', function (done) { 51 | new Server({ 52 | configFile: __dirname + '/karma.conf.js', 53 | singleRun: true 54 | }, done).start(); 55 | }); 56 | 57 | gulp.task('build_mini', function() { 58 | var tsResult = tsProjectMini.src() 59 | .pipe(replace('GALogger.debugEnabled = true', 'GALogger.debugEnabled = false')) 60 | .pipe(replace('GALogger.d(', '//GALogger.d(')) 61 | .pipe(gulpif(argv.nologging, replace('GALogger.', '//GALogger.'))) 62 | .pipe(gulpif(argv.nologging, replace('//GALOGGER_START', '/*GALOGGER_START'))) 63 | .pipe(gulpif(argv.nologging, replace('//GALOGGER_END', '//GALOGGER_END*/'))) 64 | .pipe(gulpif(argv.nologging, replace('import GALogger = gameanalytics.logging.GALogger', '//import GALogger = gameanalytics.logging.GALogger'))) 65 | .pipe(tsProjectMini()); 66 | 67 | return tsResult.js 68 | .pipe(gulp.dest('.')); 69 | }); 70 | 71 | gulp.task('build_normal', function() { 72 | var tsResult = tsProject.src() 73 | .pipe(replace('GALogger.debugEnabled = true', 'GALogger.debugEnabled = false')) 74 | .pipe(replace('GALogger.d(', '//GALogger.d(')) 75 | .pipe(tsProject()); 76 | 77 | return tsResult.js 78 | .pipe(gulp.dest('.')); 79 | }); 80 | 81 | var mini = function() { 82 | return gulp.src(['./vendor/bundle.min.js', './dist/GameAnalytics.min.js']) 83 | .pipe(concat('GameAnalytics.min.js')) 84 | .pipe(uglify()) 85 | .pipe(insert.wrap("(function(scope){\n", "\nscope.gameanalytics=gameanalytics;\nscope.GameAnalytics=GameAnalytics;\n})(this);\n")) 86 | .pipe(gulp.dest('./dist')); 87 | }; 88 | gulp.task('mini', gulp.series(gulp.parallel('bundle_min_js', 'build_mini'), mini)); 89 | 90 | var normal = function() { 91 | return gulp.src(['./vendor/bundle.min.js', './dist/GameAnalytics.js']) 92 | .pipe(concat('GameAnalytics.js')) 93 | .pipe(uglify()) 94 | .pipe(insert.wrap("(function(scope){\n", "\nscope.gameanalytics=gameanalytics;\nscope.GameAnalytics=GameAnalytics;\n})(this);\n")) 95 | .pipe(gulp.dest('./dist')); 96 | }; 97 | gulp.task('normal', gulp.series(gulp.parallel('bundle_min_js', 'build_normal'), normal)); 98 | 99 | var unity = function() { 100 | return gulp.src(['./vendor/bundle.js', './dist/GameAnalytics.js']) 101 | .pipe(concat('GameAnalytics.jspre')) 102 | .pipe(gulp.dest('./dist')); 103 | }; 104 | gulp.task('unity', gulp.series(gulp.parallel('bundle_js', 'build_normal'), unity)); 105 | 106 | var ga_node = function() { 107 | return gulp.src(['./vendor/bundle.js', './dist/GameAnalytics.js']) 108 | .pipe(concat('GameAnalytics.node.js')) 109 | .pipe(insert.wrap("'use strict';\n", "module.exports = gameanalytics;")) 110 | .pipe(gulp.dest('./dist')); 111 | }; 112 | gulp.task('ga_node', gulp.series(gulp.parallel('bundle_js', 'build_normal'), ga_node)); 113 | 114 | var construct = function () { 115 | return gulp.src(['./vendor/bundle.js', './dist/GameAnalytics.js']) 116 | .pipe(concat('GameAnalytics.construct.js')) 117 | .pipe(insert.wrap("'use strict';\n", "globalThis.gameanalytics = gameanalytics;")) 118 | .pipe(gulp.dest('./dist')); 119 | }; 120 | gulp.task('construct', gulp.series(gulp.parallel('bundle_js', 'build_normal'), construct)); 121 | 122 | var debug = function() { 123 | return gulp.src(['./vendor/bundle.min.js', './dist/GameAnalytics.debug.js']) 124 | .pipe(concat('GameAnalytics.debug.js')) 125 | .pipe(insert.wrap("(function(scope){\n", "\nscope.gameanalytics=gameanalytics;\nscope.GameAnalytics=GameAnalytics;\n})(this);\n")) 126 | .pipe(gulp.dest('./dist')); 127 | }; 128 | gulp.task('debug', gulp.series(gulp.parallel('bundle_min_js', 'build_debug'), debug)); 129 | 130 | gulp.task('default', gulp.series('debug', 'mini', 'unity', 'ga_node', 'construct', 'normal', 'declaration')); 131 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | browsers: ['ChromeHeadless'], 4 | frameworks: ['jasmine'], 5 | files: [ 6 | 'dist/*.js', 7 | 'test/*.js' 8 | ], 9 | exclude: [ 10 | 'dist/*.min.js', 11 | 'dist/GameAnalytics.js', 12 | 'dist/GameAnalytics.node.js' 13 | ], 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gameanalytics", 3 | "author": "GameAnalytics", 4 | "version": "4.4.6", 5 | "description": "Official JavaScript SDK for GameAnalytics. GameAnalytics is a free analytics platform that helps game developers understand their players' behaviour by delivering relevant insights.", 6 | "keywords": [ 7 | "GameAnalytics", 8 | "JavaScript" 9 | ], 10 | "main": "./dist/GameAnalytics.node.js", 11 | "types": "./dist/GameAnalytics.d.ts", 12 | "contributors": [ 13 | { 14 | "name": "Martin Treacy-Schwartz", 15 | "email": "mts@gameanalytics.com" 16 | } 17 | ], 18 | "license": "MIT", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/GameAnalytics/GA-SDK-JAVASCRIPT" 22 | }, 23 | "homepage": "http://www.gameanalytics.com/", 24 | "devDependencies": { 25 | "ansi-regex": "^6.0.1", 26 | "copy-props": "^3.0.1", 27 | "engine.io": "^6.1.3", 28 | "follow-redirects": "^1.14.9", 29 | "glob-parent": "^6.0.2", 30 | "gulp": "^4.0.2", 31 | "gulp-concat": "^2.6.1", 32 | "gulp-if": "^3.0.0", 33 | "gulp-insert": "^0.5.0", 34 | "gulp-replace": "^1.0.0", 35 | "gulp-sourcemaps": "^3.0.0", 36 | "gulp-typescript": "^5.0.1", 37 | "gulp-uglify": "^3.0.2", 38 | "ini": "^3.0.0", 39 | "jasmine": "^4.0.2", 40 | "karma": "^6.3.17", 41 | "karma-chrome-launcher": "^3.1.0", 42 | "karma-jasmine": "^5.0.1", 43 | "kind-of": "^6.0.3", 44 | "lodash": "^4.17.21", 45 | "lodash.template": "^4.5.0", 46 | "log4js": "^6.4.2", 47 | "minimatch": "^5.0.1", 48 | "minimist": "^1.2.3", 49 | "mixin-deep": "^2.0.1", 50 | "nanoid": "^4.0.0", 51 | "path-parse": "^1.0.7", 52 | "postcss": "^8.4.7", 53 | "set-value": "^4.1.0", 54 | "tar": "^6.1.11", 55 | "typescript": "^4.6.2", 56 | "ws": "^8.5.0", 57 | "yargs": "^17.3.0", 58 | "yargs-parser": "^21.0.1" 59 | }, 60 | "scripts": { 61 | "test": "gulp test" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Enums.ts: -------------------------------------------------------------------------------- 1 | module gameanalytics 2 | { 3 | export enum EGAErrorSeverity { 4 | Undefined = 0, 5 | Debug = 1, 6 | Info = 2, 7 | Warning = 3, 8 | Error = 4, 9 | Critical = 5 10 | } 11 | 12 | export enum EGAProgressionStatus { 13 | Undefined = 0, 14 | Start = 1, 15 | Complete = 2, 16 | Fail = 3 17 | } 18 | 19 | export enum EGAResourceFlowType { 20 | Undefined = 0, 21 | Source = 1, 22 | Sink = 2 23 | } 24 | 25 | export enum EGAAdAction { 26 | Undefined = 0, 27 | Clicked = 1, 28 | Show = 2, 29 | FailedShow = 3, 30 | RewardReceived = 4 31 | } 32 | 33 | export enum EGAAdError { 34 | Undefined = 0, 35 | Unknown = 1, 36 | Offline = 2, 37 | NoFill = 3, 38 | InternalError = 4, 39 | InvalidRequest = 5, 40 | UnableToPrecache = 6 41 | } 42 | 43 | export enum EGAAdType { 44 | Undefined = 0, 45 | Video = 1, 46 | RewardedVideo = 2, 47 | Playable = 3, 48 | Interstitial = 4, 49 | OfferWall = 5, 50 | Banner = 6 51 | } 52 | 53 | export module http 54 | { 55 | export enum EGAHTTPApiResponse 56 | { 57 | // client 58 | NoResponse, 59 | BadResponse, 60 | RequestTimeout, // 408 61 | JsonEncodeFailed, 62 | JsonDecodeFailed, 63 | // server 64 | InternalServerError, 65 | BadRequest, // 400 66 | Unauthorized, // 401 67 | UnknownResponseCode, 68 | Ok, 69 | Created 70 | } 71 | } 72 | 73 | export module events 74 | { 75 | export enum EGASdkErrorCategory 76 | { 77 | Undefined = 0, 78 | EventValidation = 1, 79 | Database = 2, 80 | Init = 3, 81 | Http = 4, 82 | Json = 5 83 | } 84 | 85 | export enum EGASdkErrorArea 86 | { 87 | Undefined = 0, 88 | BusinessEvent = 1, 89 | ResourceEvent = 2, 90 | ProgressionEvent = 3, 91 | DesignEvent = 4, 92 | ErrorEvent = 5, 93 | InitHttp = 9, 94 | EventsHttp = 10, 95 | ProcessEvents = 11, 96 | AddEventsToStore = 12, 97 | AdEvent = 20 98 | } 99 | 100 | export enum EGASdkErrorAction 101 | { 102 | Undefined = 0, 103 | InvalidCurrency = 1, 104 | InvalidShortString = 2, 105 | InvalidEventPartLength = 3, 106 | InvalidEventPartCharacters = 4, 107 | InvalidStore = 5, 108 | InvalidFlowType = 6, 109 | StringEmptyOrNull = 7, 110 | NotFoundInAvailableCurrencies = 8, 111 | InvalidAmount = 9, 112 | NotFoundInAvailableItemTypes = 10, 113 | WrongProgressionOrder = 11, 114 | InvalidEventIdLength = 12, 115 | InvalidEventIdCharacters = 13, 116 | InvalidProgressionStatus = 15, 117 | InvalidSeverity = 16, 118 | InvalidLongString = 17, 119 | DatabaseTooLarge = 18, 120 | DatabaseOpenOrCreate = 19, 121 | JsonError = 25, 122 | FailHttpJsonDecode = 29, 123 | FailHttpJsonEncode = 30, 124 | InvalidAdAction = 31, 125 | InvalidAdType = 32, 126 | InvalidString = 33 127 | } 128 | 129 | export enum EGASdkErrorParameter 130 | { 131 | Undefined = 0, 132 | Currency = 1, 133 | CartType = 2, 134 | ItemType = 3, 135 | ItemId = 4, 136 | Store = 5, 137 | FlowType = 6, 138 | Amount = 7, 139 | Progression01 = 8, 140 | Progression02 = 9, 141 | Progression03 = 10, 142 | EventId = 11, 143 | ProgressionStatus = 12, 144 | Severity = 13, 145 | Message = 14, 146 | AdAction = 15, 147 | AdType = 16, 148 | AdSdkName = 17, 149 | AdPlacement = 18 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/PublicEnums.ts: -------------------------------------------------------------------------------- 1 | module public_enums 2 | { 3 | export enum EGAErrorSeverity 4 | { 5 | Undefined = 0, 6 | Debug = 1, 7 | Info = 2, 8 | Warning = 3, 9 | Error = 4, 10 | Critical = 5 11 | } 12 | 13 | export enum EGAProgressionStatus 14 | { 15 | Undefined = 0, 16 | Start = 1, 17 | Complete = 2, 18 | Fail = 3 19 | } 20 | 21 | export enum EGAResourceFlowType 22 | { 23 | Undefined = 0, 24 | Source = 1, 25 | Sink = 2 26 | } 27 | 28 | export enum EGAAdAction 29 | { 30 | Undefined = 0, 31 | Clicked = 1, 32 | Show = 2, 33 | FailedShow = 3, 34 | RewardReceived = 4 35 | } 36 | 37 | export enum EGAAdError 38 | { 39 | Undefined = 0, 40 | Unknown = 1, 41 | Offline = 2, 42 | NoFill = 3, 43 | InternalError = 4, 44 | InvalidRequest = 5, 45 | UnableToPrecache = 6 46 | } 47 | 48 | export enum EGAAdType 49 | { 50 | Undefined = 0, 51 | Video = 1, 52 | RewardedVideo = 2, 53 | Playable = 3, 54 | Interstitial = 4, 55 | OfferWall = 5, 56 | Banner = 6 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/device/GADevice.ts: -------------------------------------------------------------------------------- 1 | module gameanalytics 2 | { 3 | export module device 4 | { 5 | export class NameValueVersion 6 | { 7 | public name:string; 8 | public value:string; 9 | public version:string; 10 | 11 | public constructor(name:string, value:string, version:string) 12 | { 13 | this.name = name; 14 | this.value = value; 15 | this.version = version; 16 | } 17 | } 18 | 19 | export class NameVersion 20 | { 21 | public name:string; 22 | public version:string; 23 | 24 | public constructor(name:string, version:string) 25 | { 26 | this.name = name; 27 | this.version = version; 28 | } 29 | } 30 | 31 | export class GADevice 32 | { 33 | private static readonly sdkWrapperVersion:string = "javascript 4.4.6"; 34 | private static readonly osVersionPair:NameVersion = GADevice.matchItem([ 35 | navigator.platform, 36 | navigator.userAgent, 37 | navigator.appVersion, 38 | navigator.vendor 39 | ].join(' '), [ 40 | new NameValueVersion("windows_phone", "Windows Phone", "OS"), 41 | new NameValueVersion("windows", "Win", "NT"), 42 | new NameValueVersion("ios", "iPhone", "OS"), 43 | new NameValueVersion("ios", "iPad", "OS"), 44 | new NameValueVersion("ios", "iPod", "OS"), 45 | new NameValueVersion("android", "Android", "Android"), 46 | new NameValueVersion("blackBerry", "BlackBerry", "/"), 47 | new NameValueVersion("mac_osx", "Mac", "OS X"), 48 | new NameValueVersion("tizen", "Tizen", "Tizen"), 49 | new NameValueVersion("linux", "Linux", "rv"), 50 | new NameValueVersion("kai_os", "KAIOS", "KAIOS") 51 | ]); 52 | 53 | public static readonly buildPlatform:string = GADevice.runtimePlatformToString(); 54 | public static readonly deviceModel:string = GADevice.getDeviceModel(); 55 | public static readonly deviceManufacturer:string = GADevice.getDeviceManufacturer(); 56 | public static readonly osVersion:string = GADevice.getOSVersionString(); 57 | public static readonly browserVersion:string = GADevice.getBrowserVersionString(); 58 | 59 | public static sdkGameEngineVersion:string; 60 | public static gameEngineVersion:string; 61 | private static connectionType:string; 62 | public static touch(): void 63 | { 64 | } 65 | 66 | public static getRelevantSdkVersion(): string 67 | { 68 | if(GADevice.sdkGameEngineVersion) 69 | { 70 | return GADevice.sdkGameEngineVersion; 71 | } 72 | return GADevice.sdkWrapperVersion; 73 | } 74 | 75 | public static getConnectionType(): string 76 | { 77 | return GADevice.connectionType; 78 | } 79 | 80 | public static updateConnectionType(): void 81 | { 82 | if(navigator.onLine) 83 | { 84 | if(GADevice.buildPlatform === "ios" || GADevice.buildPlatform === "android") 85 | { 86 | GADevice.connectionType = "wwan"; 87 | } 88 | else 89 | { 90 | GADevice.connectionType = "lan"; 91 | } 92 | // TODO: Detect wifi usage 93 | } 94 | else 95 | { 96 | GADevice.connectionType = "offline"; 97 | } 98 | } 99 | 100 | private static getOSVersionString(): string 101 | { 102 | return GADevice.buildPlatform + " " + GADevice.osVersionPair.version; 103 | } 104 | 105 | private static runtimePlatformToString(): string 106 | { 107 | return GADevice.osVersionPair.name; 108 | } 109 | 110 | private static getBrowserVersionString(): string 111 | { 112 | var ua:string = navigator.userAgent; 113 | var tem:RegExpMatchArray; 114 | var M:RegExpMatchArray = ua.match(/(opera|chrome|safari|firefox|ubrowser|msie|trident|fbav(?=\/))\/?\s*(\d+)/i) || []; 115 | 116 | if(M.length == 0) 117 | { 118 | if(GADevice.buildPlatform === "ios") 119 | { 120 | return "webkit_" + GADevice.osVersion; 121 | } 122 | } 123 | 124 | if(/trident/i.test(M[1])) 125 | { 126 | tem = /\brv[ :]+(\d+)/g.exec(ua) || []; 127 | return 'IE ' + (tem[1] || ''); 128 | } 129 | 130 | if(M[1] === 'Chrome') 131 | { 132 | tem = ua.match(/\b(OPR|Edge|UBrowser)\/(\d+)/); 133 | if(tem!= null) 134 | { 135 | return tem.slice(1).join(' ').replace('OPR', 'Opera').replace('UBrowser', 'UC').toLowerCase(); 136 | } 137 | } 138 | 139 | if(M[1] && M[1].toLowerCase() === 'fbav') 140 | { 141 | M[1] = "facebook"; 142 | 143 | if(M[2]) 144 | { 145 | return "facebook " + M[2]; 146 | } 147 | } 148 | 149 | var MString:string[] = M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?']; 150 | 151 | if((tem = ua.match(/version\/(\d+)/i)) != null) 152 | { 153 | MString.splice(1, 1, tem[1]); 154 | } 155 | 156 | return MString.join(' ').toLowerCase(); 157 | } 158 | 159 | private static getDeviceModel():string 160 | { 161 | var result:string = "unknown"; 162 | 163 | return result; 164 | } 165 | 166 | private static getDeviceManufacturer():string 167 | { 168 | var result:string = "unknown"; 169 | 170 | return result; 171 | } 172 | 173 | private static matchItem(agent:string, data:Array):NameVersion 174 | { 175 | var result:NameVersion = new NameVersion("unknown", "0.0.0"); 176 | 177 | var i:number = 0; 178 | var j:number = 0; 179 | var regex:RegExp; 180 | var regexv:RegExp; 181 | var match:boolean; 182 | var matches:RegExpMatchArray; 183 | var mathcesResult:string; 184 | var version:string; 185 | 186 | for (i = 0; i < data.length; i += 1) 187 | { 188 | regex = new RegExp(data[i].value, 'i'); 189 | match = regex.test(agent); 190 | if (match) 191 | { 192 | regexv = new RegExp(data[i].version + '[- /:;]([\\d._]+)', 'i'); 193 | matches = agent.match(regexv); 194 | version = ''; 195 | if (matches) 196 | { 197 | if (matches[1]) 198 | { 199 | mathcesResult = matches[1]; 200 | } 201 | } 202 | if (mathcesResult) 203 | { 204 | var matchesArray:string[] = mathcesResult.split(/[._]+/); 205 | for (j = 0; j < Math.min(matchesArray.length, 3); j += 1) 206 | { 207 | version += matchesArray[j] + (j < Math.min(matchesArray.length, 3) - 1 ? '.' : ''); 208 | } 209 | } 210 | else 211 | { 212 | version = '0.0.0'; 213 | } 214 | 215 | result.name = data[i].name; 216 | result.version = version; 217 | 218 | return result; 219 |         } 220 |     } 221 | 222 | return result; 223 | } 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/http/GAHTTPApi.ts: -------------------------------------------------------------------------------- 1 | module gameanalytics 2 | { 3 | export module http 4 | { 5 | import GAState = gameanalytics.state.GAState; 6 | import GALogger = gameanalytics.logging.GALogger; 7 | import GAUtilities = gameanalytics.utilities.GAUtilities; 8 | import GAValidator = gameanalytics.validators.GAValidator; 9 | import SdkErrorTask = gameanalytics.tasks.SdkErrorTask; 10 | import EGASdkErrorCategory = gameanalytics.events.EGASdkErrorCategory; 11 | import EGASdkErrorArea = gameanalytics.events.EGASdkErrorArea; 12 | import EGASdkErrorAction = gameanalytics.events.EGASdkErrorAction; 13 | import EGASdkErrorParameter = gameanalytics.events.EGASdkErrorParameter; 14 | 15 | export class GAHTTPApi 16 | { 17 | public static readonly instance:GAHTTPApi = new GAHTTPApi(); 18 | private protocol:string; 19 | private hostName:string; 20 | private version:string; 21 | private remoteConfigsVersion:string; 22 | private baseUrl:string; 23 | private remoteConfigsBaseUrl:string; 24 | private initializeUrlPath:string; 25 | private eventsUrlPath:string; 26 | private useGzip:boolean; 27 | private static readonly MAX_ERROR_MESSAGE_LENGTH:number = 256; 28 | 29 | private constructor() 30 | { 31 | // base url settings 32 | this.protocol = "https"; 33 | this.hostName = "api.gameanalytics.com"; 34 | this.version = "v2"; 35 | this.remoteConfigsVersion = "v1"; 36 | 37 | // create base url 38 | this.baseUrl = this.protocol + "://" + this.hostName + "/" + this.version; 39 | this.remoteConfigsBaseUrl = this.protocol + "://" + this.hostName + "/remote_configs/" + this.remoteConfigsVersion; 40 | 41 | this.initializeUrlPath = "init"; 42 | this.eventsUrlPath = "events"; 43 | 44 | this.useGzip = false; 45 | } 46 | 47 | public requestInit(configsHash:string, callback:(response:EGAHTTPApiResponse, json:{[key:string]: any}) => void): void 48 | { 49 | var gameKey:string = GAState.getGameKey(); 50 | 51 | // Generate URL 52 | var url:string = this.remoteConfigsBaseUrl + "/" + this.initializeUrlPath + "?game_key=" + gameKey + "&interval_seconds=0&configs_hash=" + configsHash; 53 | GALogger.d("Sending 'init' URL: " + url); 54 | 55 | var initAnnotations:{[key:string]: any} = GAState.getInitAnnotations(); 56 | 57 | // make JSON string from data 58 | var JSONstring:string = JSON.stringify(initAnnotations); 59 | 60 | if(!JSONstring) 61 | { 62 | callback(EGAHTTPApiResponse.JsonEncodeFailed, null); 63 | return; 64 | } 65 | 66 | var payloadData:string = this.createPayloadData(JSONstring, this.useGzip); 67 | var extraArgs:Array = []; 68 | extraArgs.push(JSONstring); 69 | GAHTTPApi.sendRequest(url, payloadData, extraArgs, this.useGzip, GAHTTPApi.initRequestCallback, callback); 70 | } 71 | 72 | public sendEventsInArray(eventArray:Array<{[key:string]: any}>, requestId:string, callback:(response:EGAHTTPApiResponse, json:{[key:string]: any}, requestId:string, eventCount:number) => void): void 73 | { 74 | if(eventArray.length == 0) 75 | { 76 | GALogger.d("sendEventsInArray called with missing eventArray"); 77 | return; 78 | } 79 | 80 | var gameKey:string = GAState.getGameKey(); 81 | 82 | // Generate URL 83 | var url:string = this.baseUrl + "/" + gameKey + "/" + this.eventsUrlPath; 84 | GALogger.d("Sending 'events' URL: " + url); 85 | 86 | // make JSON string from data 87 | var JSONstring:string = JSON.stringify(eventArray); 88 | 89 | if(!JSONstring) 90 | { 91 | GALogger.d("sendEventsInArray JSON encoding failed of eventArray"); 92 | callback(EGAHTTPApiResponse.JsonEncodeFailed, null, requestId, eventArray.length); 93 | return; 94 | } 95 | 96 | var payloadData = this.createPayloadData(JSONstring, this.useGzip); 97 | var extraArgs:Array = []; 98 | extraArgs.push(JSONstring); 99 | extraArgs.push(requestId); 100 | extraArgs.push(eventArray.length.toString()); 101 | GAHTTPApi.sendRequest(url, payloadData, extraArgs, this.useGzip, GAHTTPApi.sendEventInArrayRequestCallback, callback); 102 | } 103 | 104 | public sendSdkErrorEvent(category:EGASdkErrorCategory, area:EGASdkErrorArea, action:EGASdkErrorAction, parameter:EGASdkErrorParameter, reason:string, gameKey:string, secretKey:string): void 105 | { 106 | if(!GAState.isEventSubmissionEnabled()) 107 | { 108 | return; 109 | } 110 | 111 | // Validate 112 | if (!GAValidator.validateSdkErrorEvent(gameKey, secretKey, category, area, action)) 113 | { 114 | return; 115 | } 116 | 117 | // Generate URL 118 | var url:string = this.baseUrl + "/" + gameKey + "/" + this.eventsUrlPath; 119 | GALogger.d("Sending 'events' URL: " + url); 120 | 121 | var payloadJSONString:string = ""; 122 | var errorType:string = "" 123 | 124 | var json:{[key:string]: any} = GAState.getSdkErrorEventAnnotations(); 125 | 126 | var categoryString:string = GAHTTPApi.sdkErrorCategoryString(category); 127 | json["error_category"] = categoryString; 128 | errorType += categoryString; 129 | 130 | var areaString:string = GAHTTPApi.sdkErrorAreaString(area); 131 | json["error_area"] = areaString; 132 | errorType += ":" + areaString; 133 | 134 | var actionString:string = GAHTTPApi.sdkErrorActionString(action); 135 | json["error_action"] = actionString; 136 | 137 | var parameterString:string = GAHTTPApi.sdkErrorParameterString(parameter); 138 | if(parameterString.length > 0) 139 | { 140 | json["error_parameter"] = parameterString; 141 | } 142 | 143 | if(reason.length > 0) 144 | { 145 | var reasonTrimmed = reason; 146 | if(reason.length > GAHTTPApi.MAX_ERROR_MESSAGE_LENGTH) 147 | { 148 | var reasonTrimmed = reason.substring(0, GAHTTPApi.MAX_ERROR_MESSAGE_LENGTH); 149 | } 150 | json["reason"] = reasonTrimmed; 151 | } 152 | 153 | var eventArray:Array<{[key:string]: any}> = []; 154 | eventArray.push(json); 155 | payloadJSONString = JSON.stringify(eventArray); 156 | 157 | if(!payloadJSONString) 158 | { 159 | GALogger.w("sendSdkErrorEvent: JSON encoding failed."); 160 | return; 161 | } 162 | 163 | GALogger.d("sendSdkErrorEvent json: " + payloadJSONString); 164 | SdkErrorTask.execute(url, errorType, payloadJSONString, secretKey); 165 | } 166 | 167 | private static sendEventInArrayRequestCallback(request:XMLHttpRequest, url:string, callback:(response:EGAHTTPApiResponse, json:{[key:string]: any}, requestId:string, eventCount:number) => void, extra:Array = null): void 168 | { 169 | var authorization:string = extra[0]; 170 | var JSONstring:string = extra[1]; 171 | var requestId:string = extra[2]; 172 | var eventCount:number = parseInt(extra[3]); 173 | var body:string = ""; 174 | var responseCode:number = 0; 175 | 176 | body = request.responseText; 177 | responseCode = request.status; 178 | 179 | GALogger.d("events request content: " + body); 180 | 181 | var requestResponseEnum:EGAHTTPApiResponse = GAHTTPApi.instance.processRequestResponse(responseCode, request.statusText, body, "Events"); 182 | 183 | // if not 200 result 184 | if(requestResponseEnum != EGAHTTPApiResponse.Ok && requestResponseEnum != EGAHTTPApiResponse.Created && requestResponseEnum != EGAHTTPApiResponse.BadRequest) 185 | { 186 | GALogger.d("Failed events Call. URL: " + url + ", Authorization: " + authorization + ", JSONString: " + JSONstring); 187 | callback(requestResponseEnum, null, requestId, eventCount); 188 | return; 189 | } 190 | 191 | // decode JSON 192 | var requestJsonDict:{[key:string]: any} = body ? JSON.parse(body) : {}; 193 | 194 | if(requestJsonDict == null) 195 | { 196 | callback(EGAHTTPApiResponse.JsonDecodeFailed, null, requestId, eventCount); 197 | GAHTTPApi.instance.sendSdkErrorEvent(EGASdkErrorCategory.Http, EGASdkErrorArea.EventsHttp, EGASdkErrorAction.FailHttpJsonDecode, EGASdkErrorParameter.Undefined, body, GAState.getGameKey(), GAState.getGameSecret()); 198 | return; 199 | } 200 | 201 | // print reason if bad request 202 | if(requestResponseEnum == EGAHTTPApiResponse.BadRequest) 203 | { 204 | GALogger.d("Failed Events Call. Bad request. Response: " + JSON.stringify(requestJsonDict)); 205 | } 206 | 207 | // return response 208 | callback(requestResponseEnum, requestJsonDict, requestId, eventCount); 209 | } 210 | 211 | private static sendRequest(url:string, payloadData:string, extraArgs:Array, gzip:boolean, callback:(request:XMLHttpRequest, url:string, callback:(response:EGAHTTPApiResponse, json:{[key:string]: any}, requestId:string, eventCount:number) => void, extra:Array) => void, callback2:(response:EGAHTTPApiResponse, json:{[key:string]: any}, requestId:string, eventCount:number) => void): void 212 | { 213 | var request:XMLHttpRequest = new XMLHttpRequest(); 214 | 215 | // create authorization hash 216 | var key:string = GAState.getGameSecret(); 217 | var authorization:string = GAUtilities.getHmac(key, payloadData); 218 | 219 | var args:Array = []; 220 | args.push(authorization); 221 | 222 | for(let s in extraArgs) 223 | { 224 | args.push(extraArgs[s]); 225 | } 226 | 227 | request.onreadystatechange = () => { 228 | if(request.readyState === 4) 229 | { 230 | callback(request, url, callback2, args); 231 | } 232 | }; 233 | 234 | request.open("POST", url, true); 235 | request.setRequestHeader("Content-Type", "application/json"); 236 | 237 | request.setRequestHeader("Authorization", authorization); 238 | 239 | if(gzip) 240 | { 241 | throw new Error("gzip not supported"); 242 | //request.setRequestHeader("Content-Encoding", "gzip"); 243 | } 244 | 245 | try 246 | { 247 | request.send(payloadData); 248 | } 249 | catch(e) 250 | { 251 | console.error(e.stack); 252 | } 253 | } 254 | 255 | private static initRequestCallback(request:XMLHttpRequest, url:string, callback:(response:EGAHTTPApiResponse, json:{[key:string]: any}, requestId:string, eventCount:number) => void, extra:Array = null): void 256 | { 257 | var authorization:string = extra[0]; 258 | var JSONstring:string = extra[1]; 259 | var body:string = ""; 260 | var responseCode:number = 0; 261 | 262 | body = request.responseText; 263 | responseCode = request.status; 264 | 265 | // process the response 266 | GALogger.d("init request content : " + body + ", JSONstring: " + JSONstring); 267 | 268 | var requestJsonDict:{[key:string]: any} = body ? JSON.parse(body) : {}; 269 | var requestResponseEnum:EGAHTTPApiResponse = GAHTTPApi.instance.processRequestResponse(responseCode, request.statusText, body, "Init"); 270 | 271 | // if not 200 result 272 | if(requestResponseEnum != EGAHTTPApiResponse.Ok && requestResponseEnum != EGAHTTPApiResponse.Created && requestResponseEnum != EGAHTTPApiResponse.BadRequest) 273 | { 274 | GALogger.d("Failed Init Call. URL: " + url + ", Authorization: " + authorization + ", JSONString: " + JSONstring); 275 | callback(requestResponseEnum, null, "", 0); 276 | return; 277 | } 278 | 279 | if(requestJsonDict == null) 280 | { 281 | GALogger.d("Failed Init Call. Json decoding failed"); 282 | callback(EGAHTTPApiResponse.JsonDecodeFailed, null, "", 0); 283 | GAHTTPApi.instance.sendSdkErrorEvent(EGASdkErrorCategory.Http, EGASdkErrorArea.InitHttp, EGASdkErrorAction.FailHttpJsonDecode, EGASdkErrorParameter.Undefined, body, GAState.getGameKey(), GAState.getGameSecret()); 284 | return; 285 | } 286 | 287 | // print reason if bad request 288 | if(requestResponseEnum === EGAHTTPApiResponse.BadRequest) 289 | { 290 | GALogger.d("Failed Init Call. Bad request. Response: " + JSON.stringify(requestJsonDict)); 291 | // return bad request result 292 | callback(requestResponseEnum, null, "", 0); 293 | return; 294 | } 295 | 296 | // validate Init call values 297 | var validatedInitValues:{[key:string]: any} = GAValidator.validateAndCleanInitRequestResponse(requestJsonDict, requestResponseEnum === EGAHTTPApiResponse.Created); 298 | 299 | if(!validatedInitValues) 300 | { 301 | callback(EGAHTTPApiResponse.BadResponse, null, "", 0); 302 | return; 303 | } 304 | 305 | // all ok 306 | callback(requestResponseEnum, validatedInitValues, "", 0); 307 | } 308 | 309 | private createPayloadData(payload:string, gzip:boolean): string 310 | { 311 | var payloadData:string; 312 | 313 | if(gzip) 314 | { 315 | // payloadData = GAUtilities.GzipCompress(payload); 316 | // GALogger.D("Gzip stats. Size: " + Encoding.UTF8.GetBytes(payload).Length + ", Compressed: " + payloadData.Length + ", Content: " + payload); 317 | throw new Error("gzip not supported"); 318 | } 319 | else 320 | { 321 | payloadData = payload; 322 | } 323 | 324 | return payloadData; 325 | } 326 | 327 | private processRequestResponse(responseCode:number, responseMessage:string, body:string, requestId:string): EGAHTTPApiResponse 328 | { 329 | // if no result - often no connection 330 | if(!body) 331 | { 332 | GALogger.d(requestId + " request. failed. Might be no connection. Description: " + responseMessage + ", Status code: " + responseCode); 333 | return EGAHTTPApiResponse.NoResponse; 334 | } 335 | 336 | // ok 337 | if (responseCode === 200) 338 | { 339 | return EGAHTTPApiResponse.Ok; 340 | } 341 | // created 342 | if (responseCode === 201) 343 | { 344 | return EGAHTTPApiResponse.Created; 345 | } 346 | 347 | // 401 can return 0 status 348 | if (responseCode === 0 || responseCode === 401) 349 | { 350 | GALogger.d(requestId + " request. 401 - Unauthorized."); 351 | return EGAHTTPApiResponse.Unauthorized; 352 | } 353 | 354 | if (responseCode === 400) 355 | { 356 | GALogger.d(requestId + " request. 400 - Bad Request."); 357 | return EGAHTTPApiResponse.BadRequest; 358 | } 359 | 360 | if (responseCode === 500) 361 | { 362 | GALogger.d(requestId + " request. 500 - Internal Server Error."); 363 | return EGAHTTPApiResponse.InternalServerError; 364 | } 365 | 366 | return EGAHTTPApiResponse.UnknownResponseCode; 367 | } 368 | 369 | private static sdkErrorCategoryString(value:EGASdkErrorCategory): string 370 | { 371 | switch (value) 372 | { 373 | case EGASdkErrorCategory.EventValidation: 374 | return "event_validation"; 375 | case EGASdkErrorCategory.Database: 376 | return "db"; 377 | case EGASdkErrorCategory.Init: 378 | return "init"; 379 | case EGASdkErrorCategory.Http: 380 | return "http"; 381 | case EGASdkErrorCategory.Json: 382 | return "json"; 383 | default: 384 | break; 385 | } 386 | return ""; 387 | } 388 | 389 | private static sdkErrorAreaString(value:EGASdkErrorArea): string 390 | { 391 | switch (value) 392 | { 393 | case EGASdkErrorArea.BusinessEvent: 394 | return "business"; 395 | case EGASdkErrorArea.ResourceEvent: 396 | return "resource"; 397 | case EGASdkErrorArea.ProgressionEvent: 398 | return "progression"; 399 | case EGASdkErrorArea.DesignEvent: 400 | return "design"; 401 | case EGASdkErrorArea.ErrorEvent: 402 | return "error"; 403 | case EGASdkErrorArea.InitHttp: 404 | return "init_http"; 405 | case EGASdkErrorArea.EventsHttp: 406 | return "events_http"; 407 | case EGASdkErrorArea.ProcessEvents: 408 | return "process_events"; 409 | case EGASdkErrorArea.AddEventsToStore: 410 | return "add_events_to_store"; 411 | default: 412 | break; 413 | } 414 | return ""; 415 | } 416 | 417 | private static sdkErrorActionString(value:EGASdkErrorAction): string 418 | { 419 | switch (value) 420 | { 421 | case EGASdkErrorAction.InvalidCurrency: 422 | return "invalid_currency"; 423 | case EGASdkErrorAction.InvalidShortString: 424 | return "invalid_short_string"; 425 | case EGASdkErrorAction.InvalidEventPartLength: 426 | return "invalid_event_part_length"; 427 | case EGASdkErrorAction.InvalidEventPartCharacters: 428 | return "invalid_event_part_characters"; 429 | case EGASdkErrorAction.InvalidStore: 430 | return "invalid_store"; 431 | case EGASdkErrorAction.InvalidFlowType: 432 | return "invalid_flow_type"; 433 | case EGASdkErrorAction.StringEmptyOrNull: 434 | return "string_empty_or_null"; 435 | case EGASdkErrorAction.NotFoundInAvailableCurrencies: 436 | return "not_found_in_available_currencies"; 437 | case EGASdkErrorAction.InvalidAmount: 438 | return "invalid_amount"; 439 | case EGASdkErrorAction.NotFoundInAvailableItemTypes: 440 | return "not_found_in_available_item_types"; 441 | case EGASdkErrorAction.WrongProgressionOrder: 442 | return "wrong_progression_order"; 443 | case EGASdkErrorAction.InvalidEventIdLength: 444 | return "invalid_event_id_length"; 445 | case EGASdkErrorAction.InvalidEventIdCharacters: 446 | return "invalid_event_id_characters"; 447 | case EGASdkErrorAction.InvalidProgressionStatus: 448 | return "invalid_progression_status"; 449 | case EGASdkErrorAction.InvalidSeverity: 450 | return "invalid_severity"; 451 | case EGASdkErrorAction.InvalidLongString: 452 | return "invalid_long_string"; 453 | case EGASdkErrorAction.DatabaseTooLarge: 454 | return "db_too_large"; 455 | case EGASdkErrorAction.DatabaseOpenOrCreate: 456 | return "db_open_or_create"; 457 | case EGASdkErrorAction.JsonError: 458 | return "json_error"; 459 | case EGASdkErrorAction.FailHttpJsonDecode: 460 | return "fail_http_json_decode"; 461 | case EGASdkErrorAction.FailHttpJsonEncode: 462 | return "fail_http_json_encode"; 463 | default: 464 | break; 465 | } 466 | return ""; 467 | } 468 | 469 | private static sdkErrorParameterString(value:EGASdkErrorParameter): string 470 | { 471 | switch (value) 472 | { 473 | case EGASdkErrorParameter.Currency: 474 | return "currency"; 475 | case EGASdkErrorParameter.CartType: 476 | return "cart_type"; 477 | case EGASdkErrorParameter.ItemType: 478 | return "item_type"; 479 | case EGASdkErrorParameter.ItemId: 480 | return "item_id"; 481 | case EGASdkErrorParameter.Store: 482 | return "store"; 483 | case EGASdkErrorParameter.FlowType: 484 | return "flow_type"; 485 | case EGASdkErrorParameter.Amount: 486 | return "amount"; 487 | case EGASdkErrorParameter.Progression01: 488 | return "progression01"; 489 | case EGASdkErrorParameter.Progression02: 490 | return "progression02"; 491 | case EGASdkErrorParameter.Progression03: 492 | return "progression03"; 493 | case EGASdkErrorParameter.EventId: 494 | return "event_id"; 495 | case EGASdkErrorParameter.ProgressionStatus: 496 | return "progression_status"; 497 | case EGASdkErrorParameter.Severity: 498 | return "severity"; 499 | case EGASdkErrorParameter.Message: 500 | return "message"; 501 | default: 502 | break; 503 | } 504 | return ""; 505 | } 506 | } 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /src/logging/GALogger.ts: -------------------------------------------------------------------------------- 1 | //GALOGGER_START 2 | module gameanalytics 3 | { 4 | export module logging 5 | { 6 | enum EGALoggerMessageType 7 | { 8 | Error = 0, 9 | Warning = 1, 10 | Info = 2, 11 | Debug = 3 12 | } 13 | 14 | export class GALogger 15 | { 16 | // Fields and properties: START 17 | 18 | private static readonly instance:GALogger = new GALogger(); 19 | private infoLogEnabled:boolean; 20 | private infoLogVerboseEnabled:boolean; 21 | private static debugEnabled:boolean; 22 | private static readonly Tag:string = "GameAnalytics"; 23 | 24 | // Fields and properties: END 25 | 26 | private constructor() 27 | { 28 | GALogger.debugEnabled = true; 29 | } 30 | 31 | // Methods: START 32 | 33 | public static setInfoLog(value:boolean): void 34 | { 35 | GALogger.instance.infoLogEnabled = value; 36 | } 37 | 38 | public static setVerboseLog(value:boolean): void 39 | { 40 | GALogger.instance.infoLogVerboseEnabled = value; 41 | } 42 | 43 | public static i(format:string): void 44 | { 45 | if(!GALogger.instance.infoLogEnabled) 46 | { 47 | return; 48 | } 49 | 50 | var message:string = "Info/" + GALogger.Tag + ": " + format; 51 | GALogger.instance.sendNotificationMessage(message, EGALoggerMessageType.Info); 52 | } 53 | 54 | public static w(format:string): void 55 | { 56 | var message:string = "Warning/" + GALogger.Tag + ": " + format; 57 | GALogger.instance.sendNotificationMessage(message, EGALoggerMessageType.Warning); 58 | } 59 | 60 | public static e(format:string): void 61 | { 62 | var message:string = "Error/" + GALogger.Tag + ": " + format; 63 | GALogger.instance.sendNotificationMessage(message, EGALoggerMessageType.Error); 64 | } 65 | 66 | public static ii(format:string): void 67 | { 68 | if(!GALogger.instance.infoLogVerboseEnabled) 69 | { 70 | return; 71 | } 72 | 73 | var message:string = "Verbose/" + GALogger.Tag + ": " + format; 74 | GALogger.instance.sendNotificationMessage(message, EGALoggerMessageType.Info); 75 | } 76 | 77 | public static d(format:string): void 78 | { 79 | if(!GALogger.debugEnabled) 80 | { 81 | return; 82 | } 83 | 84 | var message:string = "Debug/" + GALogger.Tag + ": " + format; 85 | GALogger.instance.sendNotificationMessage(message, EGALoggerMessageType.Debug); 86 | } 87 | 88 | private sendNotificationMessage(message:string, type:EGALoggerMessageType): void 89 | { 90 | switch(type) 91 | { 92 | case EGALoggerMessageType.Error: 93 | { 94 | console.error(message); 95 | } 96 | break; 97 | 98 | case EGALoggerMessageType.Warning: 99 | { 100 | console.warn(message); 101 | } 102 | break; 103 | 104 | case EGALoggerMessageType.Debug: 105 | { 106 | if(typeof console.debug === "function") 107 | { 108 | console.debug(message); 109 | } 110 | else 111 | { 112 | console.log(message); 113 | } 114 | } 115 | break; 116 | 117 | case EGALoggerMessageType.Info: 118 | { 119 | console.log(message); 120 | } 121 | break; 122 | } 123 | } 124 | 125 | // Methods: END 126 | } 127 | } 128 | } 129 | //GALOGGER_END 130 | -------------------------------------------------------------------------------- /src/store/GAStore.ts: -------------------------------------------------------------------------------- 1 | module gameanalytics 2 | { 3 | export module store 4 | { 5 | import GALogger = gameanalytics.logging.GALogger; 6 | 7 | export enum EGAStoreArgsOperator 8 | { 9 | Equal, 10 | LessOrEqual, 11 | NotEqual 12 | } 13 | 14 | export enum EGAStore 15 | { 16 | Events = 0, 17 | Sessions = 1, 18 | Progression = 2 19 | } 20 | 21 | export class GAStore 22 | { 23 | private static readonly instance:GAStore = new GAStore(); 24 | private static storageAvailable:boolean; 25 | private static readonly MaxNumberOfEntries:number = 2000; 26 | private eventsStore:Array<{[key:string]: any}> = []; 27 | private sessionsStore:Array<{[key:string]: any}> = []; 28 | private progressionStore:Array<{[key:string]: any}> = []; 29 | private storeItems:{[key:string]: any} = {}; 30 | private static readonly StringFormat = (str:string, ...args:string[]) => str.replace(/{(\d+)}/g, (_, index:number) => args[index] || ''); 31 | private static readonly KeyFormat:string = "GA::{0}::{1}"; 32 | private static readonly EventsStoreKey:string = "ga_event"; 33 | private static readonly SessionsStoreKey:string = "ga_session"; 34 | private static readonly ProgressionStoreKey:string = "ga_progression"; 35 | private static readonly ItemsStoreKey:string = "ga_items"; 36 | 37 | private constructor() 38 | { 39 | try 40 | { 41 | if (typeof localStorage === 'object') 42 | { 43 | localStorage.setItem('testingLocalStorage', 'yes'); 44 | localStorage.removeItem('testingLocalStorage'); 45 | GAStore.storageAvailable = true; 46 | } 47 | else 48 | { 49 | GAStore.storageAvailable = false; 50 | } 51 | } 52 | catch (e) 53 | { 54 | } 55 | 56 | GALogger.d("Storage is available?: " + GAStore.storageAvailable); 57 | } 58 | 59 | public static isStorageAvailable():boolean 60 | { 61 | return GAStore.storageAvailable; 62 | } 63 | 64 | public static isStoreTooLargeForEvents(): boolean 65 | { 66 | return GAStore.instance.eventsStore.length + GAStore.instance.sessionsStore.length > GAStore.MaxNumberOfEntries; 67 | } 68 | 69 | public static select(store:EGAStore, args:Array<[string, EGAStoreArgsOperator, any]> = [], sort:boolean = false, maxCount:number = 0): Array<{[key:string]: any}> 70 | { 71 | var currentStore:Array<{[key:string]: any}> = GAStore.getStore(store); 72 | 73 | if(!currentStore) 74 | { 75 | return null; 76 | } 77 | 78 | var result:Array<{[key:string]: any}> = []; 79 | 80 | for(let i = 0; i < currentStore.length; ++i) 81 | { 82 | var entry:{[key:string]: any} = currentStore[i]; 83 | 84 | var add:boolean = true; 85 | for(let j = 0; j < args.length; ++j) 86 | { 87 | var argsEntry:[string, EGAStoreArgsOperator, any] = args[j]; 88 | 89 | if(entry[argsEntry[0]]) 90 | { 91 | switch(argsEntry[1]) 92 | { 93 | case EGAStoreArgsOperator.Equal: 94 | { 95 | add = entry[argsEntry[0]] == argsEntry[2]; 96 | } 97 | break; 98 | 99 | case EGAStoreArgsOperator.LessOrEqual: 100 | { 101 | add = entry[argsEntry[0]] <= argsEntry[2]; 102 | } 103 | break; 104 | 105 | case EGAStoreArgsOperator.NotEqual: 106 | { 107 | add = entry[argsEntry[0]] != argsEntry[2]; 108 | } 109 | break; 110 | 111 | default: 112 | { 113 | add = false; 114 | } 115 | break; 116 | } 117 | } 118 | else 119 | { 120 | add = false; 121 | } 122 | 123 | if(!add) 124 | { 125 | break; 126 | } 127 | } 128 | 129 | if(add) 130 | { 131 | result.push(entry); 132 | } 133 | } 134 | 135 | if(sort) 136 | { 137 | result.sort((a:{[key:string]: any}, b:{[key:string]: any}) => { 138 | return (a["client_ts"] as number) - (b["client_ts"] as number) 139 | }); 140 | } 141 | 142 | if(maxCount > 0 && result.length > maxCount) 143 | { 144 | result = result.slice(0, maxCount + 1) 145 | } 146 | 147 | return result; 148 | } 149 | 150 | public static update(store:EGAStore, setArgs:Array<[string, any]>, whereArgs:Array<[string, EGAStoreArgsOperator, any]> = []): boolean 151 | { 152 | var currentStore:Array<{[key:string]: any}> = GAStore.getStore(store); 153 | 154 | if(!currentStore) 155 | { 156 | return false; 157 | } 158 | 159 | for(let i = 0; i < currentStore.length; ++i) 160 | { 161 | var entry:{[key:string]: any} = currentStore[i]; 162 | 163 | var update:boolean = true; 164 | for(let j = 0; j < whereArgs.length; ++j) 165 | { 166 | var argsEntry:[string, EGAStoreArgsOperator, any] = whereArgs[j]; 167 | 168 | if(entry[argsEntry[0]]) 169 | { 170 | switch(argsEntry[1]) 171 | { 172 | case EGAStoreArgsOperator.Equal: 173 | { 174 | update = entry[argsEntry[0]] == argsEntry[2]; 175 | } 176 | break; 177 | 178 | case EGAStoreArgsOperator.LessOrEqual: 179 | { 180 | update = entry[argsEntry[0]] <= argsEntry[2]; 181 | } 182 | break; 183 | 184 | case EGAStoreArgsOperator.NotEqual: 185 | { 186 | update = entry[argsEntry[0]] != argsEntry[2]; 187 | } 188 | break; 189 | 190 | default: 191 | { 192 | update = false; 193 | } 194 | break; 195 | } 196 | } 197 | else 198 | { 199 | update = false; 200 | } 201 | 202 | if(!update) 203 | { 204 | break; 205 | } 206 | } 207 | 208 | if(update) 209 | { 210 | for(let j = 0; j < setArgs.length; ++j) 211 | { 212 | var setArgsEntry:[string, any] = setArgs[j]; 213 | entry[setArgsEntry[0]] = setArgsEntry[1]; 214 | } 215 | } 216 | } 217 | 218 | return true; 219 | } 220 | 221 | public static delete(store:EGAStore, args:Array<[string, EGAStoreArgsOperator, any]>): void 222 | { 223 | var currentStore:Array<{[key:string]: any}> = GAStore.getStore(store); 224 | 225 | if(!currentStore) 226 | { 227 | return; 228 | } 229 | 230 | for(let i = 0; i < currentStore.length; ++i) 231 | { 232 | var entry:{[key:string]: any} = currentStore[i]; 233 | 234 | var del:boolean = true; 235 | for(let j = 0; j < args.length; ++j) 236 | { 237 | var argsEntry:[string, EGAStoreArgsOperator, any] = args[j]; 238 | 239 | if(entry[argsEntry[0]]) 240 | { 241 | switch(argsEntry[1]) 242 | { 243 | case EGAStoreArgsOperator.Equal: 244 | { 245 | del = entry[argsEntry[0]] == argsEntry[2]; 246 | } 247 | break; 248 | 249 | case EGAStoreArgsOperator.LessOrEqual: 250 | { 251 | del = entry[argsEntry[0]] <= argsEntry[2]; 252 | } 253 | break; 254 | 255 | case EGAStoreArgsOperator.NotEqual: 256 | { 257 | del = entry[argsEntry[0]] != argsEntry[2]; 258 | } 259 | break; 260 | 261 | default: 262 | { 263 | del = false; 264 | } 265 | break; 266 | } 267 | } 268 | else 269 | { 270 | del = false; 271 | } 272 | 273 | if(!del) 274 | { 275 | break; 276 | } 277 | } 278 | 279 | if(del) 280 | { 281 | currentStore.splice(i, 1); 282 | --i; 283 | } 284 | } 285 | } 286 | 287 | public static insert(store:EGAStore, newEntry:{[key:string]: any}, replace:boolean = false, replaceKey:string = null): void 288 | { 289 | var currentStore:Array<{[key:string]: any}> = GAStore.getStore(store); 290 | 291 | if(!currentStore) 292 | { 293 | return; 294 | } 295 | 296 | if(replace) 297 | { 298 | if(!replaceKey) 299 | { 300 | return; 301 | } 302 | 303 | var replaced:boolean = false; 304 | 305 | for(let i = 0; i < currentStore.length; ++i) 306 | { 307 | var entry:{[key:string]: any} = currentStore[i]; 308 | 309 | if(entry[replaceKey] == newEntry[replaceKey]) 310 | { 311 | for(let s in newEntry) 312 | { 313 | entry[s] = newEntry[s]; 314 | } 315 | replaced = true; 316 | break; 317 | } 318 | } 319 | 320 | if(!replaced) 321 | { 322 | currentStore.push(newEntry); 323 | } 324 | } 325 | else 326 | { 327 | currentStore.push(newEntry); 328 | } 329 | } 330 | 331 | public static save(gameKey:string): void 332 | { 333 | if(!GAStore.isStorageAvailable()) 334 | { 335 | GALogger.w("Storage is not available, cannot save."); 336 | return; 337 | } 338 | 339 | localStorage.setItem(GAStore.StringFormat(GAStore.KeyFormat, gameKey, GAStore.EventsStoreKey), JSON.stringify(GAStore.instance.eventsStore)); 340 | localStorage.setItem(GAStore.StringFormat(GAStore.KeyFormat, gameKey, GAStore.SessionsStoreKey), JSON.stringify(GAStore.instance.sessionsStore)); 341 | localStorage.setItem(GAStore.StringFormat(GAStore.KeyFormat, gameKey, GAStore.ProgressionStoreKey), JSON.stringify(GAStore.instance.progressionStore)); 342 | localStorage.setItem(GAStore.StringFormat(GAStore.KeyFormat, gameKey, GAStore.ItemsStoreKey), JSON.stringify(GAStore.instance.storeItems)); 343 | } 344 | 345 | public static load(gameKey:string): void 346 | { 347 | if(!GAStore.isStorageAvailable()) 348 | { 349 | GALogger.w("Storage is not available, cannot load."); 350 | return; 351 | } 352 | 353 | try 354 | { 355 | GAStore.instance.eventsStore = JSON.parse(localStorage.getItem(GAStore.StringFormat(GAStore.KeyFormat, gameKey, GAStore.EventsStoreKey))); 356 | 357 | if(!GAStore.instance.eventsStore) 358 | { 359 | GAStore.instance.eventsStore = []; 360 | } 361 | } 362 | catch(e) 363 | { 364 | GALogger.w("Load failed for 'events' store. Using empty store."); 365 | GAStore.instance.eventsStore = []; 366 | } 367 | 368 | try 369 | { 370 | GAStore.instance.sessionsStore = JSON.parse(localStorage.getItem(GAStore.StringFormat(GAStore.KeyFormat, gameKey, GAStore.SessionsStoreKey))); 371 | 372 | if(!GAStore.instance.sessionsStore) 373 | { 374 | GAStore.instance.sessionsStore = []; 375 | } 376 | } 377 | catch(e) 378 | { 379 | GALogger.w("Load failed for 'sessions' store. Using empty store."); 380 | GAStore.instance.sessionsStore = []; 381 | } 382 | 383 | try 384 | { 385 | GAStore.instance.progressionStore = JSON.parse(localStorage.getItem(GAStore.StringFormat(GAStore.KeyFormat, gameKey, GAStore.ProgressionStoreKey))); 386 | 387 | if(!GAStore.instance.progressionStore) 388 | { 389 | GAStore.instance.progressionStore = []; 390 | } 391 | } 392 | catch(e) 393 | { 394 | GALogger.w("Load failed for 'progression' store. Using empty store."); 395 | GAStore.instance.progressionStore = []; 396 | } 397 | 398 | try 399 | { 400 | GAStore.instance.storeItems = JSON.parse(localStorage.getItem(GAStore.StringFormat(GAStore.KeyFormat, gameKey, GAStore.ItemsStoreKey))); 401 | 402 | if(!GAStore.instance.storeItems) 403 | { 404 | GAStore.instance.storeItems = {}; 405 | } 406 | } 407 | catch(e) 408 | { 409 | GALogger.w("Load failed for 'items' store. Using empty store."); 410 | GAStore.instance.progressionStore = []; 411 | } 412 | } 413 | 414 | public static setItem(gameKey:string, key:string, value:string): void 415 | { 416 | var keyWithPrefix:string = GAStore.StringFormat(GAStore.KeyFormat, gameKey, key); 417 | 418 | if(!value) 419 | { 420 | if(keyWithPrefix in GAStore.instance.storeItems) 421 | { 422 | delete GAStore.instance.storeItems[keyWithPrefix]; 423 | } 424 | } 425 | else 426 | { 427 | GAStore.instance.storeItems[keyWithPrefix] = value; 428 | } 429 | } 430 | 431 | public static getItem(gameKey:string, key:string): string 432 | { 433 | var keyWithPrefix:string = GAStore.StringFormat(GAStore.KeyFormat, gameKey, key); 434 | if(keyWithPrefix in GAStore.instance.storeItems) 435 | { 436 | return GAStore.instance.storeItems[keyWithPrefix] as string; 437 | } 438 | else 439 | { 440 | return null; 441 | } 442 | } 443 | 444 | private static getStore(store:EGAStore): Array<{[key:string]: any}> 445 | { 446 | switch(store) 447 | { 448 | case EGAStore.Events: 449 | { 450 | return GAStore.instance.eventsStore; 451 | } 452 | 453 | case EGAStore.Sessions: 454 | { 455 | return GAStore.instance.sessionsStore; 456 | } 457 | 458 | case EGAStore.Progression: 459 | { 460 | return GAStore.instance.progressionStore; 461 | } 462 | 463 | default: 464 | { 465 | GALogger.w("GAStore.getStore(): Cannot find store: " + store); 466 | return null; 467 | } 468 | } 469 | } 470 | } 471 | } 472 | } 473 | -------------------------------------------------------------------------------- /src/tasks/SdkErrorTask.ts: -------------------------------------------------------------------------------- 1 | module gameanalytics 2 | { 3 | export module tasks 4 | { 5 | import GAUtilities = gameanalytics.utilities.GAUtilities; 6 | import GALogger = gameanalytics.logging.GALogger; 7 | 8 | export class SdkErrorTask 9 | { 10 | private static readonly MaxCount:number = 10; 11 | private static readonly countMap:{[key:string]: number} = {}; 12 | private static readonly timestampMap:{[key:string]: Date} = {}; 13 | 14 | public static execute(url:string, type:string, payloadData:string, secretKey:string): void 15 | { 16 | var now:Date = new Date(); 17 | 18 | if(!SdkErrorTask.timestampMap[type]) 19 | { 20 | SdkErrorTask.timestampMap[type] = now; 21 | } 22 | if(!SdkErrorTask.countMap[type]) 23 | { 24 | SdkErrorTask.countMap[type] = 0; 25 | } 26 | var diff:number = now.getTime() - SdkErrorTask.timestampMap[type].getTime(); 27 | var diffSeconds:number = diff / 1000; 28 | if(diffSeconds >= 3600) 29 | { 30 | SdkErrorTask.timestampMap[type] = now; 31 | SdkErrorTask.countMap[type] = 0; 32 | } 33 | 34 | if(SdkErrorTask.countMap[type] >= SdkErrorTask.MaxCount) 35 | { 36 | return; 37 | } 38 | 39 | var hashHmac:string = GAUtilities.getHmac(secretKey, payloadData); 40 | 41 | var request:XMLHttpRequest = new XMLHttpRequest(); 42 | 43 | request.onreadystatechange = () => { 44 | if(request.readyState === 4) 45 | { 46 | if(!request.responseText) 47 | { 48 | GALogger.d("sdk error failed. Might be no connection. Description: " + request.statusText + ", Status code: " + request.status); 49 | return; 50 | } 51 | 52 | if(request.status != 200) 53 | { 54 | GALogger.w("sdk error failed. response code not 200. status code: " + request.status + ", description: " + request.statusText + ", body: " + request.responseText); 55 | return; 56 | } 57 | else 58 | { 59 | SdkErrorTask.countMap[type] = SdkErrorTask.countMap[type] + 1; 60 | } 61 | } 62 | }; 63 | 64 | request.open("POST", url, true); 65 | request.setRequestHeader("Content-Type", "application/json"); 66 | request.setRequestHeader("Authorization", hashHmac); 67 | 68 | try 69 | { 70 | request.send(payloadData); 71 | } 72 | catch(e) 73 | { 74 | console.error(e); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/threading/GAThreading.ts: -------------------------------------------------------------------------------- 1 | module gameanalytics 2 | { 3 | export module threading 4 | { 5 | import GALogger = gameanalytics.logging.GALogger; 6 | import GAUtilities = gameanalytics.utilities.GAUtilities; 7 | import GAStore = gameanalytics.store.GAStore; 8 | import EGAStoreArgsOperator = gameanalytics.store.EGAStoreArgsOperator; 9 | import EGAStore = gameanalytics.store.EGAStore; 10 | import GAState = gameanalytics.state.GAState; 11 | import GAEvents = gameanalytics.events.GAEvents; 12 | import GAHTTPApi = gameanalytics.http.GAHTTPApi; 13 | 14 | export class GAThreading 15 | { 16 | private static readonly instance:GAThreading = new GAThreading(); 17 | public readonly blocks:PriorityQueue = new PriorityQueue(>{ 18 | compare: (x:number, y:number) => { 19 | return x - y; 20 | } 21 | }); 22 | private readonly id2TimedBlockMap:{[key:number]: TimedBlock} = {}; 23 | private static runTimeoutId:NodeJS.Timeout; 24 | private static readonly ThreadWaitTimeInMs:number = 1000; 25 | private static ProcessEventsIntervalInSeconds:number = 8.0; 26 | private keepRunning:boolean; 27 | private isRunning:boolean; 28 | 29 | private constructor() 30 | { 31 | GALogger.d("Initializing GA thread..."); 32 | GAThreading.startThread(); 33 | } 34 | 35 | public static createTimedBlock(delayInSeconds:number = 0): TimedBlock 36 | { 37 | var time:Date = new Date(); 38 | time.setUTCSeconds(time.getUTCSeconds() + delayInSeconds); 39 | 40 | var timedBlock:TimedBlock = new TimedBlock(time); 41 | return timedBlock; 42 | } 43 | 44 | public static performTaskOnGAThread(taskBlock:() => void, delayInSeconds:number = 0): void 45 | { 46 | var time:Date = new Date(); 47 | time.setUTCSeconds(time.getUTCSeconds() + delayInSeconds); 48 | 49 | var timedBlock:TimedBlock = new TimedBlock(time); 50 | timedBlock.block = taskBlock; 51 | GAThreading.instance.id2TimedBlockMap[timedBlock.id] = timedBlock; 52 | GAThreading.instance.addTimedBlock(timedBlock); 53 | } 54 | 55 | public static performTimedBlockOnGAThread(timedBlock:TimedBlock): void 56 | { 57 | GAThreading.instance.id2TimedBlockMap[timedBlock.id] = timedBlock; 58 | GAThreading.instance.addTimedBlock(timedBlock); 59 | } 60 | 61 | public static scheduleTimer(interval:number, callback:() => void): number 62 | { 63 | var time:Date = new Date(); 64 | time.setUTCSeconds(time.getUTCSeconds() + interval); 65 | 66 | var timedBlock:TimedBlock = new TimedBlock(time); 67 | timedBlock.block = callback; 68 | GAThreading.instance.id2TimedBlockMap[timedBlock.id] = timedBlock; 69 | GAThreading.instance.addTimedBlock(timedBlock); 70 | 71 | return timedBlock.id; 72 | } 73 | 74 | public static getTimedBlockById(blockIdentifier:number): TimedBlock 75 | { 76 | if (blockIdentifier in GAThreading.instance.id2TimedBlockMap) 77 | { 78 | return GAThreading.instance.id2TimedBlockMap[blockIdentifier] 79 | } 80 | else 81 | { 82 | return null; 83 | } 84 | } 85 | 86 | public static ensureEventQueueIsRunning(): void 87 | { 88 | GAThreading.instance.keepRunning = true; 89 | 90 | if(!GAThreading.instance.isRunning) 91 | { 92 | GAThreading.instance.isRunning = true; 93 | GAThreading.scheduleTimer(GAThreading.ProcessEventsIntervalInSeconds, GAThreading.processEventQueue); 94 | } 95 | } 96 | 97 | public static endSessionAndStopQueue(): void 98 | { 99 | if(GAState.isInitialized()) 100 | { 101 | GALogger.i("Ending session."); 102 | GAThreading.stopEventQueue(); 103 | if (GAState.isEnabled() && GAState.sessionIsStarted()) 104 | { 105 | GAEvents.addSessionEndEvent(); 106 | GAState.instance.sessionStart = 0; 107 | } 108 | } 109 | } 110 | 111 | public static stopEventQueue(): void 112 | { 113 | GAThreading.instance.keepRunning = false; 114 | } 115 | 116 | public static ignoreTimer(blockIdentifier:number): void 117 | { 118 | if (blockIdentifier in GAThreading.instance.id2TimedBlockMap) 119 | { 120 | GAThreading.instance.id2TimedBlockMap[blockIdentifier].ignore = true; 121 | } 122 | } 123 | 124 | public static setEventProcessInterval(interval:number): void 125 | { 126 | if (interval > 0) 127 | { 128 | GAThreading.ProcessEventsIntervalInSeconds = interval; 129 | } 130 | } 131 | 132 | private addTimedBlock(timedBlock:TimedBlock): void 133 | { 134 | this.blocks.enqueue(timedBlock.deadline.getTime(), timedBlock); 135 | } 136 | 137 | private static run(): void 138 | { 139 | clearTimeout(GAThreading.runTimeoutId); 140 | 141 | try 142 | { 143 | var timedBlock:TimedBlock; 144 | 145 | while ((timedBlock = GAThreading.getNextBlock())) 146 | { 147 | if (!timedBlock.ignore) 148 | { 149 | if(timedBlock.async) 150 | { 151 | if(!timedBlock.running) 152 | { 153 | timedBlock.running = true; 154 | timedBlock.block(); 155 | break; 156 | } 157 | } 158 | else 159 | { 160 | timedBlock.block(); 161 | } 162 | } 163 | } 164 | 165 | GAThreading.runTimeoutId = setTimeout(GAThreading.run, GAThreading.ThreadWaitTimeInMs); 166 | return; 167 | } 168 | catch (e) 169 | { 170 | GALogger.e("Error on GA thread"); 171 | GALogger.e(e.stack); 172 | } 173 | GALogger.d("Ending GA thread"); 174 | } 175 | 176 | private static startThread(): void 177 | { 178 | GALogger.d("Starting GA thread"); 179 | GAThreading.runTimeoutId = setTimeout(GAThreading.run, 0); 180 | } 181 | 182 | private static getNextBlock(): TimedBlock 183 | { 184 | var now:Date = new Date(); 185 | 186 | if (GAThreading.instance.blocks.hasItems() && GAThreading.instance.blocks.peek().deadline.getTime() <= now.getTime()) 187 | { 188 | if(GAThreading.instance.blocks.peek().async) 189 | { 190 | if(GAThreading.instance.blocks.peek().running) 191 | { 192 | return GAThreading.instance.blocks.peek(); 193 | } 194 | else 195 | { 196 | return GAThreading.instance.blocks.dequeue(); 197 | } 198 | } 199 | else 200 | { 201 | return GAThreading.instance.blocks.dequeue(); 202 | } 203 | } 204 | 205 | return null; 206 | } 207 | 208 | private static processEventQueue(): void 209 | { 210 | GAEvents.processEvents("", true); 211 | if(GAThreading.instance.keepRunning) 212 | { 213 | GAThreading.scheduleTimer(GAThreading.ProcessEventsIntervalInSeconds, GAThreading.processEventQueue); 214 | } 215 | else 216 | { 217 | GAThreading.instance.isRunning = false; 218 | } 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/threading/PriorityQueue.ts: -------------------------------------------------------------------------------- 1 | module gameanalytics 2 | { 3 | export module threading 4 | { 5 | export interface IComparer 6 | { 7 | compare(x:T, y:T): number; 8 | } 9 | 10 | export class PriorityQueue 11 | { 12 | public _subQueues:{[key:number]: Array}; 13 | public _sortedKeys:Array; 14 | private comparer:IComparer; 15 | 16 | public constructor(priorityComparer:IComparer) 17 | { 18 | this.comparer = priorityComparer; 19 | this._subQueues = {}; 20 | this._sortedKeys = []; 21 | } 22 | 23 | public enqueue(priority:number, item:TItem): void 24 | { 25 | if(this._sortedKeys.indexOf(priority) === -1) 26 | { 27 | this.addQueueOfPriority(priority); 28 | } 29 | 30 | this._subQueues[priority].push(item); 31 | } 32 | 33 | private addQueueOfPriority(priority:number): void 34 | { 35 | this._sortedKeys.push(priority); 36 | this._sortedKeys.sort((x:number, y:number) => this.comparer.compare(x, y)); 37 | this._subQueues[priority] = []; 38 | } 39 | 40 | public peek(): TItem 41 | { 42 | if(this.hasItems()) 43 | { 44 | return this._subQueues[this._sortedKeys[0]][0]; 45 | } 46 | else 47 | { 48 | throw new Error("The queue is empty"); 49 | } 50 | } 51 | 52 | public hasItems(): boolean 53 | { 54 | return this._sortedKeys.length > 0; 55 | } 56 | 57 | public dequeue(): TItem 58 | { 59 | if(this.hasItems()) 60 | { 61 | return this.dequeueFromHighPriorityQueue(); 62 | } 63 | else 64 | { 65 | throw new Error("The queue is empty"); 66 | } 67 | } 68 | 69 | private dequeueFromHighPriorityQueue(): TItem 70 | { 71 | var firstKey:number = this._sortedKeys[0]; 72 | var nextItem:TItem = this._subQueues[firstKey].shift(); 73 | if(this._subQueues[firstKey].length === 0) 74 | { 75 | this._sortedKeys.shift(); 76 | delete this._subQueues[firstKey]; 77 | } 78 | 79 | return nextItem; 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/threading/TimedBlock.ts: -------------------------------------------------------------------------------- 1 | module gameanalytics 2 | { 3 | export module threading 4 | { 5 | export class TimedBlock 6 | { 7 | public readonly deadline:Date; 8 | public block:() => void; 9 | public readonly id:number; 10 | public ignore:boolean; 11 | public async:boolean; 12 | public running:boolean; 13 | private static idCounter:number = 0; 14 | 15 | public constructor(deadline:Date) 16 | { 17 | this.deadline = deadline; 18 | this.ignore = false; 19 | this.async = false; 20 | this.running = false; 21 | this.id = ++TimedBlock.idCounter; 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utilities/GAUtilities.ts: -------------------------------------------------------------------------------- 1 | module gameanalytics 2 | { 3 | export module utilities 4 | { 5 | import GALogger = gameanalytics.logging.GALogger; 6 | 7 | export class GAUtilities 8 | { 9 | public static getHmac(key:string, data:string): string 10 | { 11 | var encryptedMessage = CryptoJS.HmacSHA256(data, key); 12 | return CryptoJS.enc.Base64.stringify(encryptedMessage); 13 | } 14 | 15 | public static stringMatch(s:string, pattern:RegExp): boolean 16 | { 17 | if(!s || !pattern) 18 | { 19 | return false; 20 | } 21 | 22 | return pattern.test(s); 23 | } 24 | 25 | public static joinStringArray(v:Array, delimiter:string): string 26 | { 27 | var result:string = ""; 28 | 29 | for (let i = 0, il = v.length; i < il; i++) 30 | { 31 | if (i > 0) 32 | { 33 | result += delimiter; 34 | } 35 | result += v[i]; 36 | } 37 | return result; 38 | } 39 | 40 | public static stringArrayContainsString(array:Array, search:string): boolean 41 | { 42 | if (array.length === 0) 43 | { 44 | return false; 45 | } 46 | 47 | for(let s in array) 48 | { 49 | if(array[s] === search) 50 | { 51 | return true; 52 | } 53 | } 54 | return false; 55 | } 56 | 57 | private static readonly keyStr:string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 58 | 59 | public static encode64(input:string): string 60 | { 61 | input = encodeURI(input); 62 | var output:string = ""; 63 | var chr1:number, chr2:number, chr3:number = 0; 64 | var enc1:number, enc2:number, enc3:number, enc4:number = 0; 65 | var i = 0; 66 | 67 | do 68 | { 69 | chr1 = input.charCodeAt(i++); 70 | chr2 = input.charCodeAt(i++); 71 | chr3 = input.charCodeAt(i++); 72 | 73 | enc1 = chr1 >> 2; 74 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 75 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 76 | enc4 = chr3 & 63; 77 | 78 | if (isNaN(chr2)) 79 | { 80 | enc3 = enc4 = 64; 81 | } 82 | else if (isNaN(chr3)) 83 | { 84 | enc4 = 64; 85 | } 86 | 87 | output = output + 88 | GAUtilities.keyStr.charAt(enc1) + 89 | GAUtilities.keyStr.charAt(enc2) + 90 | GAUtilities.keyStr.charAt(enc3) + 91 | GAUtilities.keyStr.charAt(enc4); 92 | chr1 = chr2 = chr3 = 0; 93 | enc1 = enc2 = enc3 = enc4 = 0; 94 | } 95 | while (i < input.length); 96 | 97 | return output; 98 | } 99 | 100 | public static decode64(input:string): string 101 | { 102 | var output:string = ""; 103 | var chr1:number, chr2:number, chr3:number = 0; 104 | var enc1:number, enc2:number, enc3:number, enc4:number = 0; 105 | var i = 0; 106 | 107 | // remove all characters that are not A-Z, a-z, 0-9, +, /, or = 108 | var base64test = /[^A-Za-z0-9\+\/\=]/g; 109 | if (base64test.exec(input)) { 110 | GALogger.w("There were invalid base64 characters in the input text. Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='. Expect errors in decoding."); 111 | } 112 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 113 | 114 | do 115 | { 116 | enc1 = GAUtilities.keyStr.indexOf(input.charAt(i++)); 117 | enc2 = GAUtilities.keyStr.indexOf(input.charAt(i++)); 118 | enc3 = GAUtilities.keyStr.indexOf(input.charAt(i++)); 119 | enc4 = GAUtilities.keyStr.indexOf(input.charAt(i++)); 120 | 121 | chr1 = (enc1 << 2) | (enc2 >> 4); 122 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 123 | chr3 = ((enc3 & 3) << 6) | enc4; 124 | 125 | output = output + String.fromCharCode(chr1); 126 | 127 | if (enc3 != 64) { 128 | output = output + String.fromCharCode(chr2); 129 | } 130 | if (enc4 != 64) { 131 | output = output + String.fromCharCode(chr3); 132 | } 133 | 134 | chr1 = chr2 = chr3 = 0; 135 | enc1 = enc2 = enc3 = enc4 = 0; 136 | 137 | } 138 | while (i < input.length); 139 | 140 | return decodeURI(output); 141 | } 142 | 143 | public static timeIntervalSince1970(): number 144 | { 145 | var date:Date = new Date(); 146 | return Math.round(date.getTime() / 1000); 147 | } 148 | 149 | public static createGuid(): string 150 | { 151 | return ("10000000-1000-4000-8000-100000000000").replace(/[018]/g, c => (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)); 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /test/testEvents.js: -------------------------------------------------------------------------------- 1 | describe("Events", function () { 2 | describe("configureAvailableCustomDimensions01", function () { 3 | it("should be added to queue", function() { 4 | gameanalytics.GameAnalytics.configureAvailableCustomDimensions01(["ninja", "samurai"]); 5 | expect(countPatternFoundInBlocks("setAvailableCustomDimensions01")).toEqual(1); 6 | GameAnalytics("configureAvailableCustomDimensions01", ["ninja", "samurai"]); 7 | expect(countPatternFoundInBlocks("setAvailableCustomDimensions01")).toEqual(2); 8 | }); 9 | }); 10 | 11 | describe("configureAvailableCustomDimensions02", function () { 12 | it("should be added to queue", function() { 13 | gameanalytics.GameAnalytics.configureAvailableCustomDimensions02(["ninja", "samurai"]); 14 | expect(countPatternFoundInBlocks("setAvailableCustomDimensions02")).toEqual(1); 15 | GameAnalytics("configureAvailableCustomDimensions02", ["ninja", "samurai"]); 16 | expect(countPatternFoundInBlocks("setAvailableCustomDimensions02")).toEqual(2); 17 | }); 18 | }); 19 | 20 | describe("configureAvailableCustomDimensions03", function () { 21 | it("should be added to queue", function() { 22 | gameanalytics.GameAnalytics.configureAvailableCustomDimensions03(["ninja", "samurai"]); 23 | expect(countPatternFoundInBlocks("setAvailableCustomDimensions03")).toEqual(1); 24 | GameAnalytics("configureAvailableCustomDimensions03", ["ninja", "samurai"]); 25 | expect(countPatternFoundInBlocks("setAvailableCustomDimensions03")).toEqual(2); 26 | }); 27 | }); 28 | 29 | describe("configureAvailableResourceCurrencies", function () { 30 | it("should be added to queue", function() { 31 | gameanalytics.GameAnalytics.configureAvailableResourceCurrencies(["gems", "gold"]); 32 | expect(countPatternFoundInBlocks("setAvailableResourceCurrencies")).toEqual(1); 33 | GameAnalytics("configureAvailableResourceCurrencies", ["gems", "gold"]); 34 | expect(countPatternFoundInBlocks("setAvailableResourceCurrencies")).toEqual(2); 35 | }); 36 | }); 37 | 38 | describe("configureAvailableResourceItemTypes", function () { 39 | it("should be added to queue", function() { 40 | gameanalytics.GameAnalytics.configureAvailableResourceItemTypes(["guns", "powerups"]); 41 | expect(countPatternFoundInBlocks("setAvailableResourceItemTypes")).toEqual(1); 42 | GameAnalytics("configureAvailableResourceItemTypes", ["guns", "powerups"]); 43 | expect(countPatternFoundInBlocks("setAvailableResourceItemTypes")).toEqual(2); 44 | }); 45 | }); 46 | 47 | describe("configureBuild", function () { 48 | it("should be added to queue", function() { 49 | gameanalytics.GameAnalytics.configureBuild("1.0.0"); 50 | expect(countPatternFoundInBlocks("setBuild")).toEqual(1); 51 | GameAnalytics("configureBuild", "1.0.0"); 52 | expect(countPatternFoundInBlocks("setBuild")).toEqual(2); 53 | }); 54 | }); 55 | 56 | describe("configureSdkGameEngineVersion", function () { 57 | it("should be added to queue", function() { 58 | gameanalytics.GameAnalytics.configureSdkGameEngineVersion("unity 1.0.0"); 59 | expect(countPatternFoundInBlocks("GADevice.sdkGameEngineVersion")).toEqual(1); 60 | GameAnalytics("configureSdkGameEngineVersion", "unity 1.0.0"); 61 | expect(countPatternFoundInBlocks("GADevice.sdkGameEngineVersion")).toEqual(2); 62 | }); 63 | }); 64 | 65 | describe("configureGameEngineVersion", function () { 66 | it("should be added to queue", function() { 67 | gameanalytics.GameAnalytics.configureGameEngineVersion("unity 1.0.0"); 68 | expect(countPatternFoundInBlocks("GADevice.gameEngineVersion")).toEqual(1); 69 | GameAnalytics("configureGameEngineVersion", "unity 1.0.0"); 70 | expect(countPatternFoundInBlocks("GADevice.gameEngineVersion")).toEqual(2); 71 | }); 72 | }); 73 | 74 | describe("configureUserId", function () { 75 | it("should be added to queue", function() { 76 | gameanalytics.GameAnalytics.configureUserId("custom_id"); 77 | expect(countPatternFoundInBlocks("setUserId")).toEqual(1); 78 | GameAnalytics("configureUserId", "custom_id"); 79 | expect(countPatternFoundInBlocks("setUserId")).toEqual(2); 80 | }); 81 | }); 82 | 83 | describe("initialize", function () { 84 | it("should be added to queue", function() { 85 | gameanalytics.GameAnalytics.initialize("gameKey", "secretKey"); 86 | expect(countPatternFoundInBlocks("internalInitialize")).toEqual(1); 87 | GameAnalytics("initialize", "gameKey", "secretKey"); 88 | expect(countPatternFoundInBlocks("internalInitialize")).toEqual(2); 89 | }); 90 | }); 91 | 92 | describe("addBusinessEvent", function () { 93 | it("should be added to queue", function() { 94 | gameanalytics.GameAnalytics.addBusinessEvent("USD", 100, "itemType", "itemId", "shop"); 95 | expect(countPatternFoundInBlocks("addBusinessEvent")).toEqual(1); 96 | GameAnalytics("addBusinessEvent", "USD", 100, "itemType", "itemId", "shop"); 97 | expect(countPatternFoundInBlocks("addBusinessEvent")).toEqual(2); 98 | }); 99 | }); 100 | 101 | describe("addResourceEvent", function () { 102 | it("should be added to queue", function() { 103 | gameanalytics.GameAnalytics.addResourceEvent(gameanalytics.EGAResourceFlowType.Sink, "gems", 100, "guns", "bigGun"); 104 | expect(countPatternFoundInBlocks("addResourceEvent")).toEqual(1); 105 | GameAnalytics("addResourceEvent", "Sink", "gems", 100, "guns", "bigGun"); 106 | expect(countPatternFoundInBlocks("addResourceEvent")).toEqual(2); 107 | }); 108 | }); 109 | 110 | describe("addProgressionEvent", function () { 111 | it("should be added to queue", function() { 112 | gameanalytics.GameAnalytics.addProgressionEvent(gameanalytics.EGAProgressionStatus.Start, "world1", "level1", "phase1", 1000); 113 | expect(countPatternFoundInBlocks("addProgressionEvent")).toEqual(1); 114 | GameAnalytics("addProgressionEvent", "Start", "world1", "level1", "phase1", 1000); 115 | expect(countPatternFoundInBlocks("addProgressionEvent")).toEqual(2); 116 | }); 117 | }); 118 | 119 | describe("addDesignEvent", function () { 120 | it("should be added to queue", function() { 121 | gameanalytics.GameAnalytics.addDesignEvent("eventId:string"); 122 | expect(countPatternFoundInBlocks("addDesignEvent")).toEqual(1); 123 | GameAnalytics("addDesignEvent", "eventId:string"); 124 | expect(countPatternFoundInBlocks("addDesignEvent")).toEqual(2); 125 | }); 126 | }); 127 | 128 | describe("addErrorEvent", function () { 129 | it("should be added to queue", function() { 130 | gameanalytics.GameAnalytics.addErrorEvent(gameanalytics.EGAErrorSeverity.Info, "test"); 131 | expect(countPatternFoundInBlocks("addErrorEvent")).toEqual(1); 132 | GameAnalytics("addErrorEvent", "Info", "test"); 133 | expect(countPatternFoundInBlocks("addErrorEvent")).toEqual(2); 134 | }); 135 | }); 136 | 137 | describe("setEnabledInfoLog", function () { 138 | it("should be added to queue", function() { 139 | gameanalytics.GameAnalytics.setEnabledInfoLog(true); 140 | expect(countPatternFoundInBlocks("setInfoLog")).toEqual(1); 141 | GameAnalytics("setEnabledInfoLog", false); 142 | expect(countPatternFoundInBlocks("setInfoLog")).toEqual(2); 143 | }); 144 | }); 145 | 146 | describe("setEnabledVerboseLog", function () { 147 | it("should be added to queue", function() { 148 | gameanalytics.GameAnalytics.setEnabledVerboseLog(true); 149 | expect(countPatternFoundInBlocks("setVerboseLog")).toEqual(1); 150 | GameAnalytics("setEnabledVerboseLog", false); 151 | expect(countPatternFoundInBlocks("setVerboseLog")).toEqual(2); 152 | }); 153 | }); 154 | 155 | describe("setEnabledManualSessionHandling", function () { 156 | it("should be added to queue", function() { 157 | gameanalytics.GameAnalytics.setEnabledManualSessionHandling(true); 158 | expect(countPatternFoundInBlocks("setManualSessionHandling")).toEqual(1); 159 | GameAnalytics("setEnabledManualSessionHandling", false); 160 | expect(countPatternFoundInBlocks("setManualSessionHandling")).toEqual(2); 161 | }); 162 | }); 163 | 164 | describe("setCustomDimension01", function () { 165 | it("should be added to queue", function() { 166 | gameanalytics.GameAnalytics.setCustomDimension01("ninja"); 167 | expect(countPatternFoundInBlocks("setCustomDimension01")).toEqual(1); 168 | GameAnalytics("setCustomDimension01", "ninja"); 169 | expect(countPatternFoundInBlocks("setCustomDimension01")).toEqual(2); 170 | }); 171 | }); 172 | 173 | describe("setCustomDimension02", function () { 174 | it("should be added to queue", function() { 175 | gameanalytics.GameAnalytics.setCustomDimension02("ninja"); 176 | expect(countPatternFoundInBlocks("setCustomDimension02")).toEqual(1); 177 | GameAnalytics("setCustomDimension02", "ninja"); 178 | expect(countPatternFoundInBlocks("setCustomDimension02")).toEqual(2); 179 | }); 180 | }); 181 | 182 | describe("setCustomDimension03", function () { 183 | it("should be added to queue", function() { 184 | gameanalytics.GameAnalytics.setCustomDimension03("ninja"); 185 | expect(countPatternFoundInBlocks("setCustomDimension03")).toEqual(1); 186 | GameAnalytics("setCustomDimension03", "ninja"); 187 | expect(countPatternFoundInBlocks("setCustomDimension03")).toEqual(2); 188 | }); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /test/testGAState.js: -------------------------------------------------------------------------------- 1 | describe("GAState", function () { 2 | describe("ValidateAndCleanCustomFields", function () { 3 | var GAState = gameanalytics.state.GAState; 4 | 5 | var map = {}; 6 | 7 | it("should be valid custom fields", function() { 8 | map = {}; 9 | for(var i = 0; i < 100; ++i) 10 | { 11 | map[getRandomString(4)] = getRandomString(4); 12 | } 13 | expect(Object.keys(GAState.validateAndCleanCustomFields(map)).length).toEqual(50); 14 | 15 | map = {}; 16 | for(var i = 0; i < 50; ++i) 17 | { 18 | map[getRandomString(4)] = getRandomString(4); 19 | } 20 | expect(Object.keys(GAState.validateAndCleanCustomFields(map)).length).toEqual(50); 21 | 22 | map = {}; 23 | map[getRandomString(4)] = ""; 24 | expect(Object.keys(GAState.validateAndCleanCustomFields(map)).length).toEqual(0); 25 | 26 | map = {}; 27 | map[getRandomString(4)] = getRandomString(257); 28 | expect(Object.keys(GAState.validateAndCleanCustomFields(map)).length).toEqual(0); 29 | 30 | map = {}; 31 | map[""] = getRandomString(4); 32 | expect(Object.keys(GAState.validateAndCleanCustomFields(map)).length).toEqual(0); 33 | 34 | map = {}; 35 | map["___"] = getRandomString(4); 36 | expect(Object.keys(GAState.validateAndCleanCustomFields(map)).length).toEqual(1); 37 | 38 | map = {}; 39 | map["_&_"] = getRandomString(4); 40 | expect(Object.keys(GAState.validateAndCleanCustomFields(map)).length).toEqual(0); 41 | 42 | map = {}; 43 | map[getRandomString(65)] = getRandomString(4); 44 | expect(Object.keys(GAState.validateAndCleanCustomFields(map)).length).toEqual(0); 45 | 46 | map = {}; 47 | map[getRandomString(4)] = 100; 48 | expect(Object.keys(GAState.validateAndCleanCustomFields(map)).length).toEqual(1); 49 | 50 | map = {}; 51 | map[getRandomString(4)] = [100]; 52 | map[getRandomString(4)] = true; 53 | expect(Object.keys(GAState.validateAndCleanCustomFields(map)).length).toEqual(0); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/testUtilities.js: -------------------------------------------------------------------------------- 1 | function getRandomString(numberOfCharacters) 2 | { 3 | var letters = "abcdefghijklmfalsepqrstuvwxyzABCDEFGHIJKLMfalsePQRSTUVWXYZ0123456789"; 4 | 5 | var result = ""; 6 | 7 | for (var i = 0; i < numberOfCharacters; i++) 8 | { 9 | result += letters.charAt(Math.floor(Math.random() * (letters.length - 1))); 10 | } 11 | 12 | return result; 13 | } 14 | 15 | function countPatternFoundInBlocks(pattern) 16 | { 17 | var GAThreading = gameanalytics.threading.GAThreading; 18 | var result = 0; 19 | for (var i = 0; i < GAThreading.instance.blocks._sortedKeys.length; i++) 20 | { 21 | for (var j = 0; j < GAThreading.instance.blocks._subQueues[GAThreading.instance.blocks._sortedKeys[i]].length; j++) 22 | { 23 | if(GAThreading.instance.blocks._subQueues[GAThreading.instance.blocks._sortedKeys[i]][j].block.toString().indexOf(pattern) != -1) 24 | { 25 | result++; 26 | } 27 | } 28 | } 29 | 30 | return result; 31 | } 32 | -------------------------------------------------------------------------------- /test/testValidator.js: -------------------------------------------------------------------------------- 1 | describe("Validator", function () { 2 | describe("Currency", function () { 3 | var GAValidator = gameanalytics.validators.GAValidator; 4 | 5 | it("should be valid currency", function() { 6 | expect(GAValidator.validateCurrency("USD")).toEqual(true); 7 | expect(GAValidator.validateCurrency("XXX")).toEqual(true); 8 | }); 9 | 10 | it("should be invalid currency", function() { 11 | expect(GAValidator.validateCurrency("usd")).toEqual(false); 12 | expect(GAValidator.validateCurrency("US")).toEqual(false); 13 | expect(GAValidator.validateCurrency("KR")).toEqual(false); 14 | expect(GAValidator.validateCurrency("USDOLLARS")).toEqual(false); 15 | expect(GAValidator.validateCurrency("$")).toEqual(false); 16 | expect(GAValidator.validateCurrency("")).toEqual(false); 17 | expect(GAValidator.validateCurrency(null)).toEqual(false); 18 | expect(GAValidator.validateCurrency(undefined)).toEqual(false); 19 | }); 20 | }); 21 | 22 | describe("ResourceCurrencies", function () { 23 | var GAValidator = gameanalytics.validators.GAValidator; 24 | 25 | it("should be valid resource currency", function() { 26 | expect(GAValidator.validateResourceCurrencies(["gems", "gold"])).toEqual(true); 27 | }); 28 | 29 | it("should be invalid resource currency", function() { 30 | expect(GAValidator.validateResourceCurrencies(["", "gold"])).toEqual(false); 31 | expect(GAValidator.validateResourceCurrencies([])).toEqual(false); 32 | expect(GAValidator.validateResourceCurrencies([null])).toEqual(false); 33 | expect(GAValidator.validateResourceCurrencies([undefined, "gold"])).toEqual(false); 34 | }); 35 | }); 36 | 37 | describe("ResourceItemTypes", function () { 38 | var GAValidator = gameanalytics.validators.GAValidator; 39 | 40 | it("should be valid resource item types", function() { 41 | expect(GAValidator.validateResourceItemTypes(["gems", "gold"])).toEqual(true); 42 | }); 43 | 44 | it("should be invalid resource item types", function() { 45 | expect(GAValidator.validateResourceItemTypes(["", "gold"])).toEqual(false); 46 | expect(GAValidator.validateResourceItemTypes([])).toEqual(false); 47 | expect(GAValidator.validateResourceItemTypes([null])).toEqual(false); 48 | expect(GAValidator.validateResourceItemTypes([undefined, "gold"])).toEqual(false); 49 | }); 50 | }); 51 | 52 | describe("ProgressionEvent", function () { 53 | var GAValidator = gameanalytics.validators.GAValidator; 54 | var EGAProgressionStatus = gameanalytics.EGAProgressionStatus; 55 | 56 | it("should be valid progression event", function() { 57 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "world_001", "level_001", "phase_001") == null).toEqual(true); 58 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "world_001", "level_001", "") == null).toEqual(true); 59 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "world_001", "level_001", null) == null).toEqual(true); 60 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "world_001", "", "") == null).toEqual(true); 61 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "world_001", undefined, undefined) == null).toEqual(true); 62 | }); 63 | 64 | it("should be invalid progression event", function() { 65 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "", "", "") == null).toEqual(false); 66 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, null, null, null) == null).toEqual(false); 67 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, undefined, undefined, undefined) == null).toEqual(false); 68 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "world_001", "", "phase_001") == null).toEqual(false); 69 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "world_001", null, "phase_001") == null).toEqual(false); 70 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "world_001", undefined, "phase_001") == null).toEqual(false); 71 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "", "level_001", "phase_001") == null).toEqual(false); 72 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, null, "level_001", "phase_001") == null).toEqual(false); 73 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, undefined, "level_001", "phase_001") == null).toEqual(false); 74 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "", "level_001", "") == null).toEqual(false); 75 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, null, "level_001", null) == null).toEqual(false); 76 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, undefined, "level_001", undefined) == null).toEqual(false); 77 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, "", "", "phase_001") == null).toEqual(false); 78 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, null, null, "phase_001") == null).toEqual(false); 79 | expect(GAValidator.validateProgressionEvent(EGAProgressionStatus.Start, undefined, undefined, "phase_001") == null).toEqual(false); 80 | }); 81 | }); 82 | 83 | describe("BusinessEvent", function () { 84 | var GAValidator = gameanalytics.validators.GAValidator; 85 | 86 | it("should be valid business event", function() { 87 | expect(GAValidator.validateBusinessEvent("USD", 99, "cartType", "itemType", "itemId") == null).toEqual(true); 88 | expect(GAValidator.validateBusinessEvent("USD", 99, "", "itemType", "itemId") == null).toEqual(true); 89 | expect(GAValidator.validateBusinessEvent("USD", 99, null, "itemType", "itemId") == null).toEqual(true); 90 | expect(GAValidator.validateBusinessEvent("USD", 99, undefined, "itemType", "itemId") == null).toEqual(true); 91 | expect(GAValidator.validateBusinessEvent("USD", 0, "cartType", "itemType", "itemId") == null).toEqual(true); 92 | }); 93 | 94 | it("should be invalid business event", function() { 95 | expect(GAValidator.validateBusinessEvent("USD", -99, "cartType", "itemType", "itemId") == null).toEqual(false); 96 | expect(GAValidator.validateBusinessEvent("USD", 99, "", "", "itemId") == null).toEqual(false); 97 | expect(GAValidator.validateBusinessEvent("USD", 99, "", null, "itemId") == null).toEqual(false); 98 | expect(GAValidator.validateBusinessEvent("USD", 99, "", undefined, "itemId") == null).toEqual(false); 99 | expect(GAValidator.validateBusinessEvent("USD", 99, "cartType", "itemType", "") == null).toEqual(false); 100 | expect(GAValidator.validateBusinessEvent("USD", 99, "cartType", "itemType", null) == null).toEqual(false); 101 | expect(GAValidator.validateBusinessEvent("USD", 99, "cartType", "itemType", undefined) == null).toEqual(false); 102 | }); 103 | }); 104 | 105 | describe("ResourceEvent", function () { 106 | var GAValidator = gameanalytics.validators.GAValidator; 107 | var EGAResourceFlowType = gameanalytics.EGAResourceFlowType; 108 | var GAState = gameanalytics.state.GAState; 109 | 110 | GAState.setAvailableResourceCurrencies(["gems", "gold"]); 111 | GAState.setAvailableResourceItemTypes(["guns", "powerups"]); 112 | 113 | it("should be valid resource event", function() { 114 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gems", 100, "guns", "item", GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(true); 115 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gold", 100, "powerups", "item", GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(true); 116 | }); 117 | 118 | it("should be invalid resource event", function() { 119 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "iron", 100, "guns", "item", GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(false); 120 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gems", 100, "cows", "item", GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(false); 121 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gems", 0, "guns", "item", GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(false); 122 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gems", -10, "guns", "item", GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(false); 123 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gems", 10, "guns", "", GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(false); 124 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gems", 10, "guns", null, GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(false); 125 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gems", 10, "guns", undefined, GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(false); 126 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gems", 10, "", "item", GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(false); 127 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gems", 10, null, "item", GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(false); 128 | expect(GAValidator.validateResourceEvent(EGAResourceFlowType.Sink, "gems", 10, undefined, "item", GAState.getAvailableResourceCurrencies(), GAState.getAvailableResourceItemTypes()) == null).toEqual(false); 129 | }); 130 | }); 131 | 132 | describe("DesignEvent", function () { 133 | var GAValidator = gameanalytics.validators.GAValidator; 134 | 135 | it("should be valid design event", function() { 136 | expect(GAValidator.validateDesignEvent("name:name") == null).toEqual(true); 137 | expect(GAValidator.validateDesignEvent("name:name:name") == null).toEqual(true); 138 | expect(GAValidator.validateDesignEvent("name:name:name:name") == null).toEqual(true); 139 | expect(GAValidator.validateDesignEvent("name:name:name:name:name") == null).toEqual(true); 140 | expect(GAValidator.validateDesignEvent("name:name") == null).toEqual(true); 141 | }); 142 | 143 | it("should be invalid design event", function() { 144 | expect(GAValidator.validateDesignEvent("") == null).toEqual(false); 145 | expect(GAValidator.validateDesignEvent(null) == null).toEqual(false); 146 | expect(GAValidator.validateDesignEvent(undefined) == null).toEqual(false); 147 | expect(GAValidator.validateDesignEvent("name:name:name:name:name:name") == null).toEqual(false); 148 | }); 149 | }); 150 | 151 | describe("ErrorEvent", function () { 152 | var GAValidator = gameanalytics.validators.GAValidator; 153 | var EGAErrorSeverity = gameanalytics.EGAErrorSeverity; 154 | 155 | it("should be valid error event", function() { 156 | expect(GAValidator.validateErrorEvent(EGAErrorSeverity.Error, "This is a message") == null).toEqual(true); 157 | expect(GAValidator.validateErrorEvent(EGAErrorSeverity.Error, "") == null).toEqual(true); 158 | expect(GAValidator.validateErrorEvent(EGAErrorSeverity.Error, undefined) == null).toEqual(true); 159 | }); 160 | 161 | it("should be invalid error event", function() { 162 | expect(GAValidator.validateErrorEvent(EGAErrorSeverity.Error, getRandomString(8193)) == null).toEqual(false); 163 | }); 164 | }); 165 | 166 | describe("SdkErrorEvent", function () { 167 | var GAValidator = gameanalytics.validators.GAValidator; 168 | var EGASdkErrorCategory = gameanalytics.events.EGASdkErrorCategory; 169 | var EGASdkErrorArea = gameanalytics.events.EGASdkErrorArea; 170 | var EGASdkErrorAction = gameanalytics.events.EGASdkErrorAction; 171 | 172 | it("should be valid sdk error event", function() { 173 | expect(GAValidator.validateSdkErrorEvent("c6cfc80ff69d1e7316bf1e0c8194eda6", "e0ae4809f70e2fa96916c7060f417ae53895f18d", EGASdkErrorCategory.EventValidation, EGASdkErrorArea.ResourceEvent, EGASdkErrorAction.InvalidFlowType)).toEqual(true); 174 | }); 175 | 176 | it("should be invalid sdk error event", function() { 177 | expect(GAValidator.validateSdkErrorEvent("", "e0ae4809f70e2fa96916c7060f417ae53895f18d", EGASdkErrorCategory.EventValidation, EGASdkErrorArea.ResourceEvent, EGASdkErrorAction.InvalidFlowType)).toEqual(false); 178 | }); 179 | }); 180 | 181 | describe("CustomDimensions", function () { 182 | var GAValidator = gameanalytics.validators.GAValidator; 183 | 184 | it("should be valid custom dimensions", function() { 185 | expect(GAValidator.validateCustomDimensions(["abc", "def", "ghi"])).toEqual(true); 186 | }); 187 | 188 | it("should be invalid custom dimensions", function() { 189 | expect(GAValidator.validateCustomDimensions(["abc", "def", "abc", "def", "abc", "def", "abc", "def", "abc", "def", "abc", "def", "abc", "def", "abc", "def", "abc", "def", "abc", "def", "abc", "def"])).toEqual(false); 190 | expect(GAValidator.validateCustomDimensions(["abc", ""])).toEqual(false); 191 | expect(GAValidator.validateCustomDimensions(["abc", null])).toEqual(false); 192 | expect(GAValidator.validateCustomDimensions(["abc", undefined])).toEqual(false); 193 | }); 194 | }); 195 | 196 | describe("SdkWrapperVersion", function () { 197 | var GAValidator = gameanalytics.validators.GAValidator; 198 | 199 | it("should be valid sdk wrapper version", function() { 200 | expect(GAValidator.validateSdkWrapperVersion("unity 1.2.3")).toEqual(true); 201 | expect(GAValidator.validateSdkWrapperVersion("unity 1233.101.0")).toEqual(true); 202 | expect(GAValidator.validateSdkWrapperVersion("unreal 1.2.3")).toEqual(true); 203 | }); 204 | 205 | it("should be invalid sdk wrapper version", function() { 206 | expect(GAValidator.validateSdkWrapperVersion("123")).toEqual(false); 207 | expect(GAValidator.validateSdkWrapperVersion("test 1.2.x")).toEqual(false); 208 | expect(GAValidator.validateSdkWrapperVersion("unkfalsewn 1.5.6")).toEqual(false); 209 | expect(GAValidator.validateSdkWrapperVersion("unity 1.2.3.4")).toEqual(false); 210 | expect(GAValidator.validateSdkWrapperVersion("corona1.2.3")).toEqual(false); 211 | expect(GAValidator.validateSdkWrapperVersion("unity x.2.3")).toEqual(false); 212 | expect(GAValidator.validateSdkWrapperVersion("unity 1.x.3")).toEqual(false); 213 | expect(GAValidator.validateSdkWrapperVersion("unity 1.2.x")).toEqual(false); 214 | expect(GAValidator.validateSdkWrapperVersion("unity 1.2.123456")).toEqual(false); 215 | }); 216 | }); 217 | 218 | describe("Build", function () { 219 | var GAValidator = gameanalytics.validators.GAValidator; 220 | 221 | it("should be valid build", function() { 222 | expect(GAValidator.validateBuild("alpha 1.2.3")).toEqual(true); 223 | expect(GAValidator.validateBuild("ALPHA 1.2.3")).toEqual(true); 224 | expect(GAValidator.validateBuild("TES# sdf.fd3")).toEqual(true); 225 | }); 226 | 227 | it("should be invalid build", function() { 228 | expect(GAValidator.validateBuild("")).toEqual(false); 229 | expect(GAValidator.validateBuild(null)).toEqual(false); 230 | expect(GAValidator.validateBuild(undefined)).toEqual(false); 231 | expect(GAValidator.validateBuild(getRandomString(40))).toEqual(false); 232 | }); 233 | }); 234 | 235 | describe("EngineVersion", function () { 236 | var GAValidator = gameanalytics.validators.GAValidator; 237 | 238 | it("should be valid engine version", function() { 239 | expect(GAValidator.validateEngineVersion("unity 1.2.3")).toEqual(true); 240 | expect(GAValidator.validateEngineVersion("unity 1.2")).toEqual(true); 241 | expect(GAValidator.validateEngineVersion("unity 1")).toEqual(true); 242 | expect(GAValidator.validateEngineVersion("unreal 1.2.3")).toEqual(true); 243 | expect(GAValidator.validateEngineVersion("cocos2d 1.2.3")).toEqual(true); 244 | }); 245 | 246 | it("should be invalid engine version", function() { 247 | expect(GAValidator.validateEngineVersion("")).toEqual(false); 248 | expect(GAValidator.validateEngineVersion(null)).toEqual(false); 249 | expect(GAValidator.validateEngineVersion(undefined)).toEqual(false); 250 | expect(GAValidator.validateEngineVersion(getRandomString(40))).toEqual(false); 251 | expect(GAValidator.validateEngineVersion("uni 1.2.3")).toEqual(false); 252 | expect(GAValidator.validateEngineVersion("unity 123456.2.3")).toEqual(false); 253 | expect(GAValidator.validateEngineVersion("unity1.2.3")).toEqual(false); 254 | expect(GAValidator.validateEngineVersion("unity 1.2.3.4")).toEqual(false); 255 | expect(GAValidator.validateEngineVersion("Unity 1.2.3")).toEqual(false); 256 | expect(GAValidator.validateEngineVersion("UNITY 1.2.3")).toEqual(false); 257 | expect(GAValidator.validateEngineVersion("marmalade 1.2.3")).toEqual(false); 258 | expect(GAValidator.validateEngineVersion("xamarin 1.2.3")).toEqual(false); 259 | }); 260 | }); 261 | 262 | describe("Keys", function () { 263 | var GAValidator = gameanalytics.validators.GAValidator; 264 | var validGameKey = "123456789012345678901234567890ab"; 265 | var validSecretKey = "123456789012345678901234567890123456789a"; 266 | var tooLongKey = "123456789012345678901234567890123456789abcdefg"; 267 | 268 | it("should be valid keys", function() { 269 | expect(GAValidator.validateKeys(validGameKey, validSecretKey)).toEqual(true); 270 | }); 271 | 272 | it("should be invalid keys", function() { 273 | expect(GAValidator.validateKeys(validGameKey, "")).toEqual(false); 274 | expect(GAValidator.validateKeys(validGameKey, null)).toEqual(false); 275 | expect(GAValidator.validateKeys(validGameKey, undefined)).toEqual(false); 276 | expect(GAValidator.validateKeys(validGameKey, "123")).toEqual(false); 277 | expect(GAValidator.validateKeys(validGameKey, tooLongKey)).toEqual(false); 278 | expect(GAValidator.validateKeys("", validSecretKey)).toEqual(false); 279 | expect(GAValidator.validateKeys(null, validSecretKey)).toEqual(false); 280 | expect(GAValidator.validateKeys(undefined, validSecretKey)).toEqual(false); 281 | expect(GAValidator.validateKeys("123", validSecretKey)).toEqual(false); 282 | expect(GAValidator.validateKeys(tooLongKey, validSecretKey)).toEqual(false); 283 | }); 284 | }); 285 | 286 | describe("EventPartLength", function () { 287 | var GAValidator = gameanalytics.validators.GAValidator; 288 | 289 | it("should be valid event part length", function() { 290 | expect(GAValidator.validateEventPartLength("sdfdf", false)).toEqual(true); 291 | expect(GAValidator.validateEventPartLength("", true)).toEqual(true); 292 | expect(GAValidator.validateEventPartLength(null, true)).toEqual(true); 293 | expect(GAValidator.validateEventPartLength(undefined, true)).toEqual(true); 294 | expect(GAValidator.validateEventPartLength(getRandomString(32), true)).toEqual(true); 295 | expect(GAValidator.validateEventPartLength(getRandomString(40), true)).toEqual(true); 296 | expect(GAValidator.validateEventPartLength(getRandomString(40), false)).toEqual(true); 297 | }); 298 | 299 | it("should be invalid event part length", function() { 300 | expect(GAValidator.validateEventPartLength(getRandomString(80), false)).toEqual(false); 301 | expect(GAValidator.validateEventPartLength(getRandomString(80), true)).toEqual(false); 302 | expect(GAValidator.validateEventPartLength("", false)).toEqual(false); 303 | expect(GAValidator.validateEventPartLength(null, false)).toEqual(false); 304 | expect(GAValidator.validateEventPartLength(undefined, false)).toEqual(false); 305 | }); 306 | }); 307 | 308 | describe("EventPartCharacters", function () { 309 | var GAValidator = gameanalytics.validators.GAValidator; 310 | 311 | it("should be valid event part characters", function() { 312 | expect(GAValidator.validateEventPartCharacters("sdfdffdgdfg")).toEqual(true); 313 | }); 314 | 315 | it("should be invalid event part characters", function() { 316 | expect(GAValidator.validateEventPartCharacters("øææ")).toEqual(false); 317 | expect(GAValidator.validateEventPartCharacters("")).toEqual(false); 318 | expect(GAValidator.validateEventPartCharacters(null)).toEqual(false); 319 | expect(GAValidator.validateEventPartCharacters(undefined)).toEqual(false); 320 | expect(GAValidator.validateEventPartCharacters("*")).toEqual(false); 321 | expect(GAValidator.validateEventPartCharacters("))&%")).toEqual(false); 322 | }); 323 | }); 324 | 325 | describe("EventIdLength", function () { 326 | var GAValidator = gameanalytics.validators.GAValidator; 327 | 328 | it("should be valid event id length", function() { 329 | expect(GAValidator.validateEventIdLength(getRandomString(40))).toEqual(true); 330 | expect(GAValidator.validateEventIdLength(getRandomString(32))).toEqual(true); 331 | expect(GAValidator.validateEventIdLength("sdfdf")).toEqual(true); 332 | }); 333 | 334 | it("should be invalid event id length", function() { 335 | expect(GAValidator.validateEventIdLength(getRandomString(80))).toEqual(false); 336 | expect(GAValidator.validateEventIdLength("")).toEqual(false); 337 | expect(GAValidator.validateEventIdLength(null)).toEqual(false); 338 | expect(GAValidator.validateEventIdLength(undefined)).toEqual(false); 339 | }); 340 | }); 341 | 342 | describe("EventIdCharacters", function () { 343 | var GAValidator = gameanalytics.validators.GAValidator; 344 | 345 | it("should be valid event id characters", function() { 346 | expect(GAValidator.validateEventIdCharacters("GHj:df(g?h d_fk7-58.9)3!47")).toEqual(true); 347 | }); 348 | 349 | it("should be invalid event id characters", function() { 350 | expect(GAValidator.validateEventIdCharacters("GHj:df(g?h d_fk,7-58.9)3!47")).toEqual(false); 351 | expect(GAValidator.validateEventIdCharacters("")).toEqual(false); 352 | expect(GAValidator.validateEventIdCharacters(null)).toEqual(false); 353 | expect(GAValidator.validateEventIdCharacters(undefined)).toEqual(false); 354 | }); 355 | }); 356 | 357 | describe("ShortString", function () { 358 | var GAValidator = gameanalytics.validators.GAValidator; 359 | 360 | it("should be valid short string", function() { 361 | expect(GAValidator.validateShortString(getRandomString(32), false)).toEqual(true); 362 | expect(GAValidator.validateShortString(getRandomString(32), true)).toEqual(true); 363 | expect(GAValidator.validateShortString(getRandomString(10), false)).toEqual(true); 364 | expect(GAValidator.validateShortString(getRandomString(10), true)).toEqual(true); 365 | expect(GAValidator.validateShortString("", true)).toEqual(true); 366 | expect(GAValidator.validateShortString(null, true)).toEqual(true); 367 | expect(GAValidator.validateShortString(undefined, true)).toEqual(true); 368 | }); 369 | 370 | it("should be invalid short string", function() { 371 | expect(GAValidator.validateShortString(getRandomString(40), false)).toEqual(false); 372 | expect(GAValidator.validateShortString(getRandomString(40), true)).toEqual(false); 373 | expect(GAValidator.validateShortString("", false)).toEqual(false); 374 | expect(GAValidator.validateShortString(null, false)).toEqual(false); 375 | expect(GAValidator.validateShortString(undefined, false)).toEqual(false); 376 | }); 377 | }); 378 | 379 | describe("String", function () { 380 | var GAValidator = gameanalytics.validators.GAValidator; 381 | 382 | it("should be valid string", function() { 383 | expect(GAValidator.validateString(getRandomString(64), false)).toEqual(true); 384 | expect(GAValidator.validateString(getRandomString(64), true)).toEqual(true); 385 | expect(GAValidator.validateString(getRandomString(10), false)).toEqual(true); 386 | expect(GAValidator.validateString(getRandomString(10), true)).toEqual(true); 387 | expect(GAValidator.validateString("", true)).toEqual(true); 388 | expect(GAValidator.validateString(null, true)).toEqual(true); 389 | expect(GAValidator.validateString(undefined, true)).toEqual(true); 390 | }); 391 | 392 | it("should be invalid string", function() { 393 | expect(GAValidator.validateString(getRandomString(80), false)).toEqual(false); 394 | expect(GAValidator.validateString(getRandomString(80), true)).toEqual(false); 395 | expect(GAValidator.validateString("", false)).toEqual(false); 396 | expect(GAValidator.validateString(null, false)).toEqual(false); 397 | expect(GAValidator.validateString(undefined, false)).toEqual(false); 398 | }); 399 | }); 400 | 401 | describe("String", function () { 402 | var GAValidator = gameanalytics.validators.GAValidator; 403 | 404 | it("should be valid string", function() { 405 | expect(GAValidator.validateString(getRandomString(64), false)).toEqual(true); 406 | expect(GAValidator.validateString(getRandomString(64), true)).toEqual(true); 407 | expect(GAValidator.validateString(getRandomString(10), false)).toEqual(true); 408 | expect(GAValidator.validateString(getRandomString(10), true)).toEqual(true); 409 | expect(GAValidator.validateString("", true)).toEqual(true); 410 | expect(GAValidator.validateString(null, true)).toEqual(true); 411 | expect(GAValidator.validateString(undefined, true)).toEqual(true); 412 | }); 413 | 414 | it("should be invalid string", function() { 415 | expect(GAValidator.validateString(getRandomString(80), false)).toEqual(false); 416 | expect(GAValidator.validateString(getRandomString(80), true)).toEqual(false); 417 | expect(GAValidator.validateString("", false)).toEqual(false); 418 | expect(GAValidator.validateString(null, false)).toEqual(false); 419 | expect(GAValidator.validateString(undefined, false)).toEqual(false); 420 | }); 421 | }); 422 | 423 | describe("LongString", function () { 424 | var GAValidator = gameanalytics.validators.GAValidator; 425 | 426 | it("should be valid long string", function() { 427 | expect(GAValidator.validateLongString(getRandomString(8192), false)).toEqual(true); 428 | expect(GAValidator.validateLongString(getRandomString(8192), true)).toEqual(true); 429 | expect(GAValidator.validateLongString(getRandomString(10), false)).toEqual(true); 430 | expect(GAValidator.validateLongString(getRandomString(10), true)).toEqual(true); 431 | expect(GAValidator.validateLongString("", true)).toEqual(true); 432 | expect(GAValidator.validateLongString(null, true)).toEqual(true); 433 | expect(GAValidator.validateLongString(undefined, true)).toEqual(true); 434 | }); 435 | 436 | it("should be invalid long string", function() { 437 | expect(GAValidator.validateLongString(getRandomString(8193), false)).toEqual(false); 438 | expect(GAValidator.validateLongString(getRandomString(8193), true)).toEqual(false); 439 | expect(GAValidator.validateLongString("", false)).toEqual(false); 440 | expect(GAValidator.validateLongString(null, false)).toEqual(false); 441 | expect(GAValidator.validateLongString(undefined, false)).toEqual(false); 442 | }); 443 | }); 444 | 445 | describe("ArrayOfStrings", function () { 446 | var GAValidator = gameanalytics.validators.GAValidator; 447 | 448 | it("should be valid array of strings", function() { 449 | expect(GAValidator.validateArrayOfStrings(3, 10, false, "test", [getRandomString(3), getRandomString(10), getRandomString(7)])).toEqual(true); 450 | expect(GAValidator.validateArrayOfStrings(3, 10, true, "test", [])).toEqual(true); 451 | }); 452 | 453 | it("should be invalid array of strings", function() { 454 | expect(GAValidator.validateArrayOfStrings(3, 10, false, "test", [getRandomString(3), getRandomString(12), getRandomString(7)])).toEqual(false); 455 | expect(GAValidator.validateArrayOfStrings(3, 10, false, "test", [getRandomString(3), "", getRandomString(7)])).toEqual(false); 456 | expect(GAValidator.validateArrayOfStrings(3, 10, false, "test", [getRandomString(3), null, getRandomString(7)])).toEqual(false); 457 | expect(GAValidator.validateArrayOfStrings(3, 10, false, "test", [getRandomString(3), undefined, getRandomString(7)])).toEqual(false); 458 | expect(GAValidator.validateArrayOfStrings(2, 10, false, "test", [getRandomString(3), getRandomString(10), getRandomString(7)])).toEqual(false); 459 | expect(GAValidator.validateArrayOfStrings(3, 10, false, "test", [])).toEqual(false); 460 | expect(GAValidator.validateArrayOfStrings(3, 10, false, "test", null)).toEqual(false); 461 | expect(GAValidator.validateArrayOfStrings(3, 10, false, "test", undefined)).toEqual(false); 462 | }); 463 | }); 464 | 465 | describe("ClientTs", function () { 466 | var GAValidator = gameanalytics.validators.GAValidator; 467 | var GAUtilities = gameanalytics.utilities.GAUtilities; 468 | 469 | it("should be valid client ts", function() { 470 | expect(GAValidator.validateClientTs(GAUtilities.timeIntervalSince1970())).toEqual(true); 471 | expect(GAValidator.validateClientTs(4294967295)).toEqual(true); 472 | }); 473 | 474 | it("should be invalid client ts", function() { 475 | expect(GAValidator.validateClientTs(-4294967295)).toEqual(false); 476 | }); 477 | }); 478 | 479 | describe("UserId", function () { 480 | var GAValidator = gameanalytics.validators.GAValidator; 481 | 482 | it("should be valid user id", function() { 483 | expect(GAValidator.validateUserId("fhjkdfghdfjkgh")).toEqual(true); 484 | }); 485 | 486 | it("should be invalid user id", function() { 487 | expect(GAValidator.validateUserId("")).toEqual(false); 488 | expect(GAValidator.validateUserId(null)).toEqual(false); 489 | expect(GAValidator.validateUserId(undefined)).toEqual(false); 490 | }); 491 | }); 492 | }); 493 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "sourceMap": true, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noEmit": false, 8 | "outDir": "./dist", 9 | "declaration": true, 10 | "outFile": "./dist/GameAnalytics.js" 11 | }, 12 | "files": [ 13 | "./src/Enums.ts", 14 | "./src/PublicEnums.ts", 15 | "./src/logging/GALogger.ts", 16 | "./src/utilities/GAUtilities.ts", 17 | "./src/validators/GAValidator.ts", 18 | "./src/device/GADevice.ts", 19 | "./src/threading/TimedBlock.ts", 20 | "./src/threading/PriorityQueue.ts", 21 | "./src/store/GAStore.ts", 22 | "./src/state/GAState.ts", 23 | "./src/tasks/SdkErrorTask.ts", 24 | "./src/http/GAHTTPApi.ts", 25 | "./src/events/GAEvents.ts", 26 | "./src/threading/GAThreading.ts", 27 | "./src/GameAnalytics.ts" 28 | ], 29 | "include": [ 30 | "./vendor/*.d.ts", 31 | "./src/ga.d.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /vendor/bundle.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, 8 | r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 9 | 32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, 10 | 2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}}, 11 | u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;gn;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]= 15 | c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; 16 | d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math); 17 | (function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join("")},parse:function(b){var e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c));for(var c=[],a=0,d=0;d< 27 | e;d++)if(d%4){var g=f.indexOf(b.charAt(d-1))<<2*(d%4),h=f.indexOf(b.charAt(d))>>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return j.create(c,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); 28 | -------------------------------------------------------------------------------- /vendor/cryptojs.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for CryptoJS 3.1.2 2 | // Project: https://code.google.com/p/crypto-js/ 3 | // Definitions by: Gia Bảo @ Sân Đình 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | declare var CryptoJS: CryptoJS.CryptoJSStatic; 7 | 8 | declare module CryptoJS{ 9 | module lib{ 10 | interface Base{ 11 | extend(overrides: Object): Object 12 | init(...args: any[]): void 13 | //arguments of create() is same as init(). This is true for all subclasses 14 | create(...args: any[]): Base 15 | mixIn(properties: Object): void 16 | clone(): Base 17 | } 18 | 19 | interface WordArray extends Base{ 20 | words: number[] 21 | sigBytes: number 22 | init(words?: number[], sigBytes?: number): void 23 | create(words?: number[], sigBytes?: number): WordArray 24 | 25 | init(typedArray: ArrayBuffer): void 26 | init(typedArray: Int8Array): void 27 | 28 | //Because TypeScript uses a structural type system then we don't need (& can't) 29 | //declare oveload function init, create for the following type (same as Int8Array): 30 | //then Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array 31 | //Note also: Uint8ClampedArray is not defined in lib.d.ts & not supported in IE 32 | //@see http://compatibility.shwups-cms.ch/en/home?&property=Uint8ClampedArray 33 | 34 | create(typedArray: ArrayBuffer): WordArray 35 | create(typedArray: Int8Array): WordArray 36 | 37 | toString(encoder?: enc.IEncoder): string 38 | concat(wordArray: WordArray): WordArray 39 | clamp(): void 40 | clone(): WordArray 41 | random(nBytes: number): WordArray 42 | } 43 | 44 | interface BufferedBlockAlgorithm extends Base{ 45 | reset(): void 46 | clone(): BufferedBlockAlgorithm 47 | } 48 | 49 | //tparam C - Configuration type 50 | interface IHasher extends BufferedBlockAlgorithm{ 51 | cfg: C 52 | init(cfg?: C): void 53 | create(cfg?: C): IHasher 54 | 55 | update(messageUpdate: WordArray): Hasher 56 | update(messageUpdate: string): Hasher 57 | 58 | finalize(messageUpdate?: WordArray): WordArray 59 | finalize(messageUpdate?: string): WordArray 60 | 61 | blockSize: number 62 | 63 | _createHelper(hasher: Hasher): IHasherHelper 64 | _createHmacHelper(hasher: Hasher): IHasherHmacHelper 65 | 66 | clone(): IHasher 67 | } 68 | interface Hasher extends IHasher{} 69 | 70 | //tparam C - Configuration type 71 | interface IHasherHelper{ 72 | (message: WordArray, cfg?: C): WordArray 73 | (message: string, cfg?: C): WordArray 74 | } 75 | interface HasherHelper extends IHasherHelper{} 76 | 77 | interface IHasherHmacHelper{ 78 | (message: WordArray, key: WordArray): WordArray 79 | (message: string, key: WordArray): WordArray 80 | (message: WordArray, key: string): WordArray 81 | (message: string, key: string): WordArray 82 | } 83 | 84 | //tparam C - Configuration type 85 | interface ICipher extends BufferedBlockAlgorithm{ 86 | cfg: C 87 | createEncryptor(key: WordArray, cfg?: C): ICipher 88 | createDecryptor(key: WordArray, cfg?: C): ICipher 89 | 90 | create(xformMode?: number, key?: WordArray, cfg?: C): ICipher 91 | init(xformMode?: number, key?: WordArray, cfg?: C): void 92 | 93 | process(dataUpdate: WordArray): WordArray 94 | process(dataUpdate: string): WordArray 95 | 96 | finalize(dataUpdate?: WordArray): WordArray 97 | finalize(dataUpdate?: string): WordArray 98 | 99 | keySize: number 100 | ivSize: number 101 | 102 | _createHelper(cipher: Cipher): ICipherHelper 103 | 104 | clone(): ICipher 105 | } 106 | interface Cipher extends ICipher{} 107 | 108 | interface IStreamCipher extends ICipher{ 109 | drop?: number; 110 | 111 | createEncryptor(key: WordArray, cfg?: C): IStreamCipher 112 | createDecryptor(key: WordArray, cfg?: C): IStreamCipher 113 | 114 | create(xformMode?: number, key?: WordArray, cfg?: C): IStreamCipher 115 | 116 | blockSize: number 117 | } 118 | interface StreamCipher extends IStreamCipher{} 119 | 120 | interface BlockCipherMode extends Base{ 121 | createEncryptor(cipher: Cipher, iv: number[]): mode.IBlockCipherEncryptor 122 | createDecryptor(cipher: Cipher, iv: number[]): mode.IBlockCipherDecryptor 123 | init(cipher?: Cipher, iv?: number[]): void 124 | create(cipher?: Cipher, iv?: number[]): BlockCipherMode 125 | } 126 | 127 | //BlockCipher has interface same as IStreamCipher 128 | interface BlockCipher extends IStreamCipher{} 129 | 130 | interface IBlockCipherCfg{ 131 | mode?: mode.IBlockCipherModeImpl //default CBC 132 | padding?: pad.IPaddingImpl //default Pkcs7 133 | } 134 | 135 | interface CipherParamsData{ 136 | ciphertext?: lib.WordArray 137 | key?: lib.WordArray 138 | iv?: lib.WordArray 139 | salt?: lib.WordArray 140 | algorithm?: Cipher 141 | mode?: mode.IBlockCipherModeImpl 142 | padding?: pad.IPaddingImpl 143 | blockSize?: number 144 | formatter?: format.IFormatter 145 | } 146 | 147 | interface CipherParams extends Base, CipherParamsData{ 148 | init(cipherParams?: CipherParamsData): void 149 | create(cipherParams?: CipherParamsData): CipherParams 150 | toString(formatter?: format.IFormatter): string 151 | } 152 | 153 | //tparam C - Configuration type 154 | interface ISerializableCipher extends Base{ 155 | cfg: C 156 | encrypt(cipher: Cipher, message: WordArray, key: WordArray, cfg?: C): CipherParams 157 | encrypt(cipher: Cipher, message: string, key: WordArray, cfg?: C): CipherParams 158 | 159 | decrypt(cipher: Cipher, ciphertext: CipherParamsData, key: WordArray, cfg?: C): WordArray 160 | decrypt(cipher: Cipher, ciphertext: string, key: WordArray, cfg?: C): WordArray 161 | } 162 | 163 | interface SerializableCipher extends ISerializableCipher{} 164 | interface ISerializableCipherCfg{ 165 | format?: format.IFormatter //default OpenSSLFormatter 166 | } 167 | 168 | interface IPasswordBasedCipher extends Base{ 169 | cfg: C 170 | encrypt(cipher: Cipher, message: WordArray, password: string, cfg?: C): CipherParams 171 | encrypt(cipher: Cipher, message: string, password: string, cfg?: C): CipherParams 172 | 173 | decrypt(cipher: Cipher, ciphertext: CipherParamsData, password: string, cfg?: C): WordArray 174 | decrypt(cipher: Cipher, ciphertext: string, password: string, cfg?: C): WordArray 175 | } 176 | 177 | interface PasswordBasedCipher extends IPasswordBasedCipher{} 178 | interface IPasswordBasedCipherCfg extends ISerializableCipherCfg{ 179 | kdf?: kdf.IKdfImpl //default OpenSSLKdf 180 | } 181 | 182 | /** see Cipher._createHelper */ 183 | interface ICipherHelper{ 184 | encrypt(message: WordArray, key: WordArray, cfg?: C): CipherParams 185 | encrypt(message: string, key: WordArray, cfg?: C): CipherParams 186 | encrypt(message: WordArray, password: string, cfg?: C): CipherParams 187 | encrypt(message: string, password: string, cfg?: C): CipherParams 188 | 189 | decrypt(ciphertext: CipherParamsData, key: WordArray, cfg?: C): WordArray 190 | decrypt(ciphertext: string, key: WordArray, cfg?: C): WordArray 191 | decrypt(ciphertext: CipherParamsData, password: string, cfg?: C): WordArray 192 | decrypt(ciphertext: string, password: string, cfg?: C): WordArray 193 | } 194 | 195 | interface CipherHelper extends ICipherHelper{} 196 | interface LibStatic{ 197 | Base: lib.Base 198 | WordArray: lib.WordArray 199 | CipherParams: lib.CipherParams 200 | SerializableCipher: lib.SerializableCipher 201 | PasswordBasedCipher: lib.PasswordBasedCipher 202 | } 203 | } 204 | 205 | module enc{ 206 | interface IEncoder{ 207 | stringify(wordArray: lib.WordArray): string 208 | } 209 | interface IDecoder{ 210 | parse(s: string): lib.WordArray 211 | } 212 | interface ICoder extends IEncoder, IDecoder {} 213 | 214 | interface EncStatic{ 215 | Hex: ICoder 216 | Latin1: ICoder 217 | Utf8: ICoder 218 | Base64: ICoder 219 | Utf16: ICoder 220 | Utf16BE: ICoder 221 | Utf16LE: ICoder 222 | } 223 | } 224 | 225 | module kdf{ 226 | interface KdfStatic{ 227 | OpenSSL: IKdfImpl 228 | } 229 | 230 | interface IKdfImpl{ 231 | execute(password: string, keySize: number, ivSize: number, salt?: lib.WordArray): lib.CipherParams 232 | execute(password: string, keySize: number, ivSize: number, salt?: string): lib.CipherParams 233 | } 234 | } 235 | 236 | module format{ 237 | interface FormatStatic{ 238 | OpenSSL: IFormatter 239 | Hex: IFormatter 240 | } 241 | 242 | interface IFormatter{ 243 | stringify(cipherParams: lib.CipherParamsData): string 244 | parse(s: string): lib.CipherParams 245 | } 246 | } 247 | 248 | module algo{ 249 | interface AlgoStatic{ 250 | AES: algo.AES 251 | DES: algo.DES 252 | TripleDES: algo.TripleDES 253 | 254 | RabbitLegacy: algo.RabbitLegacy 255 | Rabbit: algo.Rabbit 256 | RC4: algo.RC4 257 | 258 | MD5: algo.MD5 259 | RIPEMD160: algo.RIPEMD160 260 | SHA1: algo.SHA1 261 | SHA256: algo.SHA256 262 | SHA224: algo.SHA224 263 | SHA384: algo.SHA384 264 | SHA512: algo.SHA512 265 | 266 | SHA3: algo.SHA3 267 | 268 | HMAC: algo.HMAC 269 | 270 | EvpKDF: algo.EvpKDF 271 | PBKDF2: algo.PBKDF2 272 | 273 | RC4Drop: algo.RC4Drop 274 | } 275 | 276 | interface IBlockCipherImpl extends lib.BlockCipher{ 277 | encryptBlock(M: number[], offset: number): void 278 | decryptBlock(M: number[], offset: number): void 279 | 280 | createEncryptor(key: lib.WordArray, cfg?: lib.IBlockCipherCfg): IBlockCipherImpl 281 | createDecryptor(key: lib.WordArray, cfg?: lib.IBlockCipherCfg): IBlockCipherImpl 282 | 283 | create(xformMode?: number, key?: lib.WordArray, cfg?: lib.IBlockCipherCfg): IBlockCipherImpl 284 | } 285 | 286 | interface AES extends IBlockCipherImpl{} 287 | interface DES extends IBlockCipherImpl{} 288 | interface TripleDES extends IBlockCipherImpl{} 289 | 290 | interface RabbitLegacy extends lib.StreamCipher{} 291 | interface Rabbit extends lib.StreamCipher{} 292 | interface RC4 extends lib.StreamCipher{} 293 | 294 | interface MD5 extends lib.Hasher{} 295 | interface RIPEMD160 extends lib.Hasher{} 296 | interface SHA1 extends lib.Hasher{} 297 | interface SHA256 extends lib.Hasher{} 298 | interface SHA224 extends lib.Hasher{} 299 | interface SHA384 extends lib.Hasher{} 300 | interface SHA512 extends lib.Hasher{} 301 | 302 | interface SHA3 extends lib.IHasher{} 303 | interface ISHA3Cfg{ 304 | outputLength?: number //default 512 305 | } 306 | 307 | interface HMAC extends lib.Base{ 308 | init(hasher?: lib.Hasher, key?: lib.WordArray): void 309 | init(hasher?: lib.Hasher, key?: string): void 310 | create(hasher?: lib.Hasher, key?: lib.WordArray): HMAC 311 | create(hasher?: lib.Hasher, key?: string): HMAC 312 | 313 | update(messageUpdate: lib.WordArray): HMAC 314 | update(messageUpdate: string): HMAC 315 | 316 | finalize(messageUpdate?: lib.WordArray): lib.WordArray 317 | finalize(messageUpdate?: string): lib.WordArray 318 | } 319 | 320 | interface EvpKDF extends lib.Base{ 321 | cfg: IEvpKDFCfg 322 | init(cfg?: IEvpKDFCfg): void 323 | create(cfg?: IEvpKDFCfg): EvpKDF 324 | compute(password: lib.WordArray, salt: lib.WordArray): lib.WordArray 325 | compute(password: string, salt: lib.WordArray): lib.WordArray 326 | compute(password: lib.WordArray, salt: string): lib.WordArray 327 | compute(password: string, salt: string): lib.WordArray 328 | } 329 | interface IEvpKDFCfg{ 330 | keySize?: number //default 128/32 331 | hasher?: lib.Hasher //default MD5, or SHA1 with PBKDF2 332 | iterations?: number //default 1 333 | } 334 | interface IEvpKDFHelper{ 335 | (password: lib.WordArray, salt: lib.WordArray, cfg?: IEvpKDFCfg): lib.WordArray 336 | (password: string, salt: lib.WordArray, cfg?: IEvpKDFCfg): lib.WordArray 337 | (password: lib.WordArray, salt: string, cfg?: IEvpKDFCfg): lib.WordArray 338 | (password: string, salt: string, cfg?: IEvpKDFCfg): lib.WordArray 339 | } 340 | 341 | interface PBKDF2 extends EvpKDF{} //PBKDF2 is same as EvpKDF 342 | 343 | interface RC4Drop extends RC4 { } 344 | } 345 | 346 | module mode{ 347 | interface ModeStatic{ 348 | CBC: mode.CBC 349 | CFB: mode.CFB 350 | CTR: mode.CTR 351 | CTRGladman: mode.CTRGladman 352 | ECB: mode.ECB 353 | OFB: mode.OFB 354 | } 355 | 356 | interface IBlockCipherEncryptor extends lib.BlockCipherMode{ 357 | processBlock(words: number[], offset: number): void 358 | } 359 | interface IBlockCipherDecryptor extends lib.BlockCipherMode{ //exactly as IBlockCipherEncryptor 360 | processBlock(words: number[], offset: number): void 361 | } 362 | interface IBlockCipherModeImpl extends lib.BlockCipherMode{ 363 | Encryptor: IBlockCipherEncryptor 364 | Decryptor: IBlockCipherDecryptor 365 | } 366 | 367 | interface CBC extends IBlockCipherModeImpl{} 368 | interface CFB extends IBlockCipherModeImpl{} 369 | interface CTR extends IBlockCipherModeImpl{} 370 | interface CTRGladman extends IBlockCipherModeImpl{} 371 | interface ECB extends IBlockCipherModeImpl{} 372 | interface OFB extends IBlockCipherModeImpl{} 373 | } 374 | 375 | module pad{ 376 | interface PadStatic{ 377 | Pkcs7: pad.Pkcs7 378 | AnsiX923: pad.AnsiX923 379 | Iso10126: pad.Iso10126 380 | Iso97971: pad.Iso97971 381 | ZeroPadding: pad.ZeroPadding 382 | NoPadding: pad.NoPadding 383 | } 384 | 385 | interface IPaddingImpl{ 386 | pad(data: lib.WordArray, blockSize: number): void 387 | unpad(data: lib.WordArray): void 388 | } 389 | 390 | interface Pkcs7 extends IPaddingImpl{} 391 | interface AnsiX923 extends IPaddingImpl{} 392 | interface Iso10126 extends IPaddingImpl{} 393 | interface Iso97971 extends IPaddingImpl{} 394 | interface ZeroPadding extends IPaddingImpl{} 395 | interface NoPadding extends IPaddingImpl{} 396 | } 397 | 398 | module x64{ 399 | interface X64Static{ 400 | Word: x64.Word 401 | WordArray: x64.WordArray 402 | } 403 | 404 | interface Word extends lib.Base{ 405 | high: number 406 | low: number 407 | 408 | init(high?: number, low?: number): void 409 | create(high?: number, low?: number): Word 410 | } 411 | 412 | interface WordArray extends lib.Base{ 413 | words: Word[] 414 | sigBytes: number 415 | 416 | init(words?: Word[], sigBytes?: number): void 417 | create(words?: Word[], sigBytes?: number): WordArray 418 | toX32(): lib.WordArray 419 | clone(): WordArray 420 | } 421 | } 422 | 423 | interface CryptoJSStatic{ 424 | lib: lib.LibStatic 425 | enc: enc.EncStatic 426 | kdf: kdf.KdfStatic 427 | format: format.FormatStatic 428 | algo: algo.AlgoStatic 429 | mode: mode.ModeStatic 430 | pad: pad.PadStatic 431 | x64: x64.X64Static 432 | 433 | AES: CryptoJS.lib.ICipherHelper 434 | DES: CryptoJS.lib.ICipherHelper 435 | TripleDES: CryptoJS.lib.ICipherHelper 436 | 437 | RabbitLegacy: CryptoJS.lib.CipherHelper 438 | Rabbit: CryptoJS.lib.CipherHelper 439 | RC4: CryptoJS.lib.CipherHelper 440 | RC4Drop: CryptoJS.lib.ICipherHelper 441 | 442 | MD5: CryptoJS.lib.HasherHelper 443 | HmacMD5: CryptoJS.lib.IHasherHmacHelper 444 | RIPEMD160: CryptoJS.lib.HasherHelper 445 | HmacRIPEMD160: CryptoJS.lib.IHasherHmacHelper 446 | SHA1: CryptoJS.lib.HasherHelper 447 | HmacSHA1: CryptoJS.lib.IHasherHmacHelper 448 | SHA256: CryptoJS.lib.HasherHelper 449 | HmacSHA256: CryptoJS.lib.IHasherHmacHelper 450 | SHA224: CryptoJS.lib.HasherHelper 451 | HmacSHA224: CryptoJS.lib.IHasherHmacHelper 452 | SHA512: CryptoJS.lib.HasherHelper 453 | HmacSHA512: CryptoJS.lib.IHasherHmacHelper 454 | SHA384: CryptoJS.lib.HasherHelper 455 | HmacSHA384: CryptoJS.lib.IHasherHmacHelper 456 | 457 | SHA3: CryptoJS.lib.IHasherHelper 458 | HmacSHA3: CryptoJS.lib.IHasherHmacHelper 459 | 460 | EvpKDF: CryptoJS.algo.IEvpKDFHelper 461 | PBKDF2: CryptoJS.algo.IEvpKDFHelper //PBKDF2 is same as EvpKDF 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /vendor/enc-base64-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function(){var h=CryptoJS,j=h.lib.WordArray;h.enc.Base64={stringify:function(b){var e=b.words,f=b.sigBytes,c=this._map;b.clamp();b=[];for(var a=0;a>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join("")},parse:function(b){var e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c));for(var c=[],a=0,d=0;d< 8 | e;d++)if(d%4){var g=f.indexOf(b.charAt(d-1))<<2*(d%4),h=f.indexOf(b.charAt(d))>>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return j.create(c,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); 9 | -------------------------------------------------------------------------------- /vendor/enc-base64.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | (function() { 8 | var h = CryptoJS, 9 | j = h.lib.WordArray; 10 | h.enc.Base64 = { 11 | stringify: function(b) { 12 | var e = b.words, 13 | f = b.sigBytes, 14 | c = this._map; 15 | b.clamp(); 16 | b = []; 17 | for (var a = 0; a < f; a += 3) 18 | for (var d = (e[a >>> 2] >>> 24 - 8 * (a % 4) & 255) << 16 | (e[a + 1 >>> 2] >>> 24 - 8 * ((a + 1) % 4) & 255) << 8 | e[a + 2 >>> 2] >>> 24 - 8 * ((a + 2) % 4) & 255, g = 0; 4 > g && a + 0.75 * g < f; g++) b.push(c.charAt(d >>> 6 * (3 - g) & 63)); 19 | if (e = c.charAt(64)) 20 | for (; b.length % 4;) b.push(e); 21 | return b.join("") 22 | }, 23 | parse: function(b) { 24 | var e = b.length, 25 | f = this._map, 26 | c = f.charAt(64); 27 | c && (c = b.indexOf(c), -1 != c && (e = c)); 28 | for (var c = [], a = 0, d = 0; d < 29 | e; d++) 30 | if (d % 4) { 31 | var g = f.indexOf(b.charAt(d - 1)) << 2 * (d % 4), 32 | h = f.indexOf(b.charAt(d)) >>> 6 - 2 * (d % 4); 33 | c[a >>> 2] |= (g | h) << 24 - 8 * (a % 4); 34 | a++ 35 | } 36 | return j.create(c, a) 37 | }, 38 | _map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" 39 | } 40 | })(); 41 | -------------------------------------------------------------------------------- /vendor/hmac-sha256-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, 8 | r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 9 | 32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, 10 | 2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}}, 11 | u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;gn;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]= 15 | c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; 16 | d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math); 17 | (function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j>> 2] |= (d[e >>> 2] >>> 24 - 8 * (e % 4) & 255) << 24 - 8 * ((b + e) % 4); 53 | else if (65535 < d.length) 54 | for (e = 0; e < a; e += 4) c[b + e >>> 2] = d[e >>> 2]; 55 | else c.push.apply(c, d); 56 | this.sigBytes += a; 57 | return this 58 | }, 59 | clamp: function() { 60 | var a = this.words, 61 | c = this.sigBytes; 62 | a[c >>> 2] &= 4294967295 << 63 | 32 - 8 * (c % 4); 64 | a.length = h.ceil(c / 4) 65 | }, 66 | clone: function() { 67 | var a = m.clone.call(this); 68 | a.words = this.words.slice(0); 69 | return a 70 | }, 71 | random: function(a) { 72 | for (var c = [], d = 0; d < a; d += 4) c.push(4294967296 * h.random() | 0); 73 | return new r.init(c, a) 74 | } 75 | }), 76 | l = f.enc = {}, 77 | k = l.Hex = { 78 | stringify: function(a) { 79 | var c = a.words; 80 | a = a.sigBytes; 81 | for (var d = [], b = 0; b < a; b++) { 82 | var e = c[b >>> 2] >>> 24 - 8 * (b % 4) & 255; 83 | d.push((e >>> 4).toString(16)); 84 | d.push((e & 15).toString(16)) 85 | } 86 | return d.join("") 87 | }, 88 | parse: function(a) { 89 | for (var c = a.length, d = [], b = 0; b < c; b += 2) d[b >>> 3] |= parseInt(a.substr(b, 90 | 2), 16) << 24 - 4 * (b % 8); 91 | return new r.init(d, c / 2) 92 | } 93 | }, 94 | n = l.Latin1 = { 95 | stringify: function(a) { 96 | var c = a.words; 97 | a = a.sigBytes; 98 | for (var d = [], b = 0; b < a; b++) d.push(String.fromCharCode(c[b >>> 2] >>> 24 - 8 * (b % 4) & 255)); 99 | return d.join("") 100 | }, 101 | parse: function(a) { 102 | for (var c = a.length, d = [], b = 0; b < c; b++) d[b >>> 2] |= (a.charCodeAt(b) & 255) << 24 - 8 * (b % 4); 103 | return new r.init(d, c) 104 | } 105 | }, 106 | j = l.Utf8 = { 107 | stringify: function(a) { 108 | try { 109 | return decodeURIComponent(escape(n.stringify(a))) 110 | } catch (c) { 111 | throw Error("Malformed UTF-8 data"); 112 | } 113 | }, 114 | parse: function(a) { 115 | return n.parse(unescape(encodeURIComponent(a))) 116 | } 117 | }, 118 | u = g.BufferedBlockAlgorithm = m.extend({ 119 | reset: function() { 120 | this._data = new r.init; 121 | this._nDataBytes = 0 122 | }, 123 | _append: function(a) { 124 | "string" == typeof a && (a = j.parse(a)); 125 | this._data.concat(a); 126 | this._nDataBytes += a.sigBytes 127 | }, 128 | _process: function(a) { 129 | var c = this._data, 130 | d = c.words, 131 | b = c.sigBytes, 132 | e = this.blockSize, 133 | f = b / (4 * e), 134 | f = a ? h.ceil(f) : h.max((f | 0) - this._minBufferSize, 0); 135 | a = f * e; 136 | b = h.min(4 * a, b); 137 | if (a) { 138 | for (var g = 0; g < a; g += e) this._doProcessBlock(d, g); 139 | g = d.splice(0, a); 140 | c.sigBytes -= b 141 | } 142 | return new r.init(g, b) 143 | }, 144 | clone: function() { 145 | var a = m.clone.call(this); 146 | a._data = this._data.clone(); 147 | return a 148 | }, 149 | _minBufferSize: 0 150 | }); 151 | g.Hasher = u.extend({ 152 | cfg: m.extend(), 153 | init: function(a) { 154 | this.cfg = this.cfg.extend(a); 155 | this.reset() 156 | }, 157 | reset: function() { 158 | u.reset.call(this); 159 | this._doReset() 160 | }, 161 | update: function(a) { 162 | this._append(a); 163 | this._process(); 164 | return this 165 | }, 166 | finalize: function(a) { 167 | a && this._append(a); 168 | return this._doFinalize() 169 | }, 170 | blockSize: 16, 171 | _createHelper: function(a) { 172 | return function(c, d) { 173 | return (new a.init(d)).finalize(c) 174 | } 175 | }, 176 | _createHmacHelper: function(a) { 177 | return function(c, d) { 178 | return (new t.HMAC.init(a, 179 | d)).finalize(c) 180 | } 181 | } 182 | }); 183 | var t = f.algo = {}; 184 | return f 185 | }(Math); 186 | (function(h) { 187 | for (var s = CryptoJS, f = s.lib, g = f.WordArray, q = f.Hasher, f = s.algo, m = [], r = [], l = function(a) { 188 | return 4294967296 * (a - (a | 0)) | 0 189 | }, k = 2, n = 0; 64 > n;) { 190 | var j; 191 | a: { 192 | j = k; 193 | for (var u = h.sqrt(j), t = 2; t <= u; t++) 194 | if (!(j % t)) { 195 | j = !1; 196 | break a 197 | } 198 | j = !0 199 | } 200 | j && (8 > n && (m[n] = l(h.pow(k, 0.5))), r[n] = l(h.pow(k, 1 / 3)), n++); 201 | k++ 202 | } 203 | var a = [], 204 | f = f.SHA256 = q.extend({ 205 | _doReset: function() { 206 | this._hash = new g.init(m.slice(0)) 207 | }, 208 | _doProcessBlock: function(c, d) { 209 | for (var b = this._hash.words, e = b[0], f = b[1], g = b[2], j = b[3], h = b[4], m = b[5], n = b[6], q = b[7], p = 0; 64 > p; p++) { 210 | if (16 > p) a[p] = 211 | c[d + p] | 0; 212 | else { 213 | var k = a[p - 15], 214 | l = a[p - 2]; 215 | a[p] = ((k << 25 | k >>> 7) ^ (k << 14 | k >>> 18) ^ k >>> 3) + a[p - 7] + ((l << 15 | l >>> 17) ^ (l << 13 | l >>> 19) ^ l >>> 10) + a[p - 16] 216 | } 217 | k = q + ((h << 26 | h >>> 6) ^ (h << 21 | h >>> 11) ^ (h << 7 | h >>> 25)) + (h & m ^ ~h & n) + r[p] + a[p]; 218 | l = ((e << 30 | e >>> 2) ^ (e << 19 | e >>> 13) ^ (e << 10 | e >>> 22)) + (e & f ^ e & g ^ f & g); 219 | q = n; 220 | n = m; 221 | m = h; 222 | h = j + k | 0; 223 | j = g; 224 | g = f; 225 | f = e; 226 | e = k + l | 0 227 | } 228 | b[0] = b[0] + e | 0; 229 | b[1] = b[1] + f | 0; 230 | b[2] = b[2] + g | 0; 231 | b[3] = b[3] + j | 0; 232 | b[4] = b[4] + h | 0; 233 | b[5] = b[5] + m | 0; 234 | b[6] = b[6] + n | 0; 235 | b[7] = b[7] + q | 0 236 | }, 237 | _doFinalize: function() { 238 | var a = this._data, 239 | d = a.words, 240 | b = 8 * this._nDataBytes, 241 | e = 8 * a.sigBytes; 242 | d[e >>> 5] |= 128 << 24 - e % 32; 243 | d[(e + 64 >>> 9 << 4) + 14] = h.floor(b / 4294967296); 244 | d[(e + 64 >>> 9 << 4) + 15] = b; 245 | a.sigBytes = 4 * d.length; 246 | this._process(); 247 | return this._hash 248 | }, 249 | clone: function() { 250 | var a = q.clone.call(this); 251 | a._hash = this._hash.clone(); 252 | return a 253 | } 254 | }); 255 | s.SHA256 = q._createHelper(f); 256 | s.HmacSHA256 = q._createHmacHelper(f) 257 | })(Math); 258 | (function() { 259 | var h = CryptoJS, 260 | s = h.enc.Utf8; 261 | h.algo.HMAC = h.lib.Base.extend({ 262 | init: function(f, g) { 263 | f = this._hasher = new f.init; 264 | "string" == typeof g && (g = s.parse(g)); 265 | var h = f.blockSize, 266 | m = 4 * h; 267 | g.sigBytes > m && (g = f.finalize(g)); 268 | g.clamp(); 269 | for (var r = this._oKey = g.clone(), l = this._iKey = g.clone(), k = r.words, n = l.words, j = 0; j < h; j++) k[j] ^= 1549556828, n[j] ^= 909522486; 270 | r.sigBytes = l.sigBytes = m; 271 | this.reset() 272 | }, 273 | reset: function() { 274 | var f = this._hasher; 275 | f.reset(); 276 | f.update(this._iKey) 277 | }, 278 | update: function(f) { 279 | this._hasher.update(f); 280 | return this 281 | }, 282 | finalize: function(f) { 283 | var g = 284 | this._hasher; 285 | f = g.finalize(f); 286 | g.reset(); 287 | return g.finalize(this._oKey.clone().concat(f)) 288 | } 289 | }) 290 | })(); 291 | --------------------------------------------------------------------------------