├── .gitignore ├── LICENSE ├── Ponto.podspec ├── README.md ├── android ├── .gitignore ├── README.md ├── ponto-sample │ ├── AndroidManifest.xml │ ├── assets │ │ ├── ponto.js │ │ └── ponto_sample.html │ ├── ic_launcher-web.png │ ├── libs │ │ └── android-support-v4.jar │ ├── proguard-project.txt │ ├── project.properties │ ├── res │ │ ├── drawable-hdpi │ │ │ └── ic_launcher.png │ │ ├── drawable-mdpi │ │ │ └── ic_launcher.png │ │ ├── drawable-xhdpi │ │ │ └── ic_launcher.png │ │ ├── drawable-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── layout │ │ │ └── activity_web.xml │ │ ├── menu │ │ │ └── web.xml │ │ ├── values-v11 │ │ │ └── styles.xml │ │ ├── values-v14 │ │ │ └── styles.xml │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ └── values │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── src │ │ └── com │ │ └── wikia │ │ └── ponto │ │ └── sample │ │ ├── WebActivity.java │ │ └── modules │ │ └── Messaging.java └── ponto │ ├── AndroidManifest.xml │ ├── libs │ └── android-support-v4.jar │ ├── proguard-project.txt │ ├── project.properties │ └── src │ └── com │ └── wikia │ └── ponto │ └── Ponto.java ├── bower.json ├── ios ├── Ponto │ ├── Ponto.xcodeproj │ │ └── project.pbxproj │ ├── Ponto │ │ ├── Default-568h@2x.png │ │ ├── Default.png │ │ ├── Default@2x.png │ │ ├── Ponto-Info.plist │ │ ├── Ponto-Prefix.pch │ │ ├── PontoDEMOAppDelegate.h │ │ ├── PontoDEMOAppDelegate.m │ │ ├── PontoDEMOMessaging.h │ │ ├── PontoDEMOMessaging.m │ │ ├── PontoDEMOViewController.h │ │ ├── PontoDEMOViewController.m │ │ ├── PontoDEMOViewController.xib │ │ ├── en.lproj │ │ │ └── InfoPlist.strings │ │ └── main.m │ ├── PontoLib │ │ ├── NSURL+QueryDictionary.h │ │ ├── NSURL+QueryDictionary.m │ │ ├── PontoBaseHandler.h │ │ ├── PontoBaseHandler.m │ │ ├── PontoDispatcher.h │ │ └── PontoDispatcher.m │ ├── PontoTests │ │ ├── PontoTests-Info.plist │ │ ├── PontoTests.h │ │ ├── PontoTests.m │ │ └── en.lproj │ │ │ └── InfoPlist.strings │ └── Resources │ │ └── html │ │ └── pontoDemo.html └── README.md └── web ├── README.md └── src └── ponto.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Java # 2 | *.class 3 | *.war 4 | *.ear 5 | 6 | # Objective-C # 7 | *.o 8 | ios/Ponto/Ponto.xcodeproj/xcuserdata 9 | ios/Ponto/Ponto.xcodeproj/project.xcworkspace 10 | 11 | # Android # 12 | android/bin 13 | android/gen 14 | 15 | # Mac # 16 | .DS_Store 17 | 18 | # IntelliJ Idea/AppCode # 19 | .idea 20 | ios/.idea 21 | android/.idea 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Ponto.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Ponto" 3 | s.version = "0.1.1" 4 | s.summary = "Ponto is comunication bridge between HTML and Native." 5 | s.description = <<-DESC 6 | Ponto is comunication bridge between HTML and Native. 7 | With Ponto you can call native methods from JS inside WebView, or call JS methods from native code. 8 | DESC 9 | s.homepage = "https://github.com/Wikia/ponto" 10 | s.license = "MIT" 11 | s.authors = { "Grzegorz Nowicki" => "grzegorz@wikia-inc.com" } 12 | s.platform = :ios, '7.0' 13 | s.source_files = 'ios/Ponto/PontoLib/*.{h,m}' 14 | s.source = { :git => "https://github.com/Wikia/ponto.git", :tag => s.version.to_s } 15 | s.requires_arc = true 16 | s.framework = 'WebKit' 17 | 18 | end 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ponto 2 | ===== 3 | A simple, portable [wire protocol](http://en.wikipedia.org/wiki/Wire_protocol) to allow two-ways asynchronous communication between: 4 | 5 | * native iOS/Android contextes and the JavaScript context of Webviews 6 | * IFrame child and parent within a Webview (or standard web browser) 7 | 8 | 9 | Rationale 10 | --------- 11 | 12 | Some popular HTML5-based cross-platform frameworks for mobile application development, such as [Apache PhoneGap/Cordova](http://incubator.apache.org/cordova/) 13 | and [Trigger.IO](http://trigger.io) (but also non-HTML5 frameworks as it happens for [Appcelerator Titanium](http://www.appcelerator.com/platform/titanium-sdk/)'s WebViews) are built on the 14 | top of inter-context communication, i.e. the JavaScript code running in the WebView is able to interact with classes/methods hosted 15 | in the native layer to access platform capabilities not accessible using the DOM API (e.g. the device's filesystem or 16 | camera) while the native app code is able to send information back to the WebView (e.g. the list of images in the device's 17 | media library). 18 | 19 | Native Android/iOS applications that implement WebViews in their flow may need, at any given point to be able to do the 20 | same. 21 | 22 | The aforementioned existing implementations aren't abstract enough to be extracted and reused out of the box, most of the 23 | times their code contains quirks that make sense in the context of the specific framework making the code not generic enough 24 | to be embedded in another application easily or without refactoring it. 25 | 26 | For those reasons we have developed **Ponto** (*"bridge"* in Esperanto), a generic library that can do all of the above and 27 | it's ready to be embedded in any iOS/Android app using WebViews with a simple API. 28 | 29 | 30 | Documentation 31 | ------------- 32 | 33 | * [WebView/Javascript documentation](https://github.com/Wikia/ponto/blob/master/web/README.md) 34 | * Android/Java: coming soon 35 | * iOS/Objective-C: coming soon 36 | 37 | 38 | Credits 39 | ------- 40 | 41 | This project exists thanks to the efforts of: 42 | 43 | * [Artur Klajnerok](https://github.com/ArturKlajnerok) (Android/Java) 44 | * [Bartłomiej "Bartek" Kowalczyk](https://github.com/bkoval) (IFrame transport) 45 | * [Federico "Lox" Lucignano](https://github.com/federico-lox) (WebView/JavaScript) 46 | * [Grzegorz Nowicki](https://github.com/wikia-gregor) (iOS/Objective-C) 47 | * [Jakub Olek](https://github.com/hakubo) (planning support) 48 | * all the [contributors](https://github.com/Wikia/ponto/graphs/contributors) 49 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Lint files 6 | lint.xml 7 | 8 | # Files for the dex VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | 18 | # Maven output folder 19 | target/ 20 | 21 | # Ignore gradle files 22 | .gradle/ 23 | build/ 24 | 25 | # Local configuration file (sdk path, etc) 26 | local.properties 27 | 28 | # Eclipse project files 29 | .classpath 30 | .project 31 | .metadata 32 | .settings 33 | 34 | # Intellij project files 35 | *.iml 36 | *.ipr 37 | *.iws 38 | .idea/ 39 | 40 | # Proguard folder generated by Eclipse 41 | proguard/ 42 | 43 | # Keystores 44 | *.keystore 45 | 46 | # OSX files 47 | .DS_Store 48 | -------------------------------------------------------------------------------- /android/README.md: -------------------------------------------------------------------------------- 1 | This is the root folder for the Android implementation. 2 | -------------------------------------------------------------------------------- /android/ponto-sample/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /android/ponto-sample/assets/ponto.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ponto JavaScript implementation for WebViews 3 | * 4 | * @see http://github.com/wikia-apps/Ponto 5 | * 6 | * @author Federico "Lox" Lucignano 7 | * 8 | */ 9 | 10 | /*global define, require, PontoUriProtocol*/ 11 | (function (context) { 12 | 'use strict'; 13 | 14 | /** 15 | * AMD support 16 | * 17 | * @private 18 | * 19 | * @type {Boolean} 20 | */ 21 | var amd = false; 22 | 23 | /** 24 | * module constructor 25 | * 26 | * @private 27 | * 28 | * @return {Object} The module reference 29 | */ 30 | function ponto() { 31 | var 32 | /** 33 | * [Constant] Name of the URI protocol optionally 34 | * registered by the native layer 35 | * 36 | * @private 37 | * 38 | * @type {String} 39 | * 40 | * @see protocol.request 41 | * @see protocol.response 42 | */ 43 | PROTOCOL_NAME = 'ponto', 44 | 45 | /** 46 | * [Constant] Represents a completed request 47 | * 48 | * @private 49 | * 50 | * @type {Number} 51 | * 52 | * @see Ponto.acceptResponse 53 | */ 54 | RESPONSE_COMPLETE = 0, 55 | 56 | /** 57 | * [Constant] Represents a failed request with errors 58 | * 59 | * @private 60 | * 61 | * @type {Number} 62 | * 63 | * @see Ponto.acceptResponse 64 | */ 65 | RESPONSE_ERROR = 1, 66 | 67 | /** 68 | * [Constant] Indicates that the target is a native platform 69 | * 70 | * @type {Number} 71 | */ 72 | TARGET_NATIVE = 0, 73 | 74 | /** 75 | * [Constant] Indicates that the target is an iframe 76 | * 77 | * @type {Number} 78 | */ 79 | TARGET_IFRAME = 1, 80 | 81 | /** 82 | * [Constant] Indicates that the target is an iframe parent window 83 | * 84 | * @type {Number} 85 | */ 86 | TARGET_IFRAME_PARENT = 2, 87 | 88 | /** 89 | * Window to communicate with (if iframe transport is overriden) 90 | * 91 | * @type {Window} 92 | */ 93 | targetWindow, 94 | 95 | /** 96 | * Origin url of the targeted window 97 | * 98 | * @type {String} 99 | */ 100 | targetOrigin, 101 | 102 | /** 103 | * Request / Response dispatcher 104 | * 105 | * @type {PontoDispatcher} 106 | */ 107 | dispatcher = new PontoDispatcher(context), 108 | 109 | /** 110 | * Registry for complete/error callbacks 111 | * 112 | * @private 113 | * 114 | * @type {Object} 115 | */ 116 | callbacks = {}, 117 | 118 | /** 119 | * Protocol helper 120 | * registered by native platforms that 121 | * have the capability to do so (e.g. Android) or 122 | * implemented directly for those that don't (e.g iOS) 123 | * 124 | * @type {Object} 125 | */ 126 | protocol = nativeProtocol(), 127 | 128 | targets = {}, 129 | 130 | exports; 131 | 132 | /** 133 | * Returns a valid communication protocol for native platform 134 | * @returns {*|{request: request, response: response}} 135 | * Communication protocol methods for the native layer in an object 136 | */ 137 | function nativeProtocol () { 138 | return context.PontoProtocol || { 139 | //the only other chance is for the native layer to register 140 | //a custom protocol for communicating with the webview (e.g. iOS) 141 | request: function (execContext, target, method, params, callbackId) { 142 | if (execContext.location && execContext.location.href) { 143 | execContext.location.href = PROTOCOL_NAME + ':///request?target=' + encodeURIComponent(target) + 144 | '&method=' + encodeURIComponent(method) + 145 | ((params) ? '¶ms=' + encodeURIComponent(params) : '') + 146 | ((callbackId) ? '&callbackId=' + encodeURIComponent(callbackId) : ''); 147 | } else { 148 | throw new LocationException(); 149 | } 150 | }, 151 | response: function (execContext, callbackId, params) { 152 | if (execContext.location && execContext.location.href) { 153 | execContext.location.href = PROTOCOL_NAME + ':///response?callbackId=' + encodeURIComponent(callbackId) + 154 | ((params) ? '¶ms=' + encodeURIComponent(JSON.stringify(params)) : ''); 155 | } else { 156 | throw new LocationException(); 157 | } 158 | } 159 | }; 160 | } 161 | 162 | /** 163 | * Returns a valid communication protocol for the iframe 164 | * @returns {{request: request, response: response}} 165 | * Communication protocol methods for the iframe 166 | */ 167 | function iframeProtocol () { 168 | return { 169 | request: function (execContext, target, method, params, callbackId, async) { 170 | if (targetWindow.postMessage) { 171 | targetWindow.postMessage({ 172 | protocol: PROTOCOL_NAME, 173 | action: 'request', 174 | target: target, 175 | method: method, 176 | params: params, 177 | async: async, 178 | callbackId: callbackId 179 | }, targetOrigin); 180 | } else { 181 | throw new PostMessageException(); 182 | } 183 | }, 184 | response: function (execContext, callbackId, result) { 185 | if (targetWindow.postMessage) { 186 | targetWindow.postMessage({ 187 | protocol: PROTOCOL_NAME, 188 | action: 'response', 189 | type: (result && result.type) ? result.type : RESPONSE_COMPLETE, 190 | params: result, 191 | callbackId: callbackId 192 | }, targetOrigin); 193 | } else { 194 | throw new PostMessageException(); 195 | } 196 | } 197 | }; 198 | } 199 | 200 | /** 201 | * Throws a post message error 202 | */ 203 | function PostMessageException() { 204 | this.message = 'Target context does not support postMessage'; 205 | } 206 | 207 | /** 208 | * Throws a user agent location error 209 | */ 210 | function LocationException() { 211 | this.message = 'Context doesn\'t support User Agent location API'; 212 | } 213 | 214 | /** 215 | * 'message' Event handler 216 | * @param {Event} event 217 | */ 218 | function onMessage(event) { 219 | var data = event.data; 220 | if (data && data.protocol === PROTOCOL_NAME) { 221 | dispatcher[data.action](data); 222 | } 223 | } 224 | 225 | 226 | /** 227 | * Request handler base class constructor 228 | * 229 | * @public 230 | */ 231 | function PontoBaseHandler() {} 232 | 233 | /** 234 | * Request handler factory method, each subclass of 235 | * PontoBaseHandler needs to implement this static method 236 | */ 237 | PontoBaseHandler.getInstance = function () { 238 | throw new Error('The getInstance method needs to be overridden in PontoBaseHandler subclasses'); 239 | }; 240 | 241 | /** 242 | * PontoBaseHandler extension pattern 243 | * 244 | * @param {PontoBaseHandler} constructor The reference to a constructor 245 | * of a type to be converted in a PontoBaseHandler subtype 246 | * 247 | * @example 248 | * function MyHandler(){...} 249 | * Ponto.PontoBaseHandler.derive(MyHandler); 250 | * MyHandler.getInstance = function() {...}; 251 | */ 252 | PontoBaseHandler.derive = function (constructor) { 253 | constructor.handlerSuper = PontoBaseHandler; 254 | constructor.getInstance = PontoBaseHandler.getInstance; 255 | constructor.prototype = new PontoBaseHandler(); 256 | }; 257 | 258 | /** 259 | * Dispatches a request sent by the native layer 260 | * 261 | * @private 262 | * 263 | * @param {Object} scope The execution scope 264 | * @param {Mixed} target A reference to a constructor or a static instance 265 | * @param {RequestParams} data An hash containing the parameters associated to the request 266 | */ 267 | function dispatchRequest(scope, target, data) { 268 | var instance, 269 | result; 270 | 271 | if (target && target.handlerSuper === PontoBaseHandler && target.getInstance) { 272 | instance = target.getInstance(); 273 | 274 | //unfortunately we need to instantiate before being able to 275 | if (instance[data.method]) { 276 | if (data.async) { 277 | instance[data.method](data.params, data.callbackId); 278 | } else { 279 | result = instance[data.method](data.params); 280 | 281 | if (data.callbackId && protocol && protocol.response) { 282 | protocol.response(scope, data.callbackId, result); 283 | } 284 | } 285 | } 286 | } 287 | } 288 | 289 | /** 290 | * Function called by the native layer when answering a request 291 | * 292 | * @public 293 | * 294 | * @param {Object} scope The execution scope 295 | * @param {ResponseParams} data An hash containing the parameters associated to the response 296 | */ 297 | function dispatchResponse(data) { 298 | var 299 | callbackId = data.callbackId, 300 | cbGroup = callbacks[callbackId], 301 | callback, 302 | responseType; 303 | 304 | if (cbGroup) { 305 | responseType = data.type; 306 | 307 | switch (responseType) { 308 | case RESPONSE_COMPLETE: 309 | callback = cbGroup.complete; 310 | break; 311 | case RESPONSE_ERROR: 312 | default: 313 | callback = cbGroup.error; 314 | break; 315 | } 316 | 317 | if (callback) { 318 | callback(data.params); 319 | } 320 | 321 | delete callbacks[callbackId]; 322 | } 323 | } 324 | 325 | /** 326 | * Initialized iframe protocol to work 327 | */ 328 | function setIframeProtocol () { 329 | protocol = iframeProtocol(); 330 | context.addEventListener('message', onMessage, false); 331 | 332 | /** 333 | * Enables manual trigger of the response when async operation needed 334 | * this can be explicitly called after the async operation is done 335 | * @param {Object} result response params, optionally with 'type' field 336 | * @param {Number} callbackId 337 | */ 338 | PontoDispatcher.prototype.respond = function (result, callbackId) { 339 | protocol.response(this.context, callbackId, result); 340 | }; 341 | } 342 | 343 | /** 344 | * Sets iframe's content window as the protocol's target 345 | * @param {String} _targetOrigin - origin URL of the iframe's document 346 | * @param {Window} _targetWindow 347 | */ 348 | targets[TARGET_IFRAME] = function (_targetOrigin, _targetWindow) { 349 | if (_targetWindow.top && _targetWindow !== _targetWindow.top) { 350 | targetWindow = _targetWindow; 351 | targetOrigin = _targetOrigin; 352 | } else { 353 | throw new Error('Bad iframe content window provided.'); 354 | } 355 | setIframeProtocol(); 356 | }; 357 | 358 | /** 359 | * @param {String} _targetOrigin - origin URL of the parent's document 360 | * Sets iframe's parent window as the protocol's target 361 | */ 362 | targets[TARGET_IFRAME_PARENT] = function (_targetOrigin) { 363 | if (context.top && context.top !== context) { 364 | targetWindow = context.top; 365 | targetOrigin = _targetOrigin; 366 | } else { 367 | throw new Error('No possible communication in this context.'); 368 | } 369 | setIframeProtocol(); 370 | }; 371 | 372 | /** 373 | * Sets the native layer as the protocol's target 374 | */ 375 | targets[TARGET_NATIVE] = function () { 376 | protocol = nativeProtocol(); 377 | if (PontoDispatcher.prototype.respond) { 378 | delete PontoDispatcher.prototype.respond; 379 | context.removeEventListener('message', onMessage); 380 | } 381 | }; 382 | 383 | /** 384 | * Overrides the protocol target (default: native) 385 | * @param {Number} _target 386 | * @param {String | undefined} _targetOrigin - origin url of the targeted window 387 | * @param {Number | undefined}_targetWindow 388 | * provide if target is an iframe 389 | */ 390 | function setTarget(_target, _targetOrigin, _targetWindow) { 391 | if (targets[_target]) { 392 | targets[_target](_targetOrigin, _targetWindow); 393 | } 394 | } 395 | 396 | /** 397 | * Deserializes JSON string if needed 398 | * @param {Object | String} data 399 | * @returns {Object} 400 | */ 401 | function parse(data) { 402 | return typeof data === 'string' ? JSON.parse(data) : data; 403 | } 404 | 405 | /** 406 | * RequestParams constructor 407 | * 408 | * Extracts and normalizes the parameters out of a JSON-encoded string 409 | * passed in by a request from the native context 410 | * 411 | * @private 412 | * 413 | * @param {String} data JSON-encoded string describing parameters 414 | * for a request to Ponto 415 | */ 416 | function RequestParams(data) { 417 | var hash = parse(data); 418 | 419 | this.target = hash.target; 420 | this.method = hash.method; 421 | this.params = parse(hash.params); 422 | this.callbackId = hash.callbackId; 423 | this.async = hash.async; 424 | } 425 | 426 | /** 427 | * ResponseParams constructor 428 | * 429 | * Extracts and normalizes the parameters out of a JSON-encoded string 430 | * passed in by a response from the native context 431 | * 432 | * @private 433 | * 434 | * @param {String} data JSON-encoded string describing parameters 435 | * for a response from Ponto 436 | */ 437 | function ResponseParams(data) { 438 | var hash = parse(data); 439 | 440 | this.type = parseInt(hash.type, 10); 441 | this.callbackId = hash.callbackId; 442 | this.params = hash.params; 443 | } 444 | 445 | /** 446 | * Ponto dispatcher constructor 447 | * 448 | * @public 449 | * 450 | * @param {Object} dispatchContext The execution scope to bind to this instance 451 | */ 452 | function PontoDispatcher(dispatchContext) { 453 | this.context = dispatchContext; 454 | } 455 | 456 | /** 457 | * Sets the context/scope to bind to the PontoDispatcher instance 458 | * 459 | * @public 460 | * 461 | * @param {Object} scope The context/scope to bind to the instance 462 | */ 463 | PontoDispatcher.prototype.setContext = function (scope) { 464 | this.context = scope; 465 | }; 466 | 467 | /** 468 | * Handle a request from the native layer, 469 | * this is supposed to be called directly from native code 470 | * 471 | * @public 472 | * 473 | * @param {String} data A JSON-encoded string containing the parameters 474 | * associated to the request 475 | */ 476 | PontoDispatcher.prototype.request = function (data) { 477 | var params = new RequestParams(data), 478 | scope = this.context, 479 | target = scope[params.target]; 480 | 481 | if (target) { 482 | dispatchRequest(scope, target, params); 483 | } else if (amd && params.target) { 484 | require([params.target], function (target) { 485 | dispatchRequest(scope, target, params); 486 | }); 487 | } 488 | }; 489 | 490 | /** 491 | * Handle a response from the native layer, 492 | * this is supposed to be called directly from native code 493 | * 494 | * @public 495 | * 496 | * @param {String} data A JSON-encoded string containing the parameters 497 | * associated to the response 498 | */ 499 | PontoDispatcher.prototype.response = function (data) { 500 | var params = new ResponseParams(data); 501 | dispatchResponse(params); 502 | }; 503 | 504 | /** 505 | * Makes a request to the native layer 506 | * 507 | * @public 508 | * 509 | * @param {String} target The target native class 510 | * @param {String} method The method to call 511 | * @param {Object} params [OPTIONAL] An hash contaning the parameters to pass to the method 512 | * @param {Function} completeCallback [OPTIONAL] The callback to invoke on completion 513 | * @param {Function} errorCallback [OPTIONAL] The callback to invoke in case of error 514 | * @param {Bool} async Indicates of the other side will make async operation and respond 515 | * manually 516 | */ 517 | //ToDo -> Make the method take object with params (too many params now) 518 | PontoDispatcher.prototype.invoke = function (target, method, params, completeCallback, errorCallback, async) { 519 | var callbackId; 520 | 521 | if (typeof (completeCallback || errorCallback) === 'function') { 522 | callbackId = Math.random().toString().substr(2); 523 | callbacks[callbackId] = { 524 | complete: completeCallback, 525 | error: errorCallback 526 | }; 527 | } 528 | 529 | protocol.request(this.context, target, method, JSON.stringify(params), callbackId, async); 530 | }; 531 | 532 | exports = dispatcher; 533 | exports.RESPONSE_COMPLETE = RESPONSE_COMPLETE; 534 | exports.RESPONSE_ERROR = RESPONSE_ERROR; 535 | exports.TARGET_NATIVE = TARGET_NATIVE; 536 | exports.TARGET_IFRAME = TARGET_IFRAME; 537 | exports.TARGET_IFRAME_PARENT = TARGET_IFRAME_PARENT; 538 | exports.PontoDispatcher = PontoDispatcher; 539 | exports.PontoBaseHandler = PontoBaseHandler; 540 | exports.setTarget = setTarget; 541 | 542 | return exports; 543 | } 544 | 545 | //check for AMD availability 546 | if (typeof define !== 'undefined' && define.amd) { 547 | amd = true; 548 | } 549 | 550 | //make module available in the global scope 551 | context.Ponto = ponto(); 552 | 553 | //if AMD available then register also as a module 554 | //to allow easy usage in other AMD modules 555 | if (amd) { 556 | amd = true; 557 | define('ponto', context.Ponto); 558 | } 559 | }(this)); -------------------------------------------------------------------------------- /android/ponto-sample/assets/ponto_sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pontoDemo 5 | 6 | 7 | 8 | 9 | 101 | 102 | 103 |

Ponto DEMO HTML

104 |
    105 |
  • 106 | 107 |
  • 108 | 109 |
  • 110 | 111 |
  • 112 | 113 |
  • 114 | 115 |
  • 116 | 117 |
  • 118 | 119 |
  • 120 | 121 |
  • 122 | 123 |
  • 124 | 125 |
  • 126 | 127 |
  • 128 |
129 | 130 | -------------------------------------------------------------------------------- /android/ponto-sample/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikia/ponto/6ae2171778634626865019c7c4ee60c07b26228c/android/ponto-sample/ic_launcher-web.png -------------------------------------------------------------------------------- /android/ponto-sample/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikia/ponto/6ae2171778634626865019c7c4ee60c07b26228c/android/ponto-sample/libs/android-support-v4.jar -------------------------------------------------------------------------------- /android/ponto-sample/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /android/ponto-sample/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | android.library.reference.1=../ponto 16 | -------------------------------------------------------------------------------- /android/ponto-sample/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikia/ponto/6ae2171778634626865019c7c4ee60c07b26228c/android/ponto-sample/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/ponto-sample/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikia/ponto/6ae2171778634626865019c7c4ee60c07b26228c/android/ponto-sample/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/ponto-sample/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikia/ponto/6ae2171778634626865019c7c4ee60c07b26228c/android/ponto-sample/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/ponto-sample/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikia/ponto/6ae2171778634626865019c7c4ee60c07b26228c/android/ponto-sample/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/ponto-sample/res/layout/activity_web.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /android/ponto-sample/res/menu/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/ponto-sample/res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/ponto-sample/res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/ponto-sample/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 64dp 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/ponto-sample/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/ponto-sample/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ponto-sample 5 | Hello ponto! 6 | WebView Alert 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/ponto-sample/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /android/ponto-sample/src/com/wikia/ponto/sample/WebActivity.java: -------------------------------------------------------------------------------- 1 | package com.wikia.ponto.sample; 2 | 3 | import com.wikia.ponto.Ponto; 4 | import com.wikia.ponto.Ponto.RequestCallback; 5 | 6 | import android.app.Activity; 7 | import android.os.Bundle; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | import android.webkit.WebChromeClient; 11 | import android.webkit.WebView; 12 | import android.webkit.WebViewClient; 13 | import android.widget.Toast; 14 | 15 | public class WebActivity extends Activity { 16 | 17 | private static final String HTML_FILE_PATH = "file:///android_asset/ponto_sample.html"; 18 | private static final String PONTO_MODULES_PACKAGE = "com.wikia.ponto.sample.modules"; 19 | 20 | private static final String JS_ALERT_CLASS = "Alert"; 21 | private static final String JS_ALERT_METHOD = "show"; 22 | private static final String JS_ALERT_PARAM = "{\"text\": \"Hello WebView alert\"}"; 23 | private static final String JS_ALERT_SUCCESS = "Alert.onSuccess()"; 24 | private static final String JS_ALERT_ERROR = "Alert.onError()"; 25 | 26 | private WebView mWebView; 27 | private Ponto mPonto; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_web); 33 | 34 | mWebView = (WebView) findViewById(R.id.webview); 35 | mWebView.setWebViewClient(new WebViewClient()); 36 | mWebView.setWebChromeClient(new WebChromeClient()); 37 | mWebView.getSettings().setBuiltInZoomControls(false); 38 | 39 | mPonto = new Ponto(mWebView, PONTO_MODULES_PACKAGE); 40 | 41 | mWebView.loadUrl(HTML_FILE_PATH); 42 | } 43 | 44 | @Override 45 | public boolean onCreateOptionsMenu(Menu menu) { 46 | getMenuInflater().inflate(R.menu.web, menu); 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean onOptionsItemSelected(MenuItem item) { 52 | int id = item.getItemId(); 53 | if (id == R.id.action_alert) { 54 | openWebViewAlert(); 55 | return true; 56 | } 57 | return super.onOptionsItemSelected(item); 58 | } 59 | 60 | private void openWebViewAlert() { 61 | RequestCallback callback = new Ponto.RequestCallback() { 62 | 63 | @Override 64 | public void onSuccess() { 65 | Toast.makeText(WebActivity.this, JS_ALERT_SUCCESS, Toast.LENGTH_SHORT).show(); 66 | } 67 | 68 | @Override 69 | public void onError() { 70 | Toast.makeText(WebActivity.this, JS_ALERT_ERROR, Toast.LENGTH_SHORT).show(); 71 | } 72 | }; 73 | mPonto.invoke(JS_ALERT_CLASS, JS_ALERT_METHOD, JS_ALERT_PARAM, callback); 74 | } 75 | } -------------------------------------------------------------------------------- /android/ponto-sample/src/com/wikia/ponto/sample/modules/Messaging.java: -------------------------------------------------------------------------------- 1 | package com.wikia.ponto.sample.modules; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.util.Log; 9 | import android.widget.Toast; 10 | 11 | public class Messaging { 12 | 13 | private static final String TAG = Messaging.class.getCanonicalName(); 14 | 15 | private Context mContext; 16 | 17 | public Messaging(Context context) { 18 | mContext = context; 19 | } 20 | 21 | public void showToast(String params) { 22 | String body = ""; 23 | try { 24 | JSONObject message = new JSONObject(params); 25 | if (message != null) { 26 | body = message.optString("body"); 27 | } 28 | } catch (JSONException e) { 29 | Log.e(TAG, "JSONException while parsing messaging data", e); 30 | } 31 | 32 | Toast.makeText(mContext, body, Toast.LENGTH_SHORT).show(); 33 | } 34 | 35 | public void sendEmail(String params) { 36 | String email = ""; 37 | String subject = ""; 38 | String body = ""; 39 | try { 40 | JSONObject message = new JSONObject(params); 41 | if (message != null) { 42 | email = message.optString("email"); 43 | subject = message.optString("subject"); 44 | body = message.optString("body"); 45 | } 46 | } catch (JSONException e) { 47 | Log.e(TAG, "JSONException while parsing email data", e); 48 | } 49 | 50 | Intent emailIntent = new Intent(Intent.ACTION_SEND); 51 | emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {email}); 52 | emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject ); 53 | emailIntent.putExtra(Intent.EXTRA_TEXT, body ); 54 | emailIntent.setType("plain/text"); 55 | mContext.startActivity(Intent.createChooser(emailIntent, subject)); 56 | } 57 | 58 | public void getSomeData() { 59 | Log.i("TAG", new Object() {}.getClass().getEnclosingMethod().getName()); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /android/ponto/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/ponto/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikia/ponto/6ae2171778634626865019c7c4ee60c07b26228c/android/ponto/libs/android-support-v4.jar -------------------------------------------------------------------------------- /android/ponto/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /android/ponto/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | android.library=true 16 | -------------------------------------------------------------------------------- /android/ponto/src/com/wikia/ponto/Ponto.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @see http://github.com/Wikia/ponto 3 | * @author Artur Klajnerok 4 | */ 5 | 6 | package com.wikia.ponto; 7 | 8 | import java.lang.reflect.Constructor; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.lang.reflect.Method; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.UUID; 14 | 15 | import org.json.JSONException; 16 | import org.json.JSONObject; 17 | 18 | import android.util.Log; 19 | import android.webkit.JavascriptInterface; 20 | import android.webkit.WebView; 21 | 22 | /** 23 | * Ponto Android implementation. 24 | * Two way communication between WebViews and Native Code in clean standardized manner. 25 | * Provides callback mechanism to handle properly each request and response 26 | */ 27 | public class Ponto { 28 | 29 | public static final String TAG = "Ponto"; 30 | 31 | /** 32 | * Package that contains classes which can be invoked by WebView JavaScript 33 | */ 34 | private String mClassPackage; 35 | 36 | /** 37 | * WebView that will communicate with native code 38 | */ 39 | private WebView mWebView; 40 | 41 | /** 42 | * Callbacks to get WebView response for a request from the native code 43 | */ 44 | private Map mCallbacks; 45 | 46 | public Ponto(WebView webView, String classPackage) { 47 | mCallbacks = new HashMap(); 48 | mClassPackage = classPackage + "."; 49 | mWebView = webView; 50 | mWebView.getSettings().setJavaScriptEnabled(true); 51 | mWebView.addJavascriptInterface(new PontoProtocol(), PontoProtocol.TAG); 52 | } 53 | 54 | /** 55 | * Makes a request to the WebView JavaScript 56 | * 57 | * @param className The class name to instantiate. 58 | * @param methodName The method name to invoke. 59 | * @param params The parameters that will be passed to invoked method. 60 | * @param callback The callback for this request 61 | */ 62 | public void invoke(String className, String methodName, String params, RequestCallback callback) { 63 | String callbackId = UUID.randomUUID().toString(); 64 | 65 | if (callbackId != null && callback != null) { 66 | mCallbacks.put(callbackId, callback); 67 | } 68 | 69 | StringBuilder requestString = new StringBuilder(); 70 | requestString.append("javascript:Ponto.request('{") 71 | .append("\"target\": \"").append(className).append("\", ") 72 | .append("\"method\": \"").append(methodName).append("\", ") 73 | .append("\"params\": ").append(params).append(", ") 74 | .append("\"callbackId\": \"").append(callbackId).append("\"") 75 | .append("}');"); 76 | mWebView.loadUrl(requestString.toString()); 77 | } 78 | 79 | /** 80 | * Communication protocol that should be used as JavascriptInterface 81 | */ 82 | private class PontoProtocol { 83 | 84 | public static final String TAG = "PontoProtocol"; 85 | 86 | /** 87 | * Represents a completed request 88 | */ 89 | private static final int RESPONSE_COMPLETE = 0; 90 | 91 | /** 92 | * Represents a failed request with errors 93 | */ 94 | private static final int RESPONSE_ERROR = 1; 95 | 96 | /** 97 | * The value of String that is null 98 | */ 99 | private static final String NULL_STRING = "null"; 100 | 101 | /** 102 | * The value of String that is undefined 103 | */ 104 | private static final String UNDEFINED_STRING = "undefined"; 105 | 106 | /** 107 | * Key for message response parameter 108 | */ 109 | private static final String KEY_MESSAGE = "message"; 110 | 111 | /** 112 | * Called by the web layer to perform a request on native layer 113 | * 114 | * @param execContext The JavaScript context. 115 | * @param className The class name to instantiate. 116 | * @param methodName The method name to invoke. 117 | * @param params The parameters that will be passed to invoked method. 118 | * @param callbackId The id of web callback. 119 | * @param async 120 | */ 121 | @JavascriptInterface 122 | public void request(String execContext, String className, String methodName, String params, 123 | String callbackId, String async) { 124 | 125 | int responseType = RESPONSE_ERROR; 126 | JSONObject responseParams = new JSONObject(); 127 | try { 128 | Class cls = Class.forName(mClassPackage + className); 129 | Constructor constructor = cls.getConstructor(android.content.Context.class); 130 | Object object = constructor.newInstance(mWebView.getContext()); 131 | if (params != null && !params.equalsIgnoreCase(NULL_STRING)) { 132 | Method method = cls.getDeclaredMethod(methodName, String.class); 133 | method.invoke(object, params); 134 | } else { 135 | Method method = cls.getDeclaredMethod(methodName); 136 | method.invoke(object); 137 | } 138 | responseType = RESPONSE_COMPLETE; 139 | } catch (InstantiationException e) { 140 | Log.e(TAG, "InstantiationException while executing ponto request", e); 141 | } catch (IllegalAccessException e) { 142 | Log.e(TAG, "IllegalAccessException while executing ponto request", e); 143 | } catch (ClassNotFoundException e) { 144 | Log.e(TAG, "ClassNotFoundException while executing ponto request", e); 145 | responseParams = getClassNotFoundParams(className); 146 | } catch (NoSuchMethodException e) { 147 | Log.e(TAG, "NoSuchMethodException while executing ponto request", e); 148 | responseParams = getNoSuchMethodParams(methodName); 149 | } catch (IllegalArgumentException e) { 150 | Log.e(TAG, "IllegalArgumentException while executing ponto request", e); 151 | } catch (InvocationTargetException e) { 152 | Log.e(TAG, "InvocationTargetException while executing ponto request", e); 153 | } 154 | 155 | if (callbackId != null && !callbackId.equalsIgnoreCase(UNDEFINED_STRING)) { 156 | javascriptCallback(callbackId, responseType, responseParams.toString()); 157 | } 158 | } 159 | 160 | /** 161 | * Called by the web layer when answering a request from native layer 162 | * 163 | * @param execContext The JavaScript context. 164 | * @param callbackId The id of callback that should be executed. 165 | * @param params The parameters associated with the response. 166 | */ 167 | @JavascriptInterface 168 | public void response(String execContext, String callbackId, String params) { 169 | 170 | int responseType = getResponeTypeFromParams(params); 171 | if (callbackId != null && !callbackId.equalsIgnoreCase(UNDEFINED_STRING) && 172 | mCallbacks.containsKey(callbackId)) { 173 | 174 | RequestCallback callback = mCallbacks.get(callbackId); 175 | 176 | switch (responseType) { 177 | case RESPONSE_COMPLETE: 178 | callback.onSuccess(); 179 | break; 180 | case RESPONSE_ERROR: 181 | default: 182 | callback.onError(); 183 | break; 184 | } 185 | mCallbacks.remove(callbackId); 186 | } 187 | } 188 | 189 | /** 190 | * Makes a response to web layer 191 | * 192 | * @param callbackId The id of callback that should be executed. 193 | * @param type The response type complete/error 194 | * @param params The parameters associated with the response. 195 | */ 196 | private void javascriptCallback(final String callbackId, final int type, final String params) { 197 | mWebView.post(new Runnable() { 198 | @Override 199 | public void run() { 200 | StringBuilder responseString = new StringBuilder(); 201 | responseString.append("javascript:Ponto.response('{") 202 | .append("\"type\": ").append(type).append(", ") 203 | .append("\"params\": ").append(params).append(", ") 204 | .append("\"callbackId\": \"").append(callbackId).append("\"") 205 | .append("}');"); 206 | mWebView.loadUrl(responseString.toString()); 207 | } 208 | }); 209 | } 210 | 211 | private int getResponeTypeFromParams(String params) { 212 | JSONObject responseParams; 213 | int type = RESPONSE_COMPLETE; 214 | try { 215 | responseParams = new JSONObject(params); 216 | if (responseParams != null) { 217 | type = responseParams.optInt("type"); 218 | } 219 | } catch (JSONException e) { 220 | type = RESPONSE_ERROR; 221 | Log.e(TAG, "JSONException while parsing messaging data", e); 222 | } 223 | return type; 224 | } 225 | 226 | private JSONObject getClassNotFoundParams(String className) { 227 | Map paramsMap = new HashMap(); 228 | paramsMap.put(KEY_MESSAGE, "Class not found"); 229 | paramsMap.put("className", className); 230 | return new JSONObject(paramsMap); 231 | } 232 | 233 | private JSONObject getNoSuchMethodParams(String methodName) { 234 | Map paramsMap = new HashMap(); 235 | paramsMap.put(KEY_MESSAGE, "Method not found"); 236 | paramsMap.put("methodName", methodName); 237 | return new JSONObject(paramsMap); 238 | } 239 | } 240 | 241 | /** 242 | * Interface definition for a callbacks that are invoked 243 | * when WebView responds for a request from the native code. 244 | */ 245 | public interface RequestCallback { 246 | 247 | /** 248 | * Callback method to be invoked when request was successful 249 | */ 250 | public void onSuccess(); 251 | 252 | /** 253 | * Callback method to be invoked when request failed 254 | */ 255 | public void onError(); 256 | } 257 | } -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ponto", 3 | "main": "web/src/ponto.js", 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/Wikia/ponto.git", 6 | "moduleType": [ 7 | "globals", 8 | "amd" 9 | ], 10 | "license": "MIT", 11 | "private": false, 12 | "ignore": [ 13 | "android", 14 | "ios", 15 | "Ponto.podspec" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /ios/Ponto/Ponto.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3BC46FFA160CBA6500EF9B85 /* PontoDEMOViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BC46FF8160CBA6500EF9B85 /* PontoDEMOViewController.m */; }; 11 | 3BC46FFB160CBA6500EF9B85 /* PontoDEMOViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3BC46FF9160CBA6500EF9B85 /* PontoDEMOViewController.xib */; }; 12 | 3BC47011160CCBFD00EF9B85 /* pontoDemo.html in Resources */ = {isa = PBXBuildFile; fileRef = 6BE4606A0AA3374F68EBF395 /* pontoDemo.html */; }; 13 | 3BC47014160CCC3A00EF9B85 /* web in Resources */ = {isa = PBXBuildFile; fileRef = 3BC47013160CCC3A00EF9B85 /* web */; }; 14 | 3BC4702216106F3900EF9B85 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BC4702116106F3900EF9B85 /* MessageUI.framework */; }; 15 | 6BE461294C1F54F199A633DA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BE46CDD43F0AE5328FB15E1 /* UIKit.framework */; }; 16 | 6BE461F0B974327ECDD6040F /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6BE4608CBF63C610AEFBF725 /* Default-568h@2x.png */; }; 17 | 6BE4633715CFDB994597632F /* PontoDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE46633588BC0F02C8BABC1 /* PontoDispatcher.m */; }; 18 | 6BE464608BB04ABB3FC1F59A /* NSURL+QueryDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE4618E195D0F96A202FBA1 /* NSURL+QueryDictionary.m */; }; 19 | 6BE464C927BAE9BDBB9256CB /* NSURL+QueryDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE4618E195D0F96A202FBA1 /* NSURL+QueryDictionary.m */; }; 20 | 6BE464DA2CBF20F3F0E9598E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6BE468FFE1FC6C4B414CFBA3 /* InfoPlist.strings */; }; 21 | 6BE46608C30B793E207CDF3B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BE46A58BA5C67AAED1D0F33 /* CoreGraphics.framework */; }; 22 | 6BE4664446C59E3D405FFFBC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6BE4626D12713D1B043B7930 /* InfoPlist.strings */; }; 23 | 6BE46654BA09F57036442FA8 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6BE460D353518C4EC62D2E4C /* Default@2x.png */; }; 24 | 6BE46770D0B669331645BE6D /* PontoBaseHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE468667F12B9175F833049 /* PontoBaseHandler.m */; }; 25 | 6BE4680C8461991F91F564A0 /* PontoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE46871E65DE45310604262 /* PontoTests.m */; }; 26 | 6BE4694713121642936BA592 /* PontoDEMOAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE466DE016372E65DE56D12 /* PontoDEMOAppDelegate.m */; }; 27 | 6BE469771F6E5E733EFD97FB /* PontoBaseHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE468667F12B9175F833049 /* PontoBaseHandler.m */; }; 28 | 6BE46AC9EF9D7888079E6617 /* PontoDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE46633588BC0F02C8BABC1 /* PontoDispatcher.m */; }; 29 | 6BE46B62708A8778C71D3830 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 6BE46FF755701CAC34D9AEA4 /* Default.png */; }; 30 | 6BE46C28E5CFCDD17A958456 /* PontoDEMOMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE46BF931753016EBE51652 /* PontoDEMOMessaging.m */; }; 31 | 6BE46CA78BBAB3BDAA5BDBA6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BE46CDD43F0AE5328FB15E1 /* UIKit.framework */; }; 32 | 6BE46D73BCBBA2F9A6AA474A /* PontoDEMOMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE46BF931753016EBE51652 /* PontoDEMOMessaging.m */; }; 33 | 6BE46DB35E3F636F5AEB453E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BE46B77CEFBBF875DBB2E8C /* Foundation.framework */; }; 34 | 6BE46E04F9E591168928261B /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BE46BFCA9580E6458FB9EBC /* SenTestingKit.framework */; }; 35 | 6BE46E47364F3AF0B6B98CBC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BE46B77CEFBBF875DBB2E8C /* Foundation.framework */; }; 36 | 6BE46FF6F731FA3C4F92ECD6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BE46980D08505F14FBC3A1C /* main.m */; }; 37 | /* End PBXBuildFile section */ 38 | 39 | /* Begin PBXContainerItemProxy section */ 40 | 6BE460A8857D015E1D7790DC /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = 6BE46A497320EB524FF754F6 /* Project object */; 43 | proxyType = 1; 44 | remoteGlobalIDString = 6BE464C358EA765758A9E7B0; 45 | remoteInfo = Ponto; 46 | }; 47 | /* End PBXContainerItemProxy section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | 3BC46FF7160CBA6500EF9B85 /* PontoDEMOViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PontoDEMOViewController.h; sourceTree = ""; }; 51 | 3BC46FF8160CBA6500EF9B85 /* PontoDEMOViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PontoDEMOViewController.m; sourceTree = ""; }; 52 | 3BC46FF9160CBA6500EF9B85 /* PontoDEMOViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PontoDEMOViewController.xib; sourceTree = ""; }; 53 | 3BC47013160CCC3A00EF9B85 /* web */ = {isa = PBXFileReference; lastKnownFileType = folder; name = web; path = ../../../../web; sourceTree = ""; }; 54 | 3BC4702116106F3900EF9B85 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; 55 | 3BC470241611C73E00EF9B85 /* libobjc.A.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libobjc.A.dylib; path = usr/lib/libobjc.A.dylib; sourceTree = SDKROOT; }; 56 | 6BE46023DFB838841553CE7A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 57 | 6BE4606A0AA3374F68EBF395 /* pontoDemo.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = pontoDemo.html; sourceTree = ""; }; 58 | 6BE4608CBF63C610AEFBF725 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 59 | 6BE460D353518C4EC62D2E4C /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 60 | 6BE4618E195D0F96A202FBA1 /* NSURL+QueryDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+QueryDictionary.m"; sourceTree = ""; }; 61 | 6BE461E1C0407B544A531D1C /* Ponto-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = "Ponto-Info.plist"; sourceTree = ""; }; 62 | 6BE46200F35DE4829ED3A34A /* PontoBaseHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PontoBaseHandler.h; sourceTree = ""; }; 63 | 6BE4649481F8EA1C75B45022 /* PontoDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PontoDispatcher.h; sourceTree = ""; }; 64 | 6BE464F0B61C72391C38E09B /* PontoTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = "PontoTests-Info.plist"; sourceTree = ""; }; 65 | 6BE465EDF5FCA97F788F275B /* NSURL+QueryDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+QueryDictionary.h"; sourceTree = ""; }; 66 | 6BE46633588BC0F02C8BABC1 /* PontoDispatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PontoDispatcher.m; sourceTree = ""; }; 67 | 6BE466DE016372E65DE56D12 /* PontoDEMOAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PontoDEMOAppDelegate.m; sourceTree = ""; }; 68 | 6BE467959C4560E6957E5E5C /* Ponto-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Ponto-Prefix.pch"; sourceTree = ""; }; 69 | 6BE468667F12B9175F833049 /* PontoBaseHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PontoBaseHandler.m; sourceTree = ""; }; 70 | 6BE46871E65DE45310604262 /* PontoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PontoTests.m; sourceTree = ""; }; 71 | 6BE468BAA200487C0A00CFAF /* PontoTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PontoTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | 6BE46980D08505F14FBC3A1C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 73 | 6BE46A58BA5C67AAED1D0F33 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 74 | 6BE46AF5FD42A3BAB1104831 /* PontoDEMOAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PontoDEMOAppDelegate.h; sourceTree = ""; }; 75 | 6BE46B77CEFBBF875DBB2E8C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 76 | 6BE46BF931753016EBE51652 /* PontoDEMOMessaging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PontoDEMOMessaging.m; sourceTree = ""; }; 77 | 6BE46BFCA9580E6458FB9EBC /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; 78 | 6BE46C83278BC1BD5DB3F965 /* Ponto.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ponto.app; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | 6BE46CDD43F0AE5328FB15E1 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 80 | 6BE46D4AB6F8343C1C6BA566 /* PontoDEMOMessaging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PontoDEMOMessaging.h; sourceTree = ""; }; 81 | 6BE46DCE10FAB9400863FEC0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 82 | 6BE46E3DFE2F6474C174E7DF /* PontoTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PontoTests.h; sourceTree = ""; }; 83 | 6BE46FF755701CAC34D9AEA4 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 84 | /* End PBXFileReference section */ 85 | 86 | /* Begin PBXFrameworksBuildPhase section */ 87 | 6BE467D662B856CE9AFD3E9F /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | 6BE46E04F9E591168928261B /* SenTestingKit.framework in Frameworks */, 92 | 6BE46CA78BBAB3BDAA5BDBA6 /* UIKit.framework in Frameworks */, 93 | 6BE46DB35E3F636F5AEB453E /* Foundation.framework in Frameworks */, 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | 6BE46A08A524EB5E2A11646D /* Frameworks */ = { 98 | isa = PBXFrameworksBuildPhase; 99 | buildActionMask = 2147483647; 100 | files = ( 101 | 3BC4702216106F3900EF9B85 /* MessageUI.framework in Frameworks */, 102 | 6BE461294C1F54F199A633DA /* UIKit.framework in Frameworks */, 103 | 6BE46E47364F3AF0B6B98CBC /* Foundation.framework in Frameworks */, 104 | 6BE46608C30B793E207CDF3B /* CoreGraphics.framework in Frameworks */, 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | /* End PBXFrameworksBuildPhase section */ 109 | 110 | /* Begin PBXGroup section */ 111 | 6BE46093C45C33F82D79AB7B /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 6BE4608CBF63C610AEFBF725 /* Default-568h@2x.png */, 115 | 6BE460D353518C4EC62D2E4C /* Default@2x.png */, 116 | 6BE46FF755701CAC34D9AEA4 /* Default.png */, 117 | 6BE467959C4560E6957E5E5C /* Ponto-Prefix.pch */, 118 | 6BE46980D08505F14FBC3A1C /* main.m */, 119 | 6BE468FFE1FC6C4B414CFBA3 /* InfoPlist.strings */, 120 | 6BE461E1C0407B544A531D1C /* Ponto-Info.plist */, 121 | ); 122 | name = "Supporting Files"; 123 | sourceTree = ""; 124 | }; 125 | 6BE46106C37C5301797532A2 /* Supporting Files */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 6BE4626D12713D1B043B7930 /* InfoPlist.strings */, 129 | 6BE464F0B61C72391C38E09B /* PontoTests-Info.plist */, 130 | ); 131 | name = "Supporting Files"; 132 | sourceTree = ""; 133 | }; 134 | 6BE4615A5136AF62C9FEA1E4 /* Products */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 6BE46C83278BC1BD5DB3F965 /* Ponto.app */, 138 | 6BE468BAA200487C0A00CFAF /* PontoTests.octest */, 139 | ); 140 | name = Products; 141 | sourceTree = ""; 142 | }; 143 | 6BE462237E242E7B0288C80A /* Frameworks */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 3BC470241611C73E00EF9B85 /* libobjc.A.dylib */, 147 | 3BC4702116106F3900EF9B85 /* MessageUI.framework */, 148 | 6BE46BFCA9580E6458FB9EBC /* SenTestingKit.framework */, 149 | 6BE46A58BA5C67AAED1D0F33 /* CoreGraphics.framework */, 150 | 6BE46B77CEFBBF875DBB2E8C /* Foundation.framework */, 151 | 6BE46CDD43F0AE5328FB15E1 /* UIKit.framework */, 152 | ); 153 | name = Frameworks; 154 | sourceTree = ""; 155 | }; 156 | 6BE464657B1F0E1D4C950DA3 /* PontoDemo */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 6BE46D4AB6F8343C1C6BA566 /* PontoDEMOMessaging.h */, 160 | 6BE46BF931753016EBE51652 /* PontoDEMOMessaging.m */, 161 | 6BE466DE016372E65DE56D12 /* PontoDEMOAppDelegate.m */, 162 | 6BE46AF5FD42A3BAB1104831 /* PontoDEMOAppDelegate.h */, 163 | 6BE46093C45C33F82D79AB7B /* Supporting Files */, 164 | 3BC46FF7160CBA6500EF9B85 /* PontoDEMOViewController.h */, 165 | 3BC46FF8160CBA6500EF9B85 /* PontoDEMOViewController.m */, 166 | 3BC46FF9160CBA6500EF9B85 /* PontoDEMOViewController.xib */, 167 | ); 168 | name = PontoDemo; 169 | path = Ponto; 170 | sourceTree = ""; 171 | }; 172 | 6BE464B6FEFDF2F84A9CAC33 /* html */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | 3BC47013160CCC3A00EF9B85 /* web */, 176 | 6BE4606A0AA3374F68EBF395 /* pontoDemo.html */, 177 | ); 178 | path = html; 179 | sourceTree = ""; 180 | }; 181 | 6BE467EE7E0D429D5295DBBD /* PontoTests */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | 6BE46871E65DE45310604262 /* PontoTests.m */, 185 | 6BE46E3DFE2F6474C174E7DF /* PontoTests.h */, 186 | 6BE46106C37C5301797532A2 /* Supporting Files */, 187 | ); 188 | path = PontoTests; 189 | sourceTree = ""; 190 | }; 191 | 6BE468E3549FFD94E16C08A2 /* Resources */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 6BE464B6FEFDF2F84A9CAC33 /* html */, 195 | ); 196 | path = Resources; 197 | sourceTree = ""; 198 | }; 199 | 6BE46DB786E89936EC7536CC = { 200 | isa = PBXGroup; 201 | children = ( 202 | 6BE46FD2B3707FDC2C73F0A8 /* PontoLib */, 203 | 6BE4615A5136AF62C9FEA1E4 /* Products */, 204 | 6BE462237E242E7B0288C80A /* Frameworks */, 205 | 6BE464657B1F0E1D4C950DA3 /* PontoDemo */, 206 | 6BE467EE7E0D429D5295DBBD /* PontoTests */, 207 | 6BE468E3549FFD94E16C08A2 /* Resources */, 208 | ); 209 | sourceTree = ""; 210 | }; 211 | 6BE46FD2B3707FDC2C73F0A8 /* PontoLib */ = { 212 | isa = PBXGroup; 213 | children = ( 214 | 6BE4649481F8EA1C75B45022 /* PontoDispatcher.h */, 215 | 6BE46633588BC0F02C8BABC1 /* PontoDispatcher.m */, 216 | 6BE46200F35DE4829ED3A34A /* PontoBaseHandler.h */, 217 | 6BE468667F12B9175F833049 /* PontoBaseHandler.m */, 218 | 6BE465EDF5FCA97F788F275B /* NSURL+QueryDictionary.h */, 219 | 6BE4618E195D0F96A202FBA1 /* NSURL+QueryDictionary.m */, 220 | ); 221 | path = PontoLib; 222 | sourceTree = ""; 223 | }; 224 | /* End PBXGroup section */ 225 | 226 | /* Begin PBXNativeTarget section */ 227 | 6BE464C358EA765758A9E7B0 /* Ponto */ = { 228 | isa = PBXNativeTarget; 229 | buildConfigurationList = 6BE46C7F6CD63DB72DF602A0 /* Build configuration list for PBXNativeTarget "Ponto" */; 230 | buildPhases = ( 231 | 6BE4669A412E522A6331438A /* Sources */, 232 | 6BE46A08A524EB5E2A11646D /* Frameworks */, 233 | 6BE46A83D273E5780F0C23A2 /* Resources */, 234 | ); 235 | buildRules = ( 236 | ); 237 | dependencies = ( 238 | ); 239 | name = Ponto; 240 | productName = Ponto; 241 | productReference = 6BE46C83278BC1BD5DB3F965 /* Ponto.app */; 242 | productType = "com.apple.product-type.application"; 243 | }; 244 | 6BE46AD65F8439FDCF85DF8D /* PontoTests */ = { 245 | isa = PBXNativeTarget; 246 | buildConfigurationList = 6BE469E014AC7072DBF0A02B /* Build configuration list for PBXNativeTarget "PontoTests" */; 247 | buildPhases = ( 248 | 6BE46A07545D177645F7FD11 /* Sources */, 249 | 6BE467D662B856CE9AFD3E9F /* Frameworks */, 250 | 6BE465FF61B7AA0433EC8630 /* Resources */, 251 | 6BE464B4AA6065CCCA878F54 /* ShellScript */, 252 | ); 253 | buildRules = ( 254 | ); 255 | dependencies = ( 256 | 6BE46D150F0D194A1EFC7220 /* PBXTargetDependency */, 257 | ); 258 | name = PontoTests; 259 | productName = PontoTests; 260 | productReference = 6BE468BAA200487C0A00CFAF /* PontoTests.octest */; 261 | productType = "com.apple.product-type.bundle"; 262 | }; 263 | /* End PBXNativeTarget section */ 264 | 265 | /* Begin PBXProject section */ 266 | 6BE46A497320EB524FF754F6 /* Project object */ = { 267 | isa = PBXProject; 268 | attributes = { 269 | }; 270 | buildConfigurationList = 6BE46E9490E9050BBD4C5957 /* Build configuration list for PBXProject "Ponto" */; 271 | compatibilityVersion = "Xcode 3.2"; 272 | developmentRegion = English; 273 | hasScannedForEncodings = 0; 274 | knownRegions = ( 275 | en, 276 | ); 277 | mainGroup = 6BE46DB786E89936EC7536CC; 278 | productRefGroup = 6BE4615A5136AF62C9FEA1E4 /* Products */; 279 | projectDirPath = ""; 280 | projectRoot = ""; 281 | targets = ( 282 | 6BE464C358EA765758A9E7B0 /* Ponto */, 283 | 6BE46AD65F8439FDCF85DF8D /* PontoTests */, 284 | ); 285 | }; 286 | /* End PBXProject section */ 287 | 288 | /* Begin PBXResourcesBuildPhase section */ 289 | 6BE465FF61B7AA0433EC8630 /* Resources */ = { 290 | isa = PBXResourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 6BE4664446C59E3D405FFFBC /* InfoPlist.strings in Resources */, 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | 6BE46A83D273E5780F0C23A2 /* Resources */ = { 298 | isa = PBXResourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 6BE464DA2CBF20F3F0E9598E /* InfoPlist.strings in Resources */, 302 | 6BE46B62708A8778C71D3830 /* Default.png in Resources */, 303 | 6BE46654BA09F57036442FA8 /* Default@2x.png in Resources */, 304 | 6BE461F0B974327ECDD6040F /* Default-568h@2x.png in Resources */, 305 | 3BC47011160CCBFD00EF9B85 /* pontoDemo.html in Resources */, 306 | 3BC46FFB160CBA6500EF9B85 /* PontoDEMOViewController.xib in Resources */, 307 | 3BC47014160CCC3A00EF9B85 /* web in Resources */, 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | /* End PBXResourcesBuildPhase section */ 312 | 313 | /* Begin PBXShellScriptBuildPhase section */ 314 | 6BE464B4AA6065CCCA878F54 /* ShellScript */ = { 315 | isa = PBXShellScriptBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | ); 319 | inputPaths = ( 320 | ); 321 | outputPaths = ( 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | shellPath = /bin/sh; 325 | shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; 326 | }; 327 | /* End PBXShellScriptBuildPhase section */ 328 | 329 | /* Begin PBXSourcesBuildPhase section */ 330 | 6BE4669A412E522A6331438A /* Sources */ = { 331 | isa = PBXSourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | 6BE46FF6F731FA3C4F92ECD6 /* main.m in Sources */, 335 | 6BE4694713121642936BA592 /* PontoDEMOAppDelegate.m in Sources */, 336 | 6BE46770D0B669331645BE6D /* PontoBaseHandler.m in Sources */, 337 | 6BE4633715CFDB994597632F /* PontoDispatcher.m in Sources */, 338 | 3BC46FFA160CBA6500EF9B85 /* PontoDEMOViewController.m in Sources */, 339 | 6BE46C28E5CFCDD17A958456 /* PontoDEMOMessaging.m in Sources */, 340 | 6BE464C927BAE9BDBB9256CB /* NSURL+QueryDictionary.m in Sources */, 341 | ); 342 | runOnlyForDeploymentPostprocessing = 0; 343 | }; 344 | 6BE46A07545D177645F7FD11 /* Sources */ = { 345 | isa = PBXSourcesBuildPhase; 346 | buildActionMask = 2147483647; 347 | files = ( 348 | 6BE4680C8461991F91F564A0 /* PontoTests.m in Sources */, 349 | 6BE469771F6E5E733EFD97FB /* PontoBaseHandler.m in Sources */, 350 | 6BE46AC9EF9D7888079E6617 /* PontoDispatcher.m in Sources */, 351 | 6BE46D73BCBBA2F9A6AA474A /* PontoDEMOMessaging.m in Sources */, 352 | 6BE464608BB04ABB3FC1F59A /* NSURL+QueryDictionary.m in Sources */, 353 | ); 354 | runOnlyForDeploymentPostprocessing = 0; 355 | }; 356 | /* End PBXSourcesBuildPhase section */ 357 | 358 | /* Begin PBXTargetDependency section */ 359 | 6BE46D150F0D194A1EFC7220 /* PBXTargetDependency */ = { 360 | isa = PBXTargetDependency; 361 | target = 6BE464C358EA765758A9E7B0 /* Ponto */; 362 | targetProxy = 6BE460A8857D015E1D7790DC /* PBXContainerItemProxy */; 363 | }; 364 | /* End PBXTargetDependency section */ 365 | 366 | /* Begin PBXVariantGroup section */ 367 | 6BE4626D12713D1B043B7930 /* InfoPlist.strings */ = { 368 | isa = PBXVariantGroup; 369 | children = ( 370 | 6BE46DCE10FAB9400863FEC0 /* en */, 371 | ); 372 | name = InfoPlist.strings; 373 | sourceTree = ""; 374 | }; 375 | 6BE468FFE1FC6C4B414CFBA3 /* InfoPlist.strings */ = { 376 | isa = PBXVariantGroup; 377 | children = ( 378 | 6BE46023DFB838841553CE7A /* en */, 379 | ); 380 | name = InfoPlist.strings; 381 | sourceTree = ""; 382 | }; 383 | /* End PBXVariantGroup section */ 384 | 385 | /* Begin XCBuildConfiguration section */ 386 | 6BE4602FD3CBC36D21407879 /* Release */ = { 387 | isa = XCBuildConfiguration; 388 | buildSettings = { 389 | ALWAYS_SEARCH_USER_PATHS = NO; 390 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 391 | CLANG_CXX_LIBRARY = "libc++"; 392 | CLANG_ENABLE_OBJC_ARC = YES; 393 | CLANG_WARN_EMPTY_BODY = YES; 394 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 395 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 396 | COPY_PHASE_STRIP = YES; 397 | GCC_C_LANGUAGE_STANDARD = gnu99; 398 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 402 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 403 | SDKROOT = iphoneos; 404 | VALIDATE_PRODUCT = YES; 405 | }; 406 | name = Release; 407 | }; 408 | 6BE4662CE8D09723CA77F7AD /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 412 | GCC_PREFIX_HEADER = "Ponto/Ponto-Prefix.pch"; 413 | INFOPLIST_FILE = "Ponto/Ponto-Info.plist"; 414 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | WRAPPER_EXTENSION = app; 417 | }; 418 | name = Debug; 419 | }; 420 | 6BE46955C5333F5219350062 /* Release */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 424 | GCC_PREFIX_HEADER = "Ponto/Ponto-Prefix.pch"; 425 | INFOPLIST_FILE = "Ponto/Ponto-Info.plist"; 426 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 427 | PRODUCT_NAME = "$(TARGET_NAME)"; 428 | WRAPPER_EXTENSION = app; 429 | }; 430 | name = Release; 431 | }; 432 | 6BE46D26B622DD9B0304AF98 /* Release */ = { 433 | isa = XCBuildConfiguration; 434 | buildSettings = { 435 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Ponto.app/Ponto"; 436 | FRAMEWORK_SEARCH_PATHS = ( 437 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 438 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 439 | ); 440 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 441 | GCC_PREFIX_HEADER = "Ponto/Ponto-Prefix.pch"; 442 | INFOPLIST_FILE = "PontoTests/PontoTests-Info.plist"; 443 | PRODUCT_NAME = "$(TARGET_NAME)"; 444 | TEST_HOST = "$(BUNDLE_LOADER)"; 445 | WRAPPER_EXTENSION = octest; 446 | }; 447 | name = Release; 448 | }; 449 | 6BE46DC04988953E2191D21B /* Debug */ = { 450 | isa = XCBuildConfiguration; 451 | buildSettings = { 452 | ALWAYS_SEARCH_USER_PATHS = NO; 453 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 454 | CLANG_CXX_LIBRARY = "libc++"; 455 | CLANG_ENABLE_OBJC_ARC = YES; 456 | CLANG_WARN_EMPTY_BODY = YES; 457 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 458 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 459 | COPY_PHASE_STRIP = NO; 460 | GCC_C_LANGUAGE_STANDARD = gnu99; 461 | GCC_DYNAMIC_NO_PIC = NO; 462 | GCC_OPTIMIZATION_LEVEL = 0; 463 | GCC_PREPROCESSOR_DEFINITIONS = ( 464 | "DEBUG=1", 465 | "$(inherited)", 466 | ); 467 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 468 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 469 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 470 | GCC_WARN_UNUSED_VARIABLE = YES; 471 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 472 | SDKROOT = iphoneos; 473 | }; 474 | name = Debug; 475 | }; 476 | 6BE46FEA661137ACEC527B32 /* Debug */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Ponto.app/Ponto"; 480 | FRAMEWORK_SEARCH_PATHS = ( 481 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 482 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 483 | ); 484 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 485 | GCC_PREFIX_HEADER = "Ponto/Ponto-Prefix.pch"; 486 | INFOPLIST_FILE = "PontoTests/PontoTests-Info.plist"; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | TEST_HOST = "$(BUNDLE_LOADER)"; 489 | WRAPPER_EXTENSION = octest; 490 | }; 491 | name = Debug; 492 | }; 493 | /* End XCBuildConfiguration section */ 494 | 495 | /* Begin XCConfigurationList section */ 496 | 6BE469E014AC7072DBF0A02B /* Build configuration list for PBXNativeTarget "PontoTests" */ = { 497 | isa = XCConfigurationList; 498 | buildConfigurations = ( 499 | 6BE46D26B622DD9B0304AF98 /* Release */, 500 | 6BE46FEA661137ACEC527B32 /* Debug */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | 6BE46C7F6CD63DB72DF602A0 /* Build configuration list for PBXNativeTarget "Ponto" */ = { 506 | isa = XCConfigurationList; 507 | buildConfigurations = ( 508 | 6BE46955C5333F5219350062 /* Release */, 509 | 6BE4662CE8D09723CA77F7AD /* Debug */, 510 | ); 511 | defaultConfigurationIsVisible = 0; 512 | defaultConfigurationName = Release; 513 | }; 514 | 6BE46E9490E9050BBD4C5957 /* Build configuration list for PBXProject "Ponto" */ = { 515 | isa = XCConfigurationList; 516 | buildConfigurations = ( 517 | 6BE4602FD3CBC36D21407879 /* Release */, 518 | 6BE46DC04988953E2191D21B /* Debug */, 519 | ); 520 | defaultConfigurationIsVisible = 0; 521 | defaultConfigurationName = Release; 522 | }; 523 | /* End XCConfigurationList section */ 524 | }; 525 | rootObject = 6BE46A497320EB524FF754F6 /* Project object */; 526 | } 527 | -------------------------------------------------------------------------------- /ios/Ponto/Ponto/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikia/ponto/6ae2171778634626865019c7c4ee60c07b26228c/ios/Ponto/Ponto/Default-568h@2x.png -------------------------------------------------------------------------------- /ios/Ponto/Ponto/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikia/ponto/6ae2171778634626865019c7c4ee60c07b26228c/ios/Ponto/Ponto/Default.png -------------------------------------------------------------------------------- /ios/Ponto/Ponto/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikia/ponto/6ae2171778634626865019c7c4ee60c07b26228c/ios/Ponto/Ponto/Default@2x.png -------------------------------------------------------------------------------- /ios/Ponto/Ponto/Ponto-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CFBundleIdentifier 7 | com.wikia.ponto.${PRODUCT_NAME:rfc1034identifier} 8 | 9 | CFBundleDevelopmentRegion 10 | en 11 | CFBundleExecutable 12 | ${EXECUTABLE_NAME} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | ???? 21 | 22 | CFBundleDisplayName 23 | ${PRODUCT_NAME} 24 | CFBundleVersion 25 | 1.0 26 | CFBundleShortVersionString 27 | 1.0 28 | LSRequiresIPhoneOS 29 | 30 | UIRequiredDeviceCapabilities 31 | 32 | armv7 33 | 34 | UISupportedInterfaceOrientations 35 | 36 | UIInterfaceOrientationPortrait 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | 41 | -------------------------------------------------------------------------------- /ios/Ponto/Ponto/Ponto-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Ponto' target in the 'Ponto' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iOS SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | 14 | #import 15 | 16 | #endif -------------------------------------------------------------------------------- /ios/Ponto/Ponto/PontoDEMOAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // PontoDEMOAppDelegate.h 3 | // Ponto 4 | // 5 | // Created by Gregor on 09/21/12. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface PontoDEMOAppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | @property (nonatomic, strong) UINavigationController *navigationController; 16 | 17 | - (void)displaySendEmailMessagePickerWithRecipients:(NSArray *)recipients andSubject:(NSString *)subject andBody:(NSString *)body; 18 | 19 | @end -------------------------------------------------------------------------------- /ios/Ponto/Ponto/PontoDEMOAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // PontoDEMOAppDelegate.m 3 | // Ponto 4 | // 5 | // Created by Gregor on 09/21/12. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import "PontoDEMOAppDelegate.h" 10 | #import "PontoDEMOViewController.h" 11 | 12 | @implementation PontoDEMOAppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | // Override point for customization after application launch. 18 | 19 | PontoDEMOViewController *pontoDEMOViewController = [[PontoDEMOViewController alloc] initWithNibName:@"PontoDEMOViewController" bundle:[NSBundle mainBundle]]; 20 | self.navigationController = [[UINavigationController alloc] initWithRootViewController:pontoDEMOViewController]; 21 | self.window.rootViewController = self.navigationController; 22 | [self.window makeKeyAndVisible]; 23 | return YES; 24 | } 25 | 26 | - (void)applicationWillResignActive:(UIApplication *)application 27 | { 28 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 29 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 30 | 31 | } 32 | 33 | - (void)applicationDidEnterBackground:(UIApplication *)application 34 | { 35 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 36 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 37 | 38 | } 39 | 40 | - (void)applicationWillEnterForeground:(UIApplication *)application 41 | { 42 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 43 | 44 | } 45 | 46 | - (void)applicationDidBecomeActive:(UIApplication *)application 47 | { 48 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 49 | 50 | } 51 | 52 | - (void)applicationWillTerminate:(UIApplication *)application 53 | { 54 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 55 | 56 | } 57 | 58 | - (void)displaySendEmailMessagePickerWithRecipients:(NSArray *)recipients andSubject:(NSString *)subject andBody:(NSString *)body { 59 | MFMailComposeViewController *mailComposeViewController = [[MFMailComposeViewController alloc] init]; 60 | mailComposeViewController.mailComposeDelegate = self; 61 | [mailComposeViewController setToRecipients:recipients]; 62 | [mailComposeViewController setSubject:subject]; 63 | [mailComposeViewController setMessageBody:body isHTML:YES]; 64 | [self.navigationController presentModalViewController:mailComposeViewController animated:YES]; 65 | } 66 | 67 | - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { 68 | [self.navigationController dismissModalViewControllerAnimated:YES]; 69 | } 70 | 71 | @end -------------------------------------------------------------------------------- /ios/Ponto/Ponto/PontoDEMOMessaging.h: -------------------------------------------------------------------------------- 1 | // 2 | // PontoDEMOMessaging 3 | // Ponto 4 | // 5 | // Created by Grzegorz Nowicki on 24.09.2012. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PontoBaseHandler.h" 11 | 12 | @interface PontoDEMOMessaging : PontoBaseHandler 13 | 14 | @end -------------------------------------------------------------------------------- /ios/Ponto/Ponto/PontoDEMOMessaging.m: -------------------------------------------------------------------------------- 1 | // 2 | // PontoDEMOMessaging 3 | // Ponto 4 | // 5 | // Created by Grzegorz Nowicki on 24.09.2012. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import "PontoDEMOMessaging.h" 10 | #import "PontoDEMOAppDelegate.h" 11 | 12 | 13 | @implementation PontoDEMOMessaging { 14 | 15 | } 16 | 17 | + (id)instance { 18 | return [[self alloc] init]; 19 | } 20 | 21 | - (void)sendMessage:(id)params { 22 | if ([params isKindOfClass:[NSDictionary class]]) { 23 | PontoDEMOAppDelegate *app = (PontoDEMOAppDelegate*)[[UIApplication sharedApplication] delegate]; 24 | 25 | NSString *subject = [(NSDictionary *)params objectForKey:@"subject"]; 26 | NSString *body = [(NSDictionary *)params objectForKey:@"body"]; 27 | id recipients = [(NSDictionary *)params objectForKey:@"to"]; 28 | 29 | if ([recipients isKindOfClass:[NSArray class]]) { 30 | [app displaySendEmailMessagePickerWithRecipients:(NSArray *)recipients andSubject:subject andBody:body]; 31 | } 32 | else if ([recipients isKindOfClass:[NSString class]]) { 33 | [app displaySendEmailMessagePickerWithRecipients:[NSArray arrayWithObject:(NSString *)recipients] andSubject:subject andBody:body]; 34 | } 35 | else{ 36 | [app displaySendEmailMessagePickerWithRecipients:nil andSubject:subject andBody:body]; 37 | } 38 | } 39 | } 40 | 41 | - (NSDictionary *)getSomeDataFromHandler { 42 | return [NSDictionary dictionaryWithObjectsAndKeys: 43 | @"wikia.com", @"url", 44 | @"iOS", @"current_platform", 45 | @"0.1", @"ponto_version", 46 | nil 47 | ]; 48 | } 49 | 50 | @end -------------------------------------------------------------------------------- /ios/Ponto/Ponto/PontoDEMOViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PontoDEMOViewController.h 3 | // Ponto 4 | // 5 | // Created by Gregor on 21.09.2012. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PontoDispatcher.h" 11 | 12 | @interface PontoDEMOViewController : UIViewController 13 | 14 | @property (strong, nonatomic) IBOutlet UIWebView *webView; 15 | @end 16 | -------------------------------------------------------------------------------- /ios/Ponto/Ponto/PontoDEMOViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PontoDEMOViewController.m 3 | // Ponto 4 | // 5 | // Created by Gregor on 21.09.2012. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import "PontoDEMOViewController.h" 10 | 11 | @interface PontoDEMOViewController () 12 | 13 | @property (nonatomic, strong) PontoDispatcher *pontoDispatcher; 14 | 15 | @end 16 | 17 | @implementation PontoDEMOViewController 18 | 19 | - (void)viewDidLoad 20 | { 21 | 22 | 23 | [super viewDidLoad]; 24 | 25 | 26 | // Create Ponto Dispatcher 27 | self.title = @"Ponto DEMO WebView"; 28 | self.pontoDispatcher = [[PontoDispatcher alloc] initWithHandlerClassesPrefix:@"PontoDEMO" andWebView:self.webView]; 29 | 30 | // try to call JS method 31 | [self.pontoDispatcher invokeMethod:@"testMethod" onTarget:@"TODO_target" withParams:nil successBlock:^(id params) { 32 | NSLog(@"success block with params: %@", params); 33 | } errorBlock:^(id params) { 34 | NSLog(@"error block with params: %@", params); 35 | }]; 36 | 37 | 38 | // Load local HTML file 39 | NSString *pathToLocalFile = [[NSBundle mainBundle] pathForResource:@"pontoDemo" ofType:@"html"]; 40 | NSURL *localFileURL = [[NSURL alloc] initFileURLWithPath:pathToLocalFile]; 41 | NSURLRequest *localFileRequest = [[NSURLRequest alloc] initWithURL:localFileURL]; 42 | [self.webView loadRequest:localFileRequest]; 43 | } 44 | 45 | 46 | #pragma mark - PontoDispatcherCallbackDelegate methods 47 | 48 | - (void)successCallbackWithParams:(id)params { 49 | NSLog(@"success callback with params: %@", params); 50 | } 51 | 52 | - (void)errorCallbackWithParams:(id)params { 53 | NSLog(@"error callback with params: %@", params); 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /ios/Ponto/Ponto/PontoDEMOViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1536 5 | 12C54 6 | 2840 7 | 1187.34 8 | 625.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 1926 12 | 13 | 14 | IBNSLayoutConstraint 15 | IBProxyObject 16 | IBUIView 17 | IBUIWebView 18 | 19 | 20 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 21 | 22 | 23 | PluginDependencyRecalculationVersion 24 | 25 | 26 | 27 | 28 | IBFilesOwner 29 | IBCocoaTouchFramework 30 | 31 | 32 | IBFirstResponder 33 | IBCocoaTouchFramework 34 | 35 | 36 | 37 | 274 38 | 39 | 40 | 41 | 274 42 | {320, 460} 43 | 44 | 45 | _NS:9 46 | 47 | 1 48 | MSAxIDEAA 49 | 50 | IBCocoaTouchFramework 51 | 1 52 | YES 53 | 54 | 55 | {{0, 20}, {320, 460}} 56 | 57 | 58 | 59 | 3 60 | MQA 61 | 62 | 2 63 | 64 | 65 | 66 | IBCocoaTouchFramework 67 | 68 | 69 | 70 | 71 | 72 | 73 | view 74 | 75 | 76 | 77 | 3 78 | 79 | 80 | 81 | webView 82 | 83 | 84 | 85 | 10 86 | 87 | 88 | 89 | 90 | 91 | 0 92 | 93 | 94 | 95 | 96 | 97 | 1 98 | 99 | 100 | 101 | 102 | 103 | 4 104 | 0 105 | 106 | 4 107 | 1 108 | 109 | 0.0 110 | 111 | 1000 112 | 113 | 8 114 | 29 115 | 3 116 | 117 | 118 | 119 | 3 120 | 0 121 | 122 | 3 123 | 1 124 | 125 | 0.0 126 | 127 | 1000 128 | 129 | 8 130 | 29 131 | 3 132 | 133 | 134 | 135 | 5 136 | 0 137 | 138 | 5 139 | 1 140 | 141 | 0.0 142 | 143 | 1000 144 | 145 | 8 146 | 29 147 | 3 148 | 149 | 150 | 151 | 6 152 | 0 153 | 154 | 6 155 | 1 156 | 157 | 0.0 158 | 159 | 1000 160 | 161 | 8 162 | 29 163 | 3 164 | 165 | 166 | 167 | 168 | 169 | -1 170 | 171 | 172 | File's Owner 173 | 174 | 175 | -2 176 | 177 | 178 | 179 | 180 | 4 181 | 182 | 183 | 184 | 185 | 5 186 | 187 | 188 | 189 | 190 | 6 191 | 192 | 193 | 194 | 195 | 7 196 | 197 | 198 | 199 | 200 | 8 201 | 202 | 203 | 204 | 205 | 206 | 207 | PontoDEMOViewController 208 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 209 | UIResponder 210 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 211 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 212 | 213 | 214 | 215 | 216 | 217 | 218 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 219 | 220 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 221 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 222 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 223 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 224 | 225 | 226 | 227 | 228 | 229 | 10 230 | 231 | 232 | 233 | 234 | NSLayoutConstraint 235 | NSObject 236 | 237 | IBProjectSource 238 | ./Classes/NSLayoutConstraint.h 239 | 240 | 241 | 242 | PontoDEMOViewController 243 | UIViewController 244 | 245 | webView 246 | UIWebView 247 | 248 | 249 | webView 250 | 251 | webView 252 | UIWebView 253 | 254 | 255 | 256 | IBProjectSource 257 | ./Classes/PontoDEMOViewController.h 258 | 259 | 260 | 261 | 262 | 0 263 | IBCocoaTouchFramework 264 | YES 265 | 3 266 | YES 267 | 1926 268 | 269 | 270 | -------------------------------------------------------------------------------- /ios/Ponto/Ponto/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | -------------------------------------------------------------------------------- /ios/Ponto/Ponto/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Ponto 4 | // 5 | // Created by Gregor on 09/21/12. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "PontoDEMOAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([PontoDEMOAppDelegate class])); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /ios/Ponto/PontoLib/NSURL+QueryDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+Query.h 3 | // NSURL+Query 4 | // 5 | // Created by Jon Crooke on 10/12/2013. 6 | // Copyright (c) 2013 Jonathan Crooke. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSURL (UQ_URLQuery) 12 | 13 | /** 14 | * @return URL's query component as keys/values 15 | * Returns nil for an empty query 16 | */ 17 | - (NSDictionary*) uq_queryDictionary; 18 | 19 | /** 20 | * @return URL with keys values appending to query string 21 | * @param queryDictionary Query keys/values 22 | * @param sortedKeys Sorted the keys alphabetically? 23 | * @warning If keys overlap in receiver and query dictionary, 24 | * behaviour is undefined. 25 | */ 26 | - (NSURL*) uq_URLByAppendingQueryDictionary:(NSDictionary*) queryDictionary 27 | withSortedKeys:(BOOL) sortedKeys; 28 | 29 | /** As above, but `sortedKeys=NO` */ 30 | - (NSURL*) uq_URLByAppendingQueryDictionary:(NSDictionary*) queryDictionary; 31 | 32 | @end 33 | 34 | #pragma mark - 35 | 36 | @interface NSString (UQ_URLQuery) 37 | 38 | /** 39 | * @return If the receiver is a valid URL query component, returns 40 | * components as key/value pairs. If couldn't split into *any* pairs, 41 | * returns nil. 42 | */ 43 | - (NSDictionary*) uq_URLQueryDictionary; 44 | 45 | @end 46 | 47 | #pragma mark - 48 | 49 | @interface NSDictionary (UQ_URLQuery) 50 | 51 | /** 52 | * @return URL query string component created from the keys and values in 53 | * the dictionary. Returns nil for an empty dictionary. 54 | * @param sortedKeys Sorted the keys alphabetically? 55 | * @see cavetas from the main `NSURL` category as well. 56 | */ 57 | - (NSString*) uq_URLQueryStringWithSortedKeys:(BOOL) sortedKeys; 58 | 59 | /** As above, but `sortedKeys=NO` */ 60 | - (NSString*) uq_URLQueryString; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /ios/Ponto/PontoLib/NSURL+QueryDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+Query.m 3 | // NSURL+Query 4 | // 5 | // Created by Jon Crooke on 10/12/2013. 6 | // Copyright (c) 2013 Jonathan Crooke. All rights reserved. 7 | // 8 | 9 | #import "NSURL+QueryDictionary.h" 10 | 11 | static NSString *const kQuerySeparator = @"&"; 12 | static NSString *const kQueryDivider = @"="; 13 | static NSString *const kQueryBegin = @"?"; 14 | static NSString *const kFragmentBegin = @"#"; 15 | 16 | @implementation NSURL (UQ_URLQuery) 17 | 18 | - (NSDictionary*) uq_queryDictionary { 19 | return self.query.uq_URLQueryDictionary; 20 | } 21 | 22 | - (NSURL*) uq_URLByAppendingQueryDictionary:(NSDictionary*) queryDictionary { 23 | return [self uq_URLByAppendingQueryDictionary:queryDictionary withSortedKeys:NO]; 24 | } 25 | 26 | - (NSURL *)uq_URLByAppendingQueryDictionary:(NSDictionary *)queryDictionary 27 | withSortedKeys:(BOOL)sortedKeys 28 | { 29 | NSMutableArray *queries = self.query ? @[self.query].mutableCopy : @[].mutableCopy; 30 | NSString *dictionaryQuery = [queryDictionary uq_URLQueryStringWithSortedKeys:sortedKeys]; 31 | if (dictionaryQuery) { 32 | [queries addObject:dictionaryQuery]; 33 | } 34 | NSString *newQuery = [queries componentsJoinedByString:kQuerySeparator]; 35 | 36 | if (newQuery.length) { 37 | NSArray *queryComponents = [self.absoluteString componentsSeparatedByString:kQueryBegin]; 38 | if (queryComponents.count) { 39 | return [NSURL URLWithString: 40 | [NSString stringWithFormat:@"%@%@%@%@%@", 41 | queryComponents[0], // existing url 42 | kQueryBegin, 43 | newQuery, 44 | self.fragment.length ? kFragmentBegin : @"", 45 | self.fragment.length ? self.fragment : @""]]; 46 | } 47 | } 48 | return self; 49 | } 50 | 51 | @end 52 | 53 | #pragma mark - 54 | 55 | @implementation NSString (URLQuery) 56 | 57 | - (NSDictionary*) uq_URLQueryDictionary { 58 | NSMutableDictionary *mute = @{}.mutableCopy; 59 | for (NSString *query in [self componentsSeparatedByString:kQuerySeparator]) { 60 | NSArray *components = [query componentsSeparatedByString:kQueryDivider]; 61 | if (components.count == 0) { 62 | continue; 63 | } 64 | NSString *key = [components[0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 65 | id value = nil; 66 | if (components.count == 1) { 67 | // key with no value 68 | value = [NSNull null]; 69 | } 70 | if (components.count == 2) { 71 | value = [components[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 72 | // cover case where there is a separator, but no actual value 73 | value = [value length] ? value : [NSNull null]; 74 | } 75 | if (components.count > 2) { 76 | // invalid - ignore this pair. is this best, though? 77 | continue; 78 | } 79 | mute[key] = value ?: [NSNull null]; 80 | } 81 | return mute.count ? mute.copy : nil; 82 | } 83 | 84 | @end 85 | 86 | #pragma mark - 87 | 88 | @implementation NSDictionary (URLQuery) 89 | 90 | - (NSString *)uq_URLQueryString { 91 | return [self uq_URLQueryStringWithSortedKeys:NO]; 92 | } 93 | 94 | - (NSString*) uq_URLQueryStringWithSortedKeys:(BOOL)sortedKeys { 95 | NSMutableString *queryString = @"".mutableCopy; 96 | NSArray *keys = sortedKeys ? [self.allKeys sortedArrayUsingSelector:@selector(compare:)] : self.allKeys; 97 | for (NSString *key in keys) { 98 | id rawValue = self[key]; 99 | NSString *value = nil; 100 | // beware of empty or null 101 | if (!(rawValue == [NSNull null] || ![rawValue description].length)) { 102 | value = [[self[key] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 103 | } 104 | [queryString appendFormat:@"%@%@%@%@", 105 | queryString.length ? kQuerySeparator : @"", // appending? 106 | [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], 107 | value ? kQueryDivider : @"", 108 | value ? value : @""]; 109 | } 110 | return queryString.length ? queryString.copy : nil; 111 | } 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /ios/Ponto/PontoLib/PontoBaseHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // PontoBaseHandler 3 | // Ponto 4 | // 5 | // Created by Grzegorz Nowicki on 21.09.2012. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface PontoBaseHandler : NSObject 13 | 14 | + (id)instance; 15 | 16 | @end -------------------------------------------------------------------------------- /ios/Ponto/PontoLib/PontoBaseHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // PontoBaseHandler 3 | // Ponto 4 | // 5 | // Created by Grzegorz Nowicki on 21.09.2012. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import "PontoBaseHandler.h" 10 | 11 | 12 | @implementation PontoBaseHandler { 13 | 14 | } 15 | 16 | + (id)instance { 17 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 18 | reason:[NSString stringWithFormat:@"The %s method needs to be overridden in PontoBaseHandler subclasses", __PRETTY_FUNCTION__] 19 | userInfo:nil]; 20 | } 21 | 22 | @end -------------------------------------------------------------------------------- /ios/Ponto/PontoLib/PontoDispatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // PontoDispatcher 3 | // Ponto 4 | // 5 | // Created by Grzegorz Nowicki on 21.09.2012. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PontoBaseHandler.h" 11 | #import 12 | 13 | @protocol PontoDispatcherDelegate 14 | @required 15 | - (id)deserializeJSONString:(NSString *)JSON; 16 | - (NSString *)serializeObjectToJSON:(id)someObject; 17 | 18 | @end 19 | 20 | 21 | @protocol PontoDispatcherCallbackDelegate 22 | @optional 23 | - (void)successCallbackWithParams:(id)params; 24 | - (void)errorCallbackWithParams:(id)params; 25 | 26 | @end 27 | 28 | @interface PontoDispatcher : NSObject 29 | 30 | @property (nonatomic, strong) NSString *handlerClassesPrefix; 31 | @property (nonatomic, weak) UIWebView *webView; 32 | @property (nonatomic, weak) id delegate; 33 | @property (nonatomic, weak) WKWebView *webKitView; 34 | 35 | - (id)initWithHandlerClassesPrefix:(NSString *)classesPrefix; 36 | - (id)initWithHandlerClassesPrefix:(NSString *)classesPrefix andWebView:(UIWebView *)webView; 37 | 38 | - (void)invokeMethod:(NSString *)methodName onTarget:(NSString *)target withParams:(id)params successBlock:(void(^)(id params))successBlock errorBlock:(void(^)(id params)) errorBlock; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /ios/Ponto/PontoLib/PontoDispatcher.m: -------------------------------------------------------------------------------- 1 | // 2 | // PontoDispatcher 3 | // Ponto 4 | // 5 | // Created by Grzegorz Nowicki on 21.09.2012. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import "PontoDispatcher.h" 10 | #import 11 | #import "NSURL+QueryDictionary.h" 12 | 13 | #define RESPONSE_COMPLETE 0 14 | #define RESPONSE_ERROR 1 15 | 16 | NSString *const kPontoUrlScheme = @"ponto"; 17 | NSString *const kPontoTargetParamName = @"target"; 18 | NSString *const kPontoMethodParamName = @"method"; 19 | NSString *const kPontoParamsParamName = @"params"; 20 | NSString *const kPontoCallbackIdParamName = @"callbackId"; 21 | NSString *const kPontoCallbackJSString = @"Ponto.response(decodeURIComponent('%@'));"; 22 | NSString *const kPontoMethodInvokeJSString = @"Ponto.request(decodeURIComponent('%@'));"; 23 | NSString *const kPontoRequestUrlPath = @"/request"; 24 | NSString *const kPontoResponseUrlPath = @"/response"; 25 | 26 | NSInteger const kPontoHandlerMethodReturnTypeStringBufferLength = 128; 27 | 28 | NSString *const kPontoSuccessCallbackBlockKey = @"successBlock"; 29 | NSString *const kPontoErrorCallbackBlockKey = @"errorBlock"; 30 | 31 | typedef void (^PontoSuccessCallback)(id responseObject); 32 | typedef void (^PontoErrorCallback)(id responseObject); 33 | 34 | static dispatch_queue_t ponto_dispatcher_concurrent_queue; 35 | static dispatch_queue_t getPontoQueue() { 36 | if (ponto_dispatcher_concurrent_queue == nil) { 37 | ponto_dispatcher_concurrent_queue = dispatch_queue_create("com.wikia.ponto.queue", DISPATCH_QUEUE_CONCURRENT); 38 | } 39 | 40 | return ponto_dispatcher_concurrent_queue; 41 | } 42 | 43 | typedef enum { 44 | PontoHandlerMethodReturnTypeVoid, 45 | PontoHandlerMethodReturnTypeObject, 46 | PontoHandlerMethodReturnTypeInteger, 47 | PontoHandlerMethodReturnTypeDouble, 48 | PontoHandlerMethodReturnTypeUnknown 49 | } PontoHandlerMethodReturnType; 50 | 51 | 52 | @interface PontoDispatcher() 53 | 54 | @property (nonatomic, strong) NSMutableArray *callbacksQueue; 55 | @property (nonatomic, assign) id originalWebViewDelegate; 56 | 57 | @property (nonatomic, assign, getter=isWebKitEnabled) BOOL webKitEnabled; 58 | @property (nonatomic, assign) id originalWebKitViewNavigationDelegate; 59 | 60 | @end 61 | 62 | 63 | @implementation PontoDispatcher { 64 | 65 | } 66 | 67 | #pragma mark - Initialization 68 | 69 | // Init with handler classes prefix 70 | - (id)initWithHandlerClassesPrefix:(NSString *)classesPrefix { 71 | self = [super init]; 72 | if (self) { 73 | _handlerClassesPrefix = classesPrefix; 74 | _callbacksQueue = [NSMutableArray array]; 75 | } 76 | 77 | return self; 78 | } 79 | 80 | 81 | /** 82 | * Init with handler classes prefix and webViewObject 83 | * @param NSString* classesPrefix 84 | * @param UIWebView webView 85 | * @return instance of PontoDispatcher 86 | */ 87 | - (id)initWithHandlerClassesPrefix:(NSString *)classesPrefix andWebView:(UIWebView *)webView { 88 | self = [self initWithHandlerClassesPrefix:classesPrefix]; 89 | if (self) { 90 | self.webView = webView; 91 | } 92 | 93 | return self; 94 | } 95 | 96 | #pragma mark - JS method invoking 97 | 98 | /** 99 | * Invoke JS method with success and error blocks 100 | */ 101 | - (void)invokeMethod:(NSString *)methodName onTarget:(NSString *)target withParams:(id)params successBlock:(PontoSuccessCallback)successBlock errorBlock:(PontoErrorCallback)errorBlock { 102 | NSMutableDictionary *callbacksDict = [NSMutableDictionary dictionary]; 103 | __block NSString *callbackId = nil; 104 | 105 | if (successBlock) { 106 | [callbacksDict setObject:successBlock forKey:kPontoSuccessCallbackBlockKey]; 107 | } 108 | 109 | if (errorBlock) { 110 | [callbacksDict setObject:errorBlock forKey:kPontoErrorCallbackBlockKey]; 111 | } 112 | 113 | dispatch_barrier_sync(getPontoQueue(), ^{ 114 | [self.callbacksQueue addObject:callbacksDict]; 115 | callbackId = [NSString stringWithFormat:@"%d", [self.callbacksQueue count] - 1]; 116 | }); 117 | 118 | if (params == nil) { 119 | params = @""; 120 | } 121 | 122 | NSDictionary *methodInvokeDict = @{ 123 | @"target" : target, 124 | @"method" : methodName, 125 | @"callbackId" : callbackId, 126 | @"params" : params 127 | }; 128 | 129 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:methodInvokeDict options:NSJSONWritingPrettyPrinted error:nil]; 130 | NSString *methodInvokeString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 131 | NSString *jsString = [[NSString stringWithFormat:kPontoMethodInvokeJSString, methodInvokeString] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 132 | 133 | if (self.isWebKitEnabled) { 134 | [self.webKitView evaluateJavaScript:jsString completionHandler:nil]; 135 | } 136 | else { 137 | [self.webView stringByEvaluatingJavaScriptFromString:jsString]; 138 | } 139 | } 140 | 141 | 142 | #pragma mark - WebView Setter 143 | 144 | // WebView setter - set PontoDispatcher as delegate of webView 145 | - (void)setWebView:(UIWebView *)webView { 146 | _webView = webView; 147 | _originalWebViewDelegate = _webView.delegate; 148 | _webView.delegate = self; 149 | } 150 | 151 | #pragma mark - UIWebViewDelegate 152 | 153 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { 154 | NSURL *url = [request URL]; 155 | 156 | NSLog(@"Should response to: %@", [url absoluteString]); 157 | 158 | if ([[url scheme] isEqualToString:kPontoUrlScheme]) { 159 | NSLog(@"host: %@", [url path]); 160 | 161 | if ([[url path] isEqualToString:kPontoRequestUrlPath]) { 162 | NSDictionary *requestParams = [self extractRequestParams:url]; 163 | 164 | if (requestParams) { 165 | [self dispatchRequest:requestParams]; 166 | } 167 | 168 | return NO; 169 | } 170 | else if ([[url path] isEqualToString:kPontoResponseUrlPath]) { 171 | NSDictionary *responseParams = [self extractResponseParams:url]; 172 | 173 | if (responseParams) { 174 | [self dispatchResponse:responseParams]; 175 | } 176 | 177 | return NO; 178 | } 179 | } 180 | 181 | if (self.originalWebViewDelegate != nil && [self.originalWebViewDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) { 182 | return [self.originalWebViewDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; 183 | } 184 | 185 | return YES; 186 | } 187 | 188 | - (void)webViewDidStartLoad:(UIWebView *)webView { 189 | if ([self.originalWebViewDelegate respondsToSelector:@selector(webViewDidStartLoad:)]) { 190 | [self.originalWebViewDelegate webViewDidStartLoad:webView]; 191 | } 192 | } 193 | 194 | - (void)webViewDidFinishLoad:(UIWebView *)webView { 195 | if ([self.originalWebViewDelegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { 196 | [self.originalWebViewDelegate webViewDidFinishLoad:webView]; 197 | } 198 | } 199 | 200 | - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { 201 | if ([self.originalWebViewDelegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { 202 | [self.originalWebViewDelegate webView:webView didFailLoadWithError:error]; 203 | } 204 | } 205 | 206 | #pragma mark - Private methods 207 | 208 | - (NSDictionary *)extractParamsFromUrl:(NSURL *)url { 209 | NSMutableDictionary *params = [[url uq_queryDictionary] mutableCopy]; 210 | return params; 211 | } 212 | 213 | - (NSDictionary *)extractRequestParams:(NSURL *)requestUrl { 214 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithDictionary:[self extractParamsFromUrl:requestUrl]]; 215 | 216 | return [params dictionaryWithValuesForKeys:@[ 217 | kPontoTargetParamName, 218 | kPontoMethodParamName, 219 | kPontoParamsParamName, 220 | kPontoCallbackIdParamName 221 | ]]; 222 | } 223 | 224 | - (NSDictionary *)extractResponseParams:(NSURL *)responseUrl { 225 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithDictionary:[self extractParamsFromUrl:responseUrl]]; 226 | 227 | return [params dictionaryWithValuesForKeys:@[ 228 | kPontoCallbackIdParamName, 229 | kPontoParamsParamName 230 | ]]; 231 | } 232 | 233 | // Add prefix to class name and return as Class 234 | - (Class)classNameFromString:(NSString *)className { 235 | NSString *prefixedClassName = [NSString stringWithFormat:@"%@%@", self.handlerClassesPrefix, className]; 236 | return NSClassFromString(prefixedClassName); 237 | } 238 | 239 | - (SEL)methodSelectorFromString:(NSString *)methodName withParams:(BOOL)withParams { 240 | if (withParams) { 241 | return NSSelectorFromString([NSString stringWithFormat:@"%@:", methodName]); 242 | } 243 | 244 | return NSSelectorFromString([NSString stringWithFormat:@"%@", methodName]); 245 | } 246 | 247 | - (id)paramsObjectFromString:(NSString *)paramsString { 248 | id paramsObject = [self deserializeObjectFromJSONString:paramsString]; 249 | return paramsObject; 250 | } 251 | 252 | - (id)deserializeObjectFromJSONString:(NSString *)jsonString { 253 | if (self.delegate && [self.delegate respondsToSelector:@selector(deserializeJSONString:)]) { 254 | return [self.delegate deserializeJSONString:jsonString]; 255 | } 256 | 257 | return [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableLeaves error:nil]; 258 | } 259 | 260 | - (NSString *)serializeObjectToJSONString:(id)objectToSerialize { 261 | if (objectToSerialize == nil) { 262 | return @""; 263 | } 264 | 265 | if (self.delegate && [self.delegate respondsToSelector:@selector(serializeObjectToJSON:)]) { 266 | return [self.delegate serializeObjectToJSON:objectToSerialize]; 267 | } 268 | 269 | NSData *json = [NSJSONSerialization dataWithJSONObject:objectToSerialize options:NSJSONWritingPrettyPrinted error:nil]; 270 | return [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding]; 271 | } 272 | 273 | - (void)dispatchRequest:(NSDictionary *)requestParams { 274 | if (requestParams && requestParams[kPontoTargetParamName] && requestParams[kPontoMethodParamName]) { 275 | Class targetClassName = [self classNameFromString:requestParams[kPontoTargetParamName]]; 276 | NSString *callbackId = requestParams[kPontoCallbackIdParamName]; 277 | 278 | NSString *paramsString = requestParams[kPontoParamsParamName]; 279 | id paramsObject = nil; 280 | 281 | if (paramsString && ![paramsString isEqual:[NSNull null]]) { 282 | paramsObject = [self paramsObjectFromString:[paramsString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 283 | } 284 | 285 | SEL methodSelector = [self methodSelectorFromString:requestParams[kPontoMethodParamName] withParams:(paramsObject != nil)]; 286 | 287 | if (targetClassName && [targetClassName isSubclassOfClass:[PontoBaseHandler class]]) { 288 | id handlerObject; 289 | handlerObject = [targetClassName instance]; 290 | 291 | if (handlerObject && [handlerObject respondsToSelector:methodSelector]) { 292 | id response = [self callMethod:methodSelector inHandlerObject:handlerObject withParams:paramsObject]; 293 | [self runJSCallback:callbackId withParams:response andType:RESPONSE_COMPLETE]; 294 | } 295 | else { 296 | NSDictionary *infoDict = @{@"message" : @"Handler object dont have method.", @"methodName" : requestParams[kPontoMethodParamName]}; 297 | [self runJSCallback:callbackId withParams:infoDict andType:RESPONSE_ERROR]; 298 | } 299 | } 300 | else { 301 | NSLog(@"Class %@ is not valid Ponto Request Handler", requestParams[kPontoTargetParamName]); 302 | 303 | NSDictionary *infoDict = @{@"message" : @"Class is not valid request handler.", @"className" : requestParams[kPontoTargetParamName]}; 304 | [self runJSCallback:callbackId withParams:infoDict andType:RESPONSE_ERROR]; 305 | } 306 | } 307 | } 308 | 309 | - (void)dispatchResponse:(NSDictionary *)responseParams { 310 | if (responseParams && responseParams[kPontoCallbackIdParamName]) { 311 | NSString *responseType = responseParams[@"type"]; 312 | NSString *callbackIdString = responseParams[kPontoCallbackIdParamName]; 313 | NSUInteger callbackId = (NSUInteger)[callbackIdString integerValue]; 314 | NSString *jsonString = [NSString stringWithFormat:@"[%@]", responseParams[@"params"]]; 315 | NSArray *responseArray = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableLeaves error:nil]; 316 | id responseObject = responseArray[0]; 317 | 318 | if ([self.callbacksQueue count] > callbackId) { 319 | NSDictionary *responseCallbackDict = (self.callbacksQueue)[callbackId]; 320 | 321 | if (responseCallbackDict != nil) { 322 | PontoSuccessCallback successCallback = responseCallbackDict[kPontoSuccessCallbackBlockKey]; 323 | PontoErrorCallback errorCallback = responseCallbackDict[kPontoErrorCallbackBlockKey]; 324 | 325 | if (successCallback && (responseType == nil || [responseType isEqualToString:@"0"])) { 326 | dispatch_async(dispatch_get_main_queue(), ^{ 327 | successCallback(responseObject); 328 | }); 329 | } 330 | 331 | if (errorCallback && (responseType != nil && [responseType isEqualToString:@"1"])) { 332 | dispatch_async(dispatch_get_main_queue(), ^{ 333 | errorCallback(responseObject); 334 | }); 335 | } 336 | } 337 | 338 | [self.callbacksQueue removeObjectAtIndex:callbackId]; 339 | } 340 | } 341 | } 342 | 343 | - (void)runJSCallback:(NSString *)callbackId withParams:(id)params andType:(int)type { 344 | if (callbackId && ![callbackId isEqual:[NSNull null]]) { 345 | NSDictionary *callbackDict = @{ 346 | @"callbackId" : callbackId, 347 | @"type" : @(type), 348 | @"params" : params 349 | }; 350 | 351 | NSString *jSCallbackString = [NSString stringWithFormat:kPontoCallbackJSString, [self serializeObjectToJSONString:callbackDict]]; 352 | 353 | NSLog(@"try to call callback: %@", jSCallbackString); 354 | 355 | if (self.isWebKitEnabled) { 356 | [self.webKitView evaluateJavaScript:[jSCallbackString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] completionHandler:nil]; 357 | } 358 | else { 359 | [self.webView stringByEvaluatingJavaScriptFromString:[jSCallbackString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 360 | } 361 | } 362 | } 363 | 364 | - (PontoHandlerMethodReturnType)convertEncodedTypeString:(char*) stringType { 365 | if (strcmp(stringType, @encode(void)) == 0) { 366 | return PontoHandlerMethodReturnTypeVoid; 367 | } 368 | 369 | if (strcmp(stringType, @encode(id)) == 0) { 370 | return PontoHandlerMethodReturnTypeObject; 371 | } 372 | 373 | return PontoHandlerMethodReturnTypeUnknown; 374 | } 375 | 376 | - (id)callMethod:(SEL)methodSelector inHandlerObject:(id)handlerObject withParams:(id)params { 377 | id response = nil; 378 | 379 | char methodReturnTypeDescriptionBuffer[kPontoHandlerMethodReturnTypeStringBufferLength]; 380 | Method instanceMethod = class_getInstanceMethod([handlerObject class], methodSelector); 381 | method_getReturnType(instanceMethod, methodReturnTypeDescriptionBuffer, kPontoHandlerMethodReturnTypeStringBufferLength); 382 | 383 | PontoHandlerMethodReturnType methodReturnType = [self convertEncodedTypeString:methodReturnTypeDescriptionBuffer]; 384 | 385 | switch (methodReturnType) { 386 | case PontoHandlerMethodReturnTypeObject: 387 | if (params) { 388 | response = [handlerObject performSelector:methodSelector withObject:params]; 389 | } 390 | else { 391 | response = [handlerObject performSelector:methodSelector]; 392 | } 393 | break; 394 | 395 | case PontoHandlerMethodReturnTypeUnknown: 396 | case PontoHandlerMethodReturnTypeVoid: 397 | default: 398 | if (params) { 399 | [handlerObject performSelector:methodSelector withObject:params]; 400 | } 401 | else { 402 | [handlerObject performSelector:methodSelector]; 403 | } 404 | break; 405 | } 406 | 407 | return response; 408 | } 409 | 410 | #pragma mark - iOS8 WKWebKitView stuff 411 | 412 | - (void)setWebKitView:(id)webKitView { 413 | _webKitView = webKitView; 414 | self.originalWebKitViewNavigationDelegate = _webKitView.navigationDelegate; 415 | _webKitView.navigationDelegate = self; 416 | self.webKitEnabled = YES; 417 | } 418 | 419 | #pragma mark - 420 | 421 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { 422 | NSURL *url = navigationAction.request.URL; 423 | if ([[url scheme] isEqualToString:kPontoUrlScheme]) { 424 | if ([[url path] isEqualToString:kPontoRequestUrlPath]) { 425 | decisionHandler(WKNavigationActionPolicyCancel); 426 | 427 | NSDictionary *requestParams = [self extractRequestParams:url]; 428 | 429 | if (requestParams) { 430 | [self dispatchRequest:requestParams]; 431 | } 432 | } 433 | else if ([[url path] isEqualToString:kPontoResponseUrlPath]) { 434 | decisionHandler(WKNavigationActionPolicyCancel); 435 | 436 | NSDictionary *responseParams = [self extractResponseParams:url]; 437 | 438 | if (responseParams) { 439 | [self dispatchResponse:responseParams]; 440 | } 441 | } 442 | } 443 | 444 | decisionHandler(WKNavigationActionPolicyAllow); 445 | } 446 | 447 | - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation { 448 | [self.originalWebKitViewNavigationDelegate webView:webView didCommitNavigation:navigation]; 449 | } 450 | 451 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { 452 | [self.originalWebKitViewNavigationDelegate webView:webView didFinishNavigation:navigation]; 453 | } 454 | 455 | - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { 456 | [self.originalWebKitViewNavigationDelegate webView:webView didFailNavigation:navigation withError:error]; 457 | } 458 | 459 | @end 460 | -------------------------------------------------------------------------------- /ios/Ponto/PontoTests/PontoTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CFBundleIdentifier 7 | com.wikia.ponto.${PRODUCT_NAME:rfc1034identifier} 8 | 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleShortVersionString 14 | 1.0 15 | CFBundleVersion 16 | 1 17 | CFBundleDevelopmentRegion 18 | en 19 | CFBundlePackageType 20 | BNDL 21 | CFBundleSignature 22 | ???? 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/Ponto/PontoTests/PontoTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // PontoTests.h 3 | // PontoTests 4 | // 5 | // Created by Grzegorz Nowicki on 09/21/12. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PontoTests : XCTestCase 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Ponto/PontoTests/PontoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PontoTests.m 3 | // PontoTests 4 | // 5 | // Created by Grzegorz Nowicki on 09/21/12. 6 | // Copyright (c) 2012 Wikia Sp. z o.o. All rights reserved. 7 | // 8 | 9 | #import "PontoTests.h" 10 | 11 | @implementation PontoTests 12 | 13 | - (void)setUp 14 | { 15 | [super setUp]; 16 | 17 | // Set-up code here. 18 | } 19 | 20 | - (void)tearDown 21 | { 22 | // Tear-down code here. 23 | 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample 28 | { 29 | STFail(@"Unit tests are not implemented yet in PontoTests"); 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /ios/Ponto/PontoTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | -------------------------------------------------------------------------------- /ios/Ponto/Resources/html/pontoDemo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pontoDemo 5 | 6 | 7 | 8 | 9 | 80 | 81 | 82 |

Ponto DEMO HTML

83 |
    84 |
  • 85 | 86 |
  • 87 | 88 |
  • 89 | 90 |
  • 91 | 92 |
  • 93 | 94 |
  • 95 | 96 |
  • 97 | 98 |
  • 99 | 100 |
  • 101 | 102 |
  • 103 |
104 | 105 | -------------------------------------------------------------------------------- /ios/README.md: -------------------------------------------------------------------------------- 1 | This is the root folder for the iOS implementation. 2 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | Ponto, WebView implementation [![Code Climate](https://codeclimate.com/github/Wikia/ponto.png)](https://codeclimate.com/github/Wikia/ponto) 2 | ============================= 3 | 4 | This is the root folder for the WebView (JavaScript) implementation. 5 | 6 | 7 | Examples 8 | -------- 9 | 10 | **To invoke a method in the native layer** from the WebView use: 11 | 12 | ```javascript 13 | Ponto.invoke('ClassName', 'methodName', {param1:1, param2:2}, function(d){/*completed*/}, function(e){/*error*/}); 14 | ``` 15 | 16 | The last three parameters (parameters, success/failure callbacks) are optional. 17 | 18 | **To invoke a method in the WebView from the native layer** use: 19 | 20 | ```java 21 | mWebView.loadUrl("javascript:Ponto.request(\"{\\\"target\\\": \\\"TypeOrModuleName\\\", \\\"method\\\": \\\"methodName\\\", \\\"params\\\":{\\\"a\\\":1}, \\\"callbackId\\\": \\\"callbackId\\\"}\")"); 22 | ``` 23 | 24 | ```objective-c 25 | [mWebView stringByEvaluatingJavaScriptFromString:@"Ponto.request(\"{\\\"target\\\": \\\"TypeOrModuleName\\\", \\\"method\\\": \\\"methodName\\\", \\\"params\\\":{\\\"a\\\":1}, \\\"callbackId\\\": \\\"callbackId\\\"}\";"]; 26 | ``` 27 | 28 | Ponto.request accept a JSON-encoded string of an hash/dictionary containing the required data; *params* and *callbackId* are optional. 29 | 30 | **To invoke a callback in the WebView from the native layer** after the method invoked by the WebView has completed or resulted in an error use: 31 | 32 | ```java 33 | mWebView.loadUrl("javascript:Ponto.response(\"{\\\"type\\\": 0, \\\"params\\\":{\\\"a\\\":1}, \\\"callbackId\\\": \\\"callbackId\\\"}\");"); 34 | ``` 35 | 36 | ```objective-c 37 | [mWebView stringByEvaluatingJavaScriptFromString:@"Ponto.response(\"{\\\"type\\\": 0, \\\"params\\\":{\\\"a\\\":1}, \\\"callbackId\\\": \\\"callbackId\\\"}\");"]; 38 | ``` 39 | 40 | Ponto.response accept a JSON-encoded string of an hash/dictionary containing the required data; *params* is optional and *type* is either 0 (completed successfully) or 1 (error occurred). 41 | 42 | Iframe communication 43 | -------- 44 | Ponto can be used as a message transport protocol between a parent HTML window and an iframe - to enable this mode it is required to explicitly override the default, native protocol, uing setTarget method. 45 | 46 | For parent window, this method gets two params, target indicator and targeted iframe's content window. 47 | ```javascript 48 | Ponto.setTarget(Ponto.TARGET_IFRAME, 'http://iframeOrigin.com', document.querySelector('iframe').contentWindow); 49 | ``` 50 | In the iframe, it's just enough to set the target indicator as the iframe's parent. 51 | ```javascript 52 | Ponto.setTarget(Ponto.TARGET_IFRAME_PARENT, 'http://parentWindowOrigin.com'); 53 | ``` 54 | 55 | Ponto by default performs a synchronous operation and immediately responds with a result. If there is a need to perform an asynchronous operation on the second javascript side, it needs to be indicated by using 'async' flag in the invoke method: 56 | For parent window, this method gets two params, target indicator and targeted iframe's content window. 57 | ```javascript 58 | Ponto.invoke(scope, method, params, successCallback, errorCallback, async); 59 | ``` 60 | 61 | After such invocation, the targeted method is triggered with two arguments: params object and callbackId, and is able to respond to the invocation by using the respond method: 62 | For parent window, this method gets two params, target indicator and targeted iframe's content window. 63 | ```javascript 64 | function targetedFunction(params, callbackId) { 65 | Ponto.respond(params, callbackId); 66 | } 67 | ``` 68 | 69 | To prepare a valid Ponto scope, you need to perform following steps: 70 | 71 | 1. Create a constructor: 72 | ```javascript 73 | function MyObject() { 74 | this.myMethod = function() { 75 | console.log('myMethod'); 76 | }; 77 | } 78 | ``` 79 | 80 | 2. Use Ponto's derive method on it: 81 | ```javascript 82 | Ponto.PontoBaseHandler.derive(myObject); 83 | ``` 84 | 85 | 3. Override the getInstance method prepared by Ponto: 86 | ```javascript 87 | myObject.getInstance = function(){ 88 | return new myObject(); 89 | }; 90 | ``` 91 | 92 | -------------------------------------------------------------------------------- /web/src/ponto.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ponto JavaScript implementation for WebViews 3 | * 4 | * @see http://github.com/wikia-apps/Ponto 5 | * 6 | * @author Federico "Lox" Lucignano 7 | * 8 | */ 9 | 10 | /*global define, require, PontoUriProtocol*/ 11 | (function (context) { 12 | 'use strict'; 13 | 14 | /** 15 | * AMD support 16 | * 17 | * @private 18 | * 19 | * @type {Boolean} 20 | */ 21 | var amd = false; 22 | 23 | /** 24 | * module constructor 25 | * 26 | * @private 27 | * 28 | * @return {Object} The module reference 29 | */ 30 | function ponto() { 31 | var 32 | /** 33 | * [Constant] Name of the URI protocol optionally 34 | * registered by the native layer 35 | * 36 | * @private 37 | * 38 | * @type {String} 39 | * 40 | * @see protocol.request 41 | * @see protocol.response 42 | */ 43 | PROTOCOL_NAME = 'ponto', 44 | 45 | /** 46 | * [Constant] Represents a completed request 47 | * 48 | * @private 49 | * 50 | * @type {Number} 51 | * 52 | * @see Ponto.acceptResponse 53 | */ 54 | RESPONSE_COMPLETE = 0, 55 | 56 | /** 57 | * [Constant] Represents a failed request with errors 58 | * 59 | * @private 60 | * 61 | * @type {Number} 62 | * 63 | * @see Ponto.acceptResponse 64 | */ 65 | RESPONSE_ERROR = 1, 66 | 67 | /** 68 | * [Constant] Indicates that the target is a native platform 69 | * 70 | * @type {Number} 71 | */ 72 | TARGET_NATIVE = 0, 73 | 74 | /** 75 | * [Constant] Indicates that the target is an iframe 76 | * 77 | * @type {Number} 78 | */ 79 | TARGET_IFRAME = 1, 80 | 81 | /** 82 | * [Constant] Indicates that the target is an iframe parent window 83 | * 84 | * @type {Number} 85 | */ 86 | TARGET_IFRAME_PARENT = 2, 87 | 88 | /** 89 | * Window to communicate with (if iframe transport is overriden) 90 | * 91 | * @type {Window} 92 | */ 93 | targetWindow, 94 | 95 | /** 96 | * Origin url of the targeted window 97 | * 98 | * @type {String} 99 | */ 100 | targetOrigin, 101 | 102 | /** 103 | * Request / Response dispatcher 104 | * 105 | * @type {PontoDispatcher} 106 | */ 107 | dispatcher = new PontoDispatcher(context), 108 | 109 | /** 110 | * Registry for complete/error callbacks 111 | * 112 | * @private 113 | * 114 | * @type {Object} 115 | */ 116 | callbacks = {}, 117 | 118 | /** 119 | * Protocol helper 120 | * registered by native platforms that 121 | * have the capability to do so (e.g. Android) or 122 | * implemented directly for those that don't (e.g iOS) 123 | * 124 | * @type {Object} 125 | */ 126 | protocol = nativeProtocol(), 127 | 128 | targets = {}, 129 | 130 | exports; 131 | 132 | /** 133 | * Returns a valid communication protocol for native platform 134 | * @returns {*|{request: request, response: response}} 135 | * Communication protocol methods for the native layer in an object 136 | */ 137 | function nativeProtocol () { 138 | return context.PontoProtocol || { 139 | //the only other chance is for the native layer to register 140 | //a custom protocol for communicating with the webview (e.g. iOS) 141 | request: function (execContext, target, method, params, callbackId) { 142 | if (execContext.location && execContext.location.href) { 143 | execContext.location.href = PROTOCOL_NAME + ':///request?target=' + encodeURIComponent(target) + 144 | '&method=' + encodeURIComponent(method) + 145 | ((params) ? '¶ms=' + encodeURIComponent(params) : '') + 146 | ((callbackId) ? '&callbackId=' + encodeURIComponent(callbackId) : ''); 147 | } else { 148 | throw new LocationException(); 149 | } 150 | }, 151 | response: function (execContext, callbackId, params) { 152 | if (execContext.location && execContext.location.href) { 153 | execContext.location.href = PROTOCOL_NAME + ':///response?callbackId=' + encodeURIComponent(callbackId) + 154 | ((params) ? '¶ms=' + encodeURIComponent(JSON.stringify(params)) : ''); 155 | } else { 156 | throw new LocationException(); 157 | } 158 | } 159 | }; 160 | } 161 | 162 | /** 163 | * Returns a valid communication protocol for the iframe 164 | * @returns {{request: request, response: response}} 165 | * Communication protocol methods for the iframe 166 | */ 167 | function iframeProtocol () { 168 | return { 169 | request: function (execContext, target, method, params, callbackId, async) { 170 | if (targetWindow.postMessage) { 171 | targetWindow.postMessage({ 172 | protocol: PROTOCOL_NAME, 173 | action: 'request', 174 | target: target, 175 | method: method, 176 | params: params, 177 | async: async, 178 | callbackId: callbackId 179 | }, targetOrigin); 180 | } else { 181 | throw new PostMessageException(); 182 | } 183 | }, 184 | response: function (execContext, callbackId, result) { 185 | if (targetWindow.postMessage) { 186 | targetWindow.postMessage({ 187 | protocol: PROTOCOL_NAME, 188 | action: 'response', 189 | type: (result && result.type) ? result.type : RESPONSE_COMPLETE, 190 | params: result, 191 | callbackId: callbackId 192 | }, targetOrigin); 193 | } else { 194 | throw new PostMessageException(); 195 | } 196 | } 197 | }; 198 | } 199 | 200 | /** 201 | * Throws a post message error 202 | */ 203 | function PostMessageException() { 204 | this.message = 'Target context does not support postMessage'; 205 | } 206 | 207 | /** 208 | * Throws a user agent location error 209 | */ 210 | function LocationException() { 211 | this.message = 'Context doesn\'t support User Agent location API'; 212 | } 213 | 214 | /** 215 | * 'message' Event handler 216 | * @param {Event} event 217 | */ 218 | function onMessage(event) { 219 | var data = event.data; 220 | if (data && data.protocol === PROTOCOL_NAME) { 221 | dispatcher[data.action](data); 222 | } 223 | } 224 | 225 | 226 | /** 227 | * Request handler base class constructor 228 | * 229 | * @public 230 | */ 231 | function PontoBaseHandler() {} 232 | 233 | /** 234 | * Request handler factory method, each subclass of 235 | * PontoBaseHandler needs to implement this static method 236 | */ 237 | PontoBaseHandler.getInstance = function () { 238 | throw new Error('The getInstance method needs to be overridden in PontoBaseHandler subclasses'); 239 | }; 240 | 241 | /** 242 | * PontoBaseHandler extension pattern 243 | * 244 | * @param {PontoBaseHandler} constructor The reference to a constructor 245 | * of a type to be converted in a PontoBaseHandler subtype 246 | * 247 | * @example 248 | * function MyHandler(){...} 249 | * Ponto.PontoBaseHandler.derive(MyHandler); 250 | * MyHandler.getInstance = function() {...}; 251 | */ 252 | PontoBaseHandler.derive = function (constructor) { 253 | constructor.handlerSuper = PontoBaseHandler; 254 | constructor.getInstance = PontoBaseHandler.getInstance; 255 | constructor.prototype = new PontoBaseHandler(); 256 | }; 257 | 258 | /** 259 | * Dispatches a request sent by the native layer 260 | * 261 | * @private 262 | * 263 | * @param {Object} scope The execution scope 264 | * @param {Mixed} target A reference to a constructor or a static instance 265 | * @param {RequestParams} data An hash containing the parameters associated to the request 266 | */ 267 | function dispatchRequest(scope, target, data) { 268 | var instance, 269 | result; 270 | 271 | if (target && target.handlerSuper === PontoBaseHandler && target.getInstance) { 272 | instance = target.getInstance(); 273 | 274 | //unfortunately we need to instantiate before being able to 275 | if (instance[data.method]) { 276 | if (data.async) { 277 | instance[data.method](data.params, data.callbackId); 278 | } else { 279 | result = instance[data.method](data.params); 280 | 281 | if (data.callbackId && protocol && protocol.response) { 282 | protocol.response(scope, data.callbackId, result); 283 | } 284 | } 285 | } 286 | } 287 | } 288 | 289 | /** 290 | * Function called by the native layer when answering a request 291 | * 292 | * @public 293 | * 294 | * @param {Object} scope The execution scope 295 | * @param {ResponseParams} data An hash containing the parameters associated to the response 296 | */ 297 | function dispatchResponse(data) { 298 | var 299 | callbackId = data.callbackId, 300 | cbGroup = callbacks[callbackId], 301 | callback, 302 | responseType; 303 | 304 | if (cbGroup) { 305 | responseType = data.type; 306 | 307 | switch (responseType) { 308 | case RESPONSE_COMPLETE: 309 | callback = cbGroup.complete; 310 | break; 311 | case RESPONSE_ERROR: 312 | default: 313 | callback = cbGroup.error; 314 | break; 315 | } 316 | 317 | if (callback) { 318 | callback(data.params); 319 | } 320 | 321 | delete callbacks[callbackId]; 322 | } 323 | } 324 | 325 | /** 326 | * Initialized iframe protocol to work 327 | */ 328 | function setIframeProtocol () { 329 | protocol = iframeProtocol(); 330 | context.addEventListener('message', onMessage, false); 331 | 332 | /** 333 | * Enables manual trigger of the response when async operation needed 334 | * this can be explicitly called after the async operation is done 335 | * @param {Object} result response params, optionally with 'type' field 336 | * @param {Number} callbackId 337 | */ 338 | PontoDispatcher.prototype.respond = function (result, callbackId) { 339 | protocol.response(this.context, callbackId, result); 340 | }; 341 | } 342 | 343 | /** 344 | * Sets iframe's content window as the protocol's target 345 | * @param {String} _targetOrigin - origin URL of the iframe's document 346 | * @param {Window} _targetWindow 347 | */ 348 | targets[TARGET_IFRAME] = function (_targetOrigin, _targetWindow) { 349 | if (_targetWindow.top && _targetWindow !== _targetWindow.top) { 350 | targetWindow = _targetWindow; 351 | targetOrigin = _targetOrigin; 352 | } else { 353 | throw new Error('Bad iframe content window provided.'); 354 | } 355 | setIframeProtocol(); 356 | }; 357 | 358 | /** 359 | * @param {String} _targetOrigin - origin URL of the parent's document 360 | * Sets iframe's parent window as the protocol's target 361 | */ 362 | targets[TARGET_IFRAME_PARENT] = function (_targetOrigin) { 363 | if (context.top && context.top !== context) { 364 | targetWindow = context.top; 365 | targetOrigin = _targetOrigin; 366 | } else { 367 | throw new Error('No possible communication in this context.'); 368 | } 369 | setIframeProtocol(); 370 | }; 371 | 372 | /** 373 | * Sets the native layer as the protocol's target 374 | */ 375 | targets[TARGET_NATIVE] = function () { 376 | protocol = nativeProtocol(); 377 | if (PontoDispatcher.prototype.respond) { 378 | delete PontoDispatcher.prototype.respond; 379 | context.removeEventListener('message', onMessage); 380 | } 381 | }; 382 | 383 | /** 384 | * Overrides the protocol target (default: native) 385 | * @param {Number} _target 386 | * @param {String | undefined} _targetOrigin - origin url of the targeted window 387 | * @param {Number | undefined}_targetWindow 388 | * provide if target is an iframe 389 | */ 390 | function setTarget(_target, _targetOrigin, _targetWindow) { 391 | if (targets[_target]) { 392 | targets[_target](_targetOrigin, _targetWindow); 393 | } 394 | } 395 | 396 | /** 397 | * Deserializes JSON string if needed 398 | * @param {Object | String} data 399 | * @returns {Object} 400 | */ 401 | function parse(data) { 402 | return typeof data === 'string' ? JSON.parse(data) : data; 403 | } 404 | 405 | /** 406 | * RequestParams constructor 407 | * 408 | * Extracts and normalizes the parameters out of a JSON-encoded string 409 | * passed in by a request from the native context 410 | * 411 | * @private 412 | * 413 | * @param {String} data JSON-encoded string describing parameters 414 | * for a request to Ponto 415 | */ 416 | function RequestParams(data) { 417 | var hash = parse(data); 418 | 419 | this.target = hash.target; 420 | this.method = hash.method; 421 | this.params = parse(hash.params); 422 | this.callbackId = hash.callbackId; 423 | this.async = hash.async; 424 | } 425 | 426 | /** 427 | * ResponseParams constructor 428 | * 429 | * Extracts and normalizes the parameters out of a JSON-encoded string 430 | * passed in by a response from the native context 431 | * 432 | * @private 433 | * 434 | * @param {String} data JSON-encoded string describing parameters 435 | * for a response from Ponto 436 | */ 437 | function ResponseParams(data) { 438 | var hash = parse(data); 439 | 440 | this.type = parseInt(hash.type, 10); 441 | this.callbackId = hash.callbackId; 442 | this.params = hash.params; 443 | } 444 | 445 | /** 446 | * Ponto dispatcher constructor 447 | * 448 | * @public 449 | * 450 | * @param {Object} dispatchContext The execution scope to bind to this instance 451 | */ 452 | function PontoDispatcher(dispatchContext) { 453 | this.context = dispatchContext; 454 | } 455 | 456 | /** 457 | * Sets the context/scope to bind to the PontoDispatcher instance 458 | * 459 | * @public 460 | * 461 | * @param {Object} scope The context/scope to bind to the instance 462 | */ 463 | PontoDispatcher.prototype.setContext = function (scope) { 464 | this.context = scope; 465 | }; 466 | 467 | /** 468 | * Handle a request from the native layer, 469 | * this is supposed to be called directly from native code 470 | * 471 | * @public 472 | * 473 | * @param {String} data A JSON-encoded string containing the parameters 474 | * associated to the request 475 | */ 476 | PontoDispatcher.prototype.request = function (data) { 477 | var params = new RequestParams(data), 478 | scope = this.context, 479 | target = scope[params.target]; 480 | 481 | if (target) { 482 | dispatchRequest(scope, target, params); 483 | } else if (amd && params.target) { 484 | require([params.target], function (target) { 485 | dispatchRequest(scope, target, params); 486 | }); 487 | } 488 | }; 489 | 490 | /** 491 | * Handle a response from the native layer, 492 | * this is supposed to be called directly from native code 493 | * 494 | * @public 495 | * 496 | * @param {String} data A JSON-encoded string containing the parameters 497 | * associated to the response 498 | */ 499 | PontoDispatcher.prototype.response = function (data) { 500 | var params = new ResponseParams(data); 501 | dispatchResponse(params); 502 | }; 503 | 504 | /** 505 | * Makes a request to the native layer 506 | * 507 | * @public 508 | * 509 | * @param {String} target The target native class 510 | * @param {String} method The method to call 511 | * @param {Object} params [OPTIONAL] An hash contaning the parameters to pass to the method 512 | * @param {Function} completeCallback [OPTIONAL] The callback to invoke on completion 513 | * @param {Function} errorCallback [OPTIONAL] The callback to invoke in case of error 514 | * @param {Bool} async Indicates of the other side will make async operation and respond 515 | * manually 516 | */ 517 | //ToDo -> Make the method take object with params (too many params now) 518 | PontoDispatcher.prototype.invoke = function (target, method, params, completeCallback, errorCallback, async) { 519 | var callbackId; 520 | 521 | if (typeof (completeCallback || errorCallback) === 'function') { 522 | callbackId = Math.random().toString().substr(2); 523 | callbacks[callbackId] = { 524 | complete: completeCallback, 525 | error: errorCallback 526 | }; 527 | } 528 | 529 | protocol.request(this.context, target, method, JSON.stringify(params), callbackId, async); 530 | }; 531 | 532 | exports = dispatcher; 533 | exports.RESPONSE_COMPLETE = RESPONSE_COMPLETE; 534 | exports.RESPONSE_ERROR = RESPONSE_ERROR; 535 | exports.TARGET_NATIVE = TARGET_NATIVE; 536 | exports.TARGET_IFRAME = TARGET_IFRAME; 537 | exports.TARGET_IFRAME_PARENT = TARGET_IFRAME_PARENT; 538 | exports.PontoDispatcher = PontoDispatcher; 539 | exports.PontoBaseHandler = PontoBaseHandler; 540 | exports.setTarget = setTarget; 541 | 542 | return exports; 543 | } 544 | 545 | //check for AMD availability 546 | if (typeof define !== 'undefined' && define.amd) { 547 | amd = true; 548 | } 549 | 550 | //make module available in the global scope 551 | context.Ponto = ponto(); 552 | 553 | //if AMD available then register also as a module 554 | //to allow easy usage in other AMD modules 555 | if (amd) { 556 | amd = true; 557 | define('ponto', context.Ponto); 558 | } 559 | }(this)); 560 | --------------------------------------------------------------------------------