├── docs ├── public │ ├── fonts │ │ ├── aller-bold.eot │ │ ├── aller-bold.ttf │ │ ├── aller-bold.woff │ │ ├── aller-light.eot │ │ ├── aller-light.ttf │ │ ├── aller-light.woff │ │ ├── novecento-bold.eot │ │ ├── novecento-bold.ttf │ │ └── novecento-bold.woff │ └── stylesheets │ │ └── normalize.css ├── docco.css └── index.html ├── WebView ├── WebView │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── WebView_58x58.png │ │ │ ├── WebView_80x80.png │ │ │ ├── WebView_120x120.png │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── Info.plist │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── Main.storyboard │ └── ViewController.swift ├── WebView.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── paulguo.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── WorkspaceSettings.xcsettings │ ├── xcuserdata │ │ └── paulguo.xcuserdatad │ │ │ └── xcschemes │ │ │ ├── xcschememanagement.plist │ │ │ └── WebView.xcscheme │ └── project.pbxproj └── WebViewTests │ ├── Info.plist │ └── WebViewTests.swift ├── .gitignore ├── guide └── index.md ├── abc.json ├── package.json ├── demo ├── index.html └── bridge.html ├── README.md ├── Gruntfile.js └── index.js /docs/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/docs/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/docs/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/docs/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/docs/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/docs/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/docs/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/docs/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/docs/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/docs/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /WebView/WebView/Images.xcassets/AppIcon.appiconset/WebView_58x58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/WebView/WebView/Images.xcassets/AppIcon.appiconset/WebView_58x58.png -------------------------------------------------------------------------------- /WebView/WebView/Images.xcassets/AppIcon.appiconset/WebView_80x80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/WebView/WebView/Images.xcassets/AppIcon.appiconset/WebView_80x80.png -------------------------------------------------------------------------------- /WebView/WebView/Images.xcassets/AppIcon.appiconset/WebView_120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/WebView/WebView/Images.xcassets/AppIcon.appiconset/WebView_120x120.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | */node_modules 4 | npm-debug.log 5 | *.swp 6 | .sw* 7 | .idea/* 8 | .editorconfig 9 | .jshintrc 10 | .travis.yml 11 | .sass-cache/ 12 | .project 13 | mods/.sass-cache/ 14 | -------------------------------------------------------------------------------- /guide/index.md: -------------------------------------------------------------------------------- 1 | ## 综述 2 | 3 | bridge是。 4 | 5 | ## 快速使用 6 | 7 | ### 初始化组件 8 | 9 | S.use('gallery/bridge/index', function (S, Bridge) { 10 | var bridge = new Bridge(); 11 | }) 12 | 13 | ## API说明 14 | 15 | -------------------------------------------------------------------------------- /WebView/WebView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WebView/WebView.xcodeproj/project.xcworkspace/xcuserdata/paulguo.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/bridge/HEAD/WebView/WebView.xcodeproj/project.xcworkspace/xcuserdata/paulguo.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /abc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge", 3 | "version": "0.1.1", 4 | "componentName": "Bridge", 5 | "desc": "", 6 | "cover": "", 7 | "tag": "", 8 | "author": { 9 | "name": "", 10 | "email": "", 11 | "page": "" 12 | }, 13 | "type": "kissy-gallery", 14 | "type-url": "http://gallery.kissyui.com/guide" 15 | } -------------------------------------------------------------------------------- /WebView/WebView/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge", 3 | "version": "0.0.1", 4 | "componentName":"Bridge", 5 | "devDependencies": { 6 | "grunt": "~0.4.1", 7 | "grunt-contrib-copy": "", 8 | "grunt-contrib-uglify": "~0.2.0", 9 | "grunt-contrib-clean": "~0.4.0", 10 | "grunt-contrib-less": "~0.5.0", 11 | "grunt-css-combo": "~0.2.1", 12 | "grunt-contrib-cssmin": "~0.5.0", 13 | "grunt-contrib-watch": "~0.3.1", 14 | "grunt-kmc": "", 15 | "grunt-exec": "~0.4.1", 16 | "fs-extra":"", 17 | "grunt-replace":"" 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /WebView/WebView/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "WebView_58x58.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "40x40", 11 | "idiom" : "iphone", 12 | "filename" : "WebView_80x80.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "60x60", 17 | "idiom" : "iphone", 18 | "filename" : "WebView_120x120.png", 19 | "scale" : "2x" 20 | } 21 | ], 22 | "info" : { 23 | "version" : 1, 24 | "author" : "xcode" 25 | } 26 | } -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bridge的demo 6 | 7 | 8 | 9 | 10 |

bridge

11 | 12 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /WebView/WebView.xcodeproj/xcuserdata/paulguo.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | WebView.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 11DF46DC194CE33F0072FDE3 16 | 17 | primary 18 | 19 | 20 | 11DF46EE194CE3400072FDE3 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /WebView/WebView.xcodeproj/project.xcworkspace/xcuserdata/paulguo.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | IssueFilterStyle 12 | ShowActiveSchemeOnly 13 | LiveSourceIssuesEnabled 14 | 15 | SnapshotAutomaticallyBeforeSignificantChanges 16 | 17 | SnapshotLocationStyle 18 | Default 19 | 20 | 21 | -------------------------------------------------------------------------------- /WebView/WebViewTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | PaulGuo.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /WebView/WebViewTests/WebViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewTests.swift 3 | // WebViewTests 4 | // 5 | // Created by 破锣锅 on 14-6-15. 6 | // Copyright (c) 2014年 破锣锅. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class WebViewTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | XCTAssert(true, "Pass") 26 | } 27 | 28 | func testPerformanceExample() { 29 | // This is an example of a performance test case. 30 | self.measureBlock() { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /WebView/WebView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | PaulGuo.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /WebView/WebView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // WebView 4 | // 5 | // Created by 破锣锅 on 14-6-15. 6 | // Copyright (c) 2014年 破锣锅. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // 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. 24 | // 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. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // 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. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /WebView/WebView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /WebView/WebView/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // WebView 4 | // 5 | // Created by 破锣锅 on 14-6-15. 6 | // Copyright (c) 2014年 破锣锅. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | var webview: UIWebView! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | // Do any additional setup after loading the view, typically from a nib. 18 | 19 | self.modifyUserAgent() 20 | self.webviewInit() 21 | self.webviewLoad() 22 | self.messageTimerInit() 23 | } 24 | 25 | override func didReceiveMemoryWarning() { 26 | super.didReceiveMemoryWarning() 27 | // Dispose of any resources that can be recreated. 28 | } 29 | 30 | func webviewInit() { 31 | self.webview = UIWebView(frame: UIScreen.mainScreen().applicationFrame) 32 | self.view.addSubview(self.webview) 33 | } 34 | 35 | func webviewLoad() { 36 | //var url = NSURL(string: "http://h5.m.taobao.com/trip/hotel/search/index.html") 37 | var url = NSURL(string: "http://b.ued.taobao.net/liuhuo.gk/bridge/demo/example.html") 38 | var request = NSURLRequest(URL: url) 39 | self.webview.reload() 40 | self.webview.loadRequest(request) 41 | } 42 | 43 | func messageTimerInit() { 44 | var messageTimer = NSTimer.scheduledTimerWithTimeInterval(0.02, target: self, selector: "messageLoop", userInfo: nil, repeats: true) 45 | } 46 | 47 | func messageLoop() { 48 | let FECTCHMESSAGE = "if(this.messageQueueFetch) messageQueueFetch();" 49 | var messages: NSString = webview.stringByEvaluatingJavaScriptFromString(FECTCHMESSAGE) 50 | 51 | if(messages.length > 0) { 52 | println("BRIDGE PROTOCOL DETECTED:") 53 | println(messages) 54 | } 55 | } 56 | 57 | func modifyUserAgent() { 58 | var clientVersion: NSString = "AliApp(LX/4.0.0) AliTrip/4.0.0" 59 | // for "avoid show blank when webview first time lanch after app lanch 60 | var origin: NSString = UIWebView().stringByEvaluatingJavaScriptFromString("navigator.userAgent") 61 | var userAgent: NSString = "\(origin) \(clientVersion)" 62 | NSUserDefaults.standardUserDefaults().registerDefaults(["UserAgent": userAgent]) 63 | NSUserDefaults.standardUserDefaults().synchronize() 64 | } 65 | 66 | 67 | } 68 | 69 | -------------------------------------------------------------------------------- /WebView/WebView.xcodeproj/xcuserdata/paulguo.xcuserdatad/xcschemes/WebView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Native2H5 2 | `mClient协议约定及优先级梳理 v0.1` 3 | 4 | --- 5 | 6 | ### Overview 7 | 8 | - H5 WebView特性方法 9 | - 客户端协议接口约定(握手通讯) 10 | - 客户端页面跳转及跳转拦截 11 | - 客户端唤醒(调研总结) 12 | - 降级方案(页面生存环境自适应) 13 | 14 | ##### 面向Android和iOS的JS调用原理 15 | 16 | 1. `Android` 通过桥(客户端暴露在WebView全局对象下的一个对象,内挂各种API方法)的方式进行调用,如`window['Android_Bridge']['method'](JSON)`。 17 | 18 | 2. `iOS`通过自定义`Scheme`(如`native://method?data=JSON`)方式调用。 19 | 20 | 3. 需要回调的接口需要将函数名称在调用时一并传给客户端,同时将回调函数通过唯一名称挂在全局,待客户端执行回调后移除该全局函数。 21 | 22 | 4. 握手通讯,`mclient`初始化时发送`ready`消息,当客户端拦截到`ready`消息后返回`Ok`状态码,`mclient`拿到成功状态码后则认为初始化完成,否则将认为页面自身处在非客户端或者第三方WebView环境中进行执行降级逻辑处理。 23 | 24 | `mClient`是为H5和客户端交互通讯而产生的一个中间件,即一个`JavaScript`的SDK。负责处理H5和客户端的方法调用、通信及H5页面自身的降级处理(非内嵌在客户端的情况)。 25 | 26 | H5内嵌客户端需要借助WebView完成自身的展现和“存活”,H5自身由于权限有限,有些功能和特性需要借助客户端辅助提供一些属性和方法来实现。如设备蜂鸣、震动,网络类型的识别(2G/3G/Wifi等)相关特性,除此之外还有一些特有的事件(如点击`back`键,网络变化,电量报警等)。倘若WebView的功能能够足够完善,H5就能够更大的发挥其优势,就会有更大的用武之地。下面是参考借鉴`PhoneGap`所罗列的一些常用的属性、方法抑或事件(`排序按照优先级由高到低,非必要的特性已经暂时从下表中去除`)。 27 | 28 | ### Storage 29 | 30 | ##### Objects: 31 | 32 | - localStorage 33 | 34 | ##### localStorage 35 | 36 | 开启本地存储特性 37 | 38 | --- 39 | 40 | ### UserAgent 41 | 42 | 请参照规范格式设置`WebView`的`UserAgent`,避免页面进行设备适配时取不到该字段的值或者取到的信息不正确。 43 | 44 | --- 45 | 46 | ### Connection 47 | 48 | ##### Properties 49 | 50 | - connection.type 51 | 52 | ##### Constants 53 | 54 | - Connection.UNKNOWN 55 | - Connection.ETHERNET 56 | - Connection.WIFI 57 | - Connection.CELL_2G 58 | - Connection.CELL_3G 59 | - Connection.CELL_4G 60 | - Connection.CELL 61 | - Connection.NONE 62 | 63 | ##### connection.type 64 | 65 | 获取当前用户网络环境类型,wifi/2G/3G, etc. 66 | 67 | 备注:`mclient`提供JavaScript钩子,通过钩子获取设备当前所处的网络环境及网络类型。iOS若在非Wifi网络环境下无法区分2G/3G状态,则默认认为是3G即可。 68 | 69 | --- 70 | 71 | ### Notification 72 | 73 | ##### Methods: 74 | 75 | - alert 76 | - confirm 77 | - prompt 78 | - beep 79 | - vibrate 80 | 81 | ##### notification.alert 82 | 83 | 警告提示框 84 | 85 | ##### notification.confirm 86 | 87 | 确认提示框 88 | 89 | ##### notification.prompt 90 | 91 | 提示对话框 92 | 93 | ##### notification.beep 94 | 95 | 蜂鸣声 96 | 97 | ##### notification.vibrate 98 | 99 | 设备震动 100 | 101 | --- 102 | 103 | ### Device 104 | 105 | ##### Properties: 106 | 107 | - name 108 | - mclient 109 | - platform 110 | - uuid 111 | - model 112 | - version 113 | 114 | ##### device.name 115 | 116 | 设备名称 117 | 118 | ##### device.mclient 119 | 120 | 当前所用`mclient`的版本号 121 | 122 | ##### device.platform 123 | 124 | 平台类型 125 | 126 | ##### device.uuid 127 | 128 | 设备全球唯一标识 129 | 130 | ##### device.model 131 | 132 | 设备信息 133 | 134 | ##### device.version 135 | 136 | 操作系统版本号 137 | 138 | ##### Events 139 | 140 | `按照优先级排序` 141 | 142 | - backbutton 143 | - deviceready 144 | - online 145 | - offline 146 | - pause 147 | - resume 148 | - batterycritical 149 | - batterylow 150 | - batterystatus 151 | - menubutton 152 | - searchbutton 153 | - startcallbutton 154 | - endcallbutton 155 | - volumedownbutton 156 | - volumeupbutton 157 | - shake 158 | 159 | --- 160 | 161 | ### Client 162 | 163 | ##### Properties: 164 | 165 | - version 166 | 167 | ##### client.version 168 | 169 | 客户端版本号 170 | 171 | --- 172 | 173 | ### InAppBrowser 174 | 175 | ##### Methods: 176 | 177 | - window.open 178 | 179 | ##### window.open 180 | 181 | Events: 182 | 183 | - loadstart 184 | - loadstop 185 | - loaderror 186 | - exit 187 | 188 | --- 189 | 190 | ### 页面跳转/拦截 191 | 192 | ##### 跳转的页面类型: 193 | 194 | - 商品详情页 195 | - 系统登录框 196 | - 跳转到AppStore 197 | - 任意页面(如客户端首页) 198 | 199 | ##### 跳转方式: 200 | 201 | - 协议约定方式 202 | - 客户端拦截(借鉴主站客户端方式,规则是否能做成可配置的更加灵活) 203 | 204 | ### 协议约定 205 | 206 | 下述为原有的协议约定(是否需要调整或者变更还待确定),如有改动或者新增请及时同步变更信息。 207 | 208 | ###### a. 单VIEW操作 209 | 210 | | 方法名 |说明 | 需要的参数名 | 211 | | -----------------------|:------------------|:-------------------| 212 | | open_system_browser |用浏览器打开url |url | 213 | | client_appstore_call |跳转到appstore |url | 214 | | set_browser_title |设置顶部title |title | 215 | | client_page_back |客户端页面回退(back到webview上一页) |- | 216 | | client_alert |警告提示框,要求客户端自定义风格 |title, msg, ok_wording, callback | 217 | | client_confirm |确认提示框,俩按钮的提示框 |title, msg, ok_wording, cancle_wording, ok_callback, cancel_callback | 218 | | get_client_info |获取客户端类型/版本信息 |callback | 219 | | get_client_location |获取客户端定位信息 |callback, failback | 220 | | show_loading |显示菊花 | | 221 | | close_loading |关闭菊花 | | 222 | 223 | ###### b. 多VIEW操作 224 | 225 | | 方法名 |说明 | 需要的参数名 | 226 | | -----------------------|:------------------|:-------------------| 227 | | open |跳转(进入)到下一个view |url, param, callback | 228 | | back |回退到上一个view |args, callback | 229 | 230 | 231 | ###### c. 通用操作 232 | 233 | | 方法名 | 说明 | 需要的参数名 | 234 | | -----------------------|:------------------|:-------------------| 235 | | start_data_statistics | 客户端埋点 | control_type, control_name, args | 236 | 237 | 注释: 238 | 239 | - control_type: 埋点类型,0为`page`, 1为`button`, etc. 240 | - control_name: 埋点名称,如`1ActGoodsList`. 241 | - args: 为了方便扩展灵活的附加参数对象。 242 | 243 | ### 降级方案 244 | 245 | 当“握手”信号检测不到响应时,即认为非客户端,按照H5页面逻辑处理。 246 | 247 | ### 统一封装 248 | 249 | 完善`mClient`形成统一的`API`接口供页面调用,针对客户端和H5自适应的逻辑处理应由`mClient`内部完成,业务方应当不需要关注。 -------------------------------------------------------------------------------- /docs/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'novecento-bold'; 25 | src: url('public/fonts/novecento-bold.eot'); 26 | src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/novecento-bold.woff') format('woff'), 28 | url('public/fonts/novecento-bold.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | /*--------------------- Layout ----------------------------*/ 34 | html { height: 100%; } 35 | body { 36 | font-family: "aller-light"; 37 | font-size: 14px; 38 | line-height: 18px; 39 | color: #30404f; 40 | margin: 0; padding: 0; 41 | height:100%; 42 | } 43 | #container { min-height: 100%; } 44 | 45 | a { 46 | color: #000; 47 | } 48 | 49 | b, strong { 50 | font-weight: normal; 51 | font-family: "aller-bold"; 52 | } 53 | 54 | p { 55 | margin: 15px 0 0px; 56 | } 57 | .annotation ul, .annotation ol { 58 | margin: 25px 0; 59 | } 60 | .annotation ul li, .annotation ol li { 61 | font-size: 14px; 62 | line-height: 18px; 63 | margin: 10px 0; 64 | } 65 | 66 | h1, h2, h3, h4, h5, h6 { 67 | color: #112233; 68 | line-height: 1em; 69 | font-weight: normal; 70 | font-family: "novecento-bold"; 71 | text-transform: uppercase; 72 | margin: 30px 0 15px 0; 73 | } 74 | 75 | h1 { 76 | margin-top: 40px; 77 | } 78 | 79 | hr { 80 | border: 0; 81 | background: 1px #ddd; 82 | height: 1px; 83 | margin: 20px 0; 84 | } 85 | 86 | pre, tt, code { 87 | font-size: 12px; line-height: 16px; 88 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 89 | margin: 0; padding: 0; 90 | } 91 | .annotation pre { 92 | display: block; 93 | margin: 0; 94 | padding: 7px 10px; 95 | background: #fcfcfc; 96 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 97 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 98 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 99 | overflow-x: auto; 100 | } 101 | .annotation pre code { 102 | border: 0; 103 | padding: 0; 104 | background: transparent; 105 | } 106 | 107 | 108 | blockquote { 109 | border-left: 5px solid #ccc; 110 | margin: 0; 111 | padding: 1px 0 1px 1em; 112 | } 113 | .sections blockquote p { 114 | font-family: Menlo, Consolas, Monaco, monospace; 115 | font-size: 12px; line-height: 16px; 116 | color: #999; 117 | margin: 10px 0 0; 118 | white-space: pre-wrap; 119 | } 120 | 121 | ul.sections { 122 | list-style: none; 123 | padding:0 0 5px 0;; 124 | margin:0; 125 | } 126 | 127 | /* 128 | Force border-box so that % widths fit the parent 129 | container without overlap because of margin/padding. 130 | 131 | More Info : http://www.quirksmode.org/css/box.html 132 | */ 133 | ul.sections > li > div { 134 | -moz-box-sizing: border-box; /* firefox */ 135 | -ms-box-sizing: border-box; /* ie */ 136 | -webkit-box-sizing: border-box; /* webkit */ 137 | -khtml-box-sizing: border-box; /* konqueror */ 138 | box-sizing: border-box; /* css3 */ 139 | } 140 | 141 | 142 | /*---------------------- Jump Page -----------------------------*/ 143 | #jump_to, #jump_page { 144 | margin: 0; 145 | background: white; 146 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 147 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 148 | font: 16px Arial; 149 | cursor: pointer; 150 | text-align: right; 151 | list-style: none; 152 | } 153 | 154 | #jump_to a { 155 | text-decoration: none; 156 | } 157 | 158 | #jump_to a.large { 159 | display: none; 160 | } 161 | #jump_to a.small { 162 | font-size: 22px; 163 | font-weight: bold; 164 | color: #676767; 165 | } 166 | 167 | #jump_to, #jump_wrapper { 168 | position: fixed; 169 | right: 0; top: 0; 170 | padding: 10px 15px; 171 | margin:0; 172 | } 173 | 174 | #jump_wrapper { 175 | display: none; 176 | padding:0; 177 | } 178 | 179 | #jump_to:hover #jump_wrapper { 180 | display: block; 181 | } 182 | 183 | #jump_page { 184 | padding: 5px 0 3px; 185 | margin: 0 0 25px 25px; 186 | } 187 | 188 | #jump_page .source { 189 | display: block; 190 | padding: 15px; 191 | text-decoration: none; 192 | border-top: 1px solid #eee; 193 | } 194 | 195 | #jump_page .source:hover { 196 | background: #f5f5ff; 197 | } 198 | 199 | #jump_page .source:first-child { 200 | } 201 | 202 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 203 | @media only screen and (min-width: 320px) { 204 | .pilwrap { display: none; } 205 | 206 | ul.sections > li > div { 207 | display: block; 208 | padding:5px 10px 0 10px; 209 | } 210 | 211 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 212 | padding-left: 30px; 213 | } 214 | 215 | ul.sections > li > div.content { 216 | overflow-x:auto; 217 | -webkit-box-shadow: inset 0 0 5px #e5e5ee; 218 | box-shadow: inset 0 0 5px #e5e5ee; 219 | border: 1px solid #dedede; 220 | margin:5px 10px 5px 10px; 221 | padding-bottom: 5px; 222 | } 223 | 224 | ul.sections > li > div.annotation pre { 225 | margin: 7px 0 7px; 226 | padding-left: 15px; 227 | } 228 | 229 | ul.sections > li > div.annotation p tt, .annotation code { 230 | background: #f8f8ff; 231 | border: 1px solid #dedede; 232 | font-size: 12px; 233 | padding: 0 0.2em; 234 | } 235 | } 236 | 237 | /*---------------------- (> 481px) ---------------------*/ 238 | @media only screen and (min-width: 481px) { 239 | #container { 240 | position: relative; 241 | } 242 | body { 243 | background-color: #F5F5FF; 244 | font-size: 15px; 245 | line-height: 21px; 246 | } 247 | pre, tt, code { 248 | line-height: 18px; 249 | } 250 | p, ul, ol { 251 | margin: 0 0 15px; 252 | } 253 | 254 | 255 | #jump_to { 256 | padding: 5px 10px; 257 | } 258 | #jump_wrapper { 259 | padding: 0; 260 | } 261 | #jump_to, #jump_page { 262 | font: 10px Arial; 263 | text-transform: uppercase; 264 | } 265 | #jump_page .source { 266 | padding: 5px 10px; 267 | } 268 | #jump_to a.large { 269 | display: inline-block; 270 | } 271 | #jump_to a.small { 272 | display: none; 273 | } 274 | 275 | 276 | 277 | #background { 278 | position: absolute; 279 | top: 0; bottom: 0; 280 | width: 350px; 281 | background: #fff; 282 | border-right: 1px solid #e5e5ee; 283 | z-index: -1; 284 | } 285 | 286 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 287 | padding-left: 40px; 288 | } 289 | 290 | ul.sections > li { 291 | white-space: nowrap; 292 | } 293 | 294 | ul.sections > li > div { 295 | display: inline-block; 296 | } 297 | 298 | ul.sections > li > div.annotation { 299 | max-width: 350px; 300 | min-width: 350px; 301 | min-height: 5px; 302 | padding: 13px; 303 | overflow-x: hidden; 304 | white-space: normal; 305 | vertical-align: top; 306 | text-align: left; 307 | } 308 | ul.sections > li > div.annotation pre { 309 | margin: 15px 0 15px; 310 | padding-left: 15px; 311 | } 312 | 313 | ul.sections > li > div.content { 314 | padding: 13px; 315 | vertical-align: top; 316 | border: none; 317 | -webkit-box-shadow: none; 318 | box-shadow: none; 319 | } 320 | 321 | .pilwrap { 322 | position: relative; 323 | display: inline; 324 | } 325 | 326 | .pilcrow { 327 | font: 12px Arial; 328 | text-decoration: none; 329 | color: #454545; 330 | position: absolute; 331 | top: 3px; left: -20px; 332 | padding: 1px 2px; 333 | opacity: 0; 334 | -webkit-transition: opacity 0.2s linear; 335 | } 336 | .for-h1 .pilcrow { 337 | top: 47px; 338 | } 339 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { 340 | top: 35px; 341 | } 342 | 343 | ul.sections > li > div.annotation:hover .pilcrow { 344 | opacity: 1; 345 | } 346 | } 347 | 348 | /*---------------------- (> 1025px) ---------------------*/ 349 | @media only screen and (min-width: 1025px) { 350 | 351 | body { 352 | font-size: 16px; 353 | line-height: 24px; 354 | } 355 | 356 | #background { 357 | width: 525px; 358 | } 359 | ul.sections > li > div.annotation { 360 | max-width: 525px; 361 | min-width: 525px; 362 | padding: 10px 25px 1px 50px; 363 | } 364 | ul.sections > li > div.content { 365 | padding: 9px 15px 16px 25px; 366 | } 367 | } 368 | 369 | /*---------------------- Syntax Highlighting -----------------------------*/ 370 | 371 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 372 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 373 | /* 374 | 375 | github.com style (c) Vasily Polovnyov 376 | 377 | */ 378 | 379 | pre code { 380 | display: block; padding: 0.5em; 381 | color: #000; 382 | background: #f8f8ff 383 | } 384 | 385 | pre .hljs-comment, 386 | pre .hljs-template_comment, 387 | pre .hljs-diff .hljs-header, 388 | pre .hljs-javadoc { 389 | color: #408080; 390 | font-style: italic 391 | } 392 | 393 | pre .hljs-keyword, 394 | pre .hljs-assignment, 395 | pre .hljs-literal, 396 | pre .hljs-css .hljs-rule .hljs-keyword, 397 | pre .hljs-winutils, 398 | pre .hljs-javascript .hljs-title, 399 | pre .hljs-lisp .hljs-title, 400 | pre .hljs-subst { 401 | color: #954121; 402 | /*font-weight: bold*/ 403 | } 404 | 405 | pre .hljs-number, 406 | pre .hljs-hexcolor { 407 | color: #40a070 408 | } 409 | 410 | pre .hljs-string, 411 | pre .hljs-tag .hljs-value, 412 | pre .hljs-phpdoc, 413 | pre .hljs-tex .hljs-formula { 414 | color: #219161; 415 | } 416 | 417 | pre .hljs-title, 418 | pre .hljs-id { 419 | color: #19469D; 420 | } 421 | pre .hljs-params { 422 | color: #00F; 423 | } 424 | 425 | pre .hljs-javascript .hljs-title, 426 | pre .hljs-lisp .hljs-title, 427 | pre .hljs-subst { 428 | font-weight: normal 429 | } 430 | 431 | pre .hljs-class .hljs-title, 432 | pre .hljs-haskell .hljs-label, 433 | pre .hljs-tex .hljs-command { 434 | color: #458; 435 | font-weight: bold 436 | } 437 | 438 | pre .hljs-tag, 439 | pre .hljs-tag .hljs-title, 440 | pre .hljs-rules .hljs-property, 441 | pre .hljs-django .hljs-tag .hljs-keyword { 442 | color: #000080; 443 | font-weight: normal 444 | } 445 | 446 | pre .hljs-attribute, 447 | pre .hljs-variable, 448 | pre .hljs-instancevar, 449 | pre .hljs-lisp .hljs-body { 450 | color: #008080 451 | } 452 | 453 | pre .hljs-regexp { 454 | color: #B68 455 | } 456 | 457 | pre .hljs-class { 458 | color: #458; 459 | font-weight: bold 460 | } 461 | 462 | pre .hljs-symbol, 463 | pre .hljs-ruby .hljs-symbol .hljs-string, 464 | pre .hljs-ruby .hljs-symbol .hljs-keyword, 465 | pre .hljs-ruby .hljs-symbol .hljs-keymethods, 466 | pre .hljs-lisp .hljs-keyword, 467 | pre .hljs-tex .hljs-special, 468 | pre .hljs-input_number { 469 | color: #990073 470 | } 471 | 472 | pre .hljs-builtin, 473 | pre .hljs-constructor, 474 | pre .hljs-built_in, 475 | pre .hljs-lisp .hljs-title { 476 | color: #0086b3 477 | } 478 | 479 | pre .hljs-preprocessor, 480 | pre .hljs-pi, 481 | pre .hljs-doctype, 482 | pre .hljs-shebang, 483 | pre .hljs-cdata { 484 | color: #999; 485 | font-weight: bold 486 | } 487 | 488 | pre .hljs-deletion { 489 | background: #fdd 490 | } 491 | 492 | pre .hljs-addition { 493 | background: #dfd 494 | } 495 | 496 | pre .hljs-diff .hljs-change { 497 | background: #0086b3 498 | } 499 | 500 | pre .hljs-chunk { 501 | color: #aaa 502 | } 503 | 504 | pre .hljs-tex .hljs-formula { 505 | opacity: 0.5; 506 | } 507 | -------------------------------------------------------------------------------- /demo/bridge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bridge Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 33 | 34 | 35 | 36 |
37 | 外跳测试 38 | 39 | 40 | 41 | 42 |
43 | 44 | 45 | 46 |
47 | SDK相关测试 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
83 | 84 | 85 | 86 |
87 | 其它接口测试 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 | 98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
110 | 111 | 339 | 340 | 392 | 393 | 394 | 395 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | fs = require('fs-extra'), 3 | os = require('os'), 4 | exec = require('child_process').exec; 5 | 6 | module.exports = function (grunt) { 7 | 8 | var file = grunt.file; 9 | var task = grunt.task; 10 | var pathname = path.basename(__dirname); 11 | var files = doWalk('./'); 12 | // files.js 存储项目中的所有js文件 13 | // file.css 存储项目中的所有css文件 14 | // file.less 存储项目中所有less文件 15 | 16 | // ======================= 配置每个任务 ========================== 17 | 18 | grunt.initConfig({ 19 | 20 | pkg : grunt.file.readJSON('abc.json'), 21 | 22 | // 配置默认分支 23 | currentBranch: 'master', 24 | 25 | 26 | // 对页面进行清理 27 | clean : { 28 | 29 | build: { 30 | src: 'build/*' 31 | } 32 | }, 33 | 34 | /** 35 | * 将src目录中的KISSY文件做编译打包,仅做合并,源文件需要指定名称 36 | * KISSY.add('package/path/to/file',function(S){}); 37 | * 38 | * @link https://github.com/daxingplay/grunt-kmc 39 | * 40 | * 如果需要只生成依赖关系表,不做合并 41 | * 在kmc.options中增加两个参数: 42 | * depFilePath: 'build/mods.js', 43 | * comboOnly: true, 44 | * comboMap: true, 45 | */ 46 | kmc : { 47 | options: { 48 | packages: [ 49 | { 50 | name : 'mpi', 51 | path : '../', 52 | charset: 'utf-8' 53 | } 54 | 55 | ] 56 | }, 57 | 58 | main: { 59 | files: [ 60 | { 61 | // 这里指定项目根目录下所有文件为入口文件,自定义入口请自行添加 62 | expand: true, 63 | cwd : './', 64 | src : [ '**/*.js', '!**/node_modules/**', '!**/build/**', '!**/demo/**', '!**/doc/**', '!**/guide/**', '!**/tests/**', '!Gruntfile.js'], 65 | dest : 'build/' 66 | } 67 | ] 68 | } 69 | // 若有新任务,请自行添加 70 | /* 71 | "simple-example": { 72 | files: [ 73 | { 74 | src: "a.js", 75 | dest: "build/index.js" 76 | } 77 | ] 78 | } 79 | */ 80 | }, 81 | 82 | // CSS-Combo: 合并项目中所有css,通过@import "other.css" 来处理CSS的依赖关系 83 | css_combo: { 84 | options: { 85 | paths: './' 86 | }, 87 | main : { 88 | files: [ 89 | { 90 | expand: true, 91 | cwd : 'build', 92 | src : ['**/*.css'], 93 | dest : 'build/', 94 | ext : '.css' 95 | } 96 | ] 97 | } 98 | }, 99 | 100 | // 编译LESS为CSS https://github.com/gruntjs/grunt-contrib-less 101 | less : { 102 | options: { 103 | paths: './' 104 | }, 105 | main : { 106 | files: [ 107 | { 108 | expand: true, 109 | cwd : 'build/', 110 | src : ['**/*.less'], 111 | dest : 'build/', 112 | ext : '.css' 113 | } 114 | ] 115 | } 116 | }, 117 | 118 | // 压缩JS https://github.com/gruntjs/grunt-contrib-uglify 119 | uglify : { 120 | options: { 121 | banner : '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd hh:MM:ss") %> */\n', 122 | beautify: { 123 | ascii_only: true 124 | } 125 | }, 126 | main : { 127 | files: [ 128 | { 129 | expand: true, 130 | cwd : 'build/', 131 | src : ['**/*.js', '!**/*-min.js'], 132 | dest : 'build/', 133 | ext : '-min.js' 134 | } 135 | ] 136 | } 137 | }, 138 | 139 | // 压缩CSS https://github.com/gruntjs/grunt-contrib-cssmin 140 | cssmin : { 141 | main: { 142 | files: [ 143 | { 144 | expand: true, 145 | cwd : 'build/', 146 | src : ['**/*.css', '!**/*-min.css'], 147 | dest : 'build/', 148 | ext : '-min.css' 149 | } 150 | ] 151 | } 152 | }, 153 | 154 | // 发布命令 155 | exec : { 156 | tag : { 157 | command: 'git tag publish/<%= currentBranch %>' 158 | }, 159 | publish : { 160 | command: 'git push origin publish/<%= currentBranch %>:publish/<%= currentBranch %>' 161 | }, 162 | commit : { 163 | command: 'git commit -m "<%= currentBranch %> - <%= grunt.template.today("yyyy-mm-dd hh:MM:ss") %>"' 164 | }, 165 | add : { 166 | command: 'git add .' 167 | }, 168 | prepub : { 169 | command: 'git push origin daily/<%= currentBranch %>:daily/<%= currentBranch %>' 170 | }, 171 | grunt_publish: { 172 | command: 'grunt default:publish' 173 | }, 174 | grunt_prepub : { 175 | command: 'grunt default:prepub' 176 | }, 177 | new_branch : { 178 | command: 'git checkout -b daily/<%= currentBranch %>' 179 | } 180 | }, 181 | 182 | // 拷贝文件 183 | copy : { 184 | main: { 185 | files: [ 186 | { 187 | // src: files.js, 188 | expand: true, 189 | src : ['**/*.html', '**/*.htm', '**/*.js', '**/*.less', '**/*.css', '**/*.png', '**/*.gif', '**/*.jpg', '!**/node_modules/**', '!**/build/', '!**/demo/**', '!**/doc/**', '!**/guide/**', '!**/tests/**', '!Gruntfile.js'], 190 | dest : 'build/', 191 | cwd : './', 192 | filter: 'isFile' 193 | } 194 | ] 195 | } 196 | }, 197 | // 替换config中的版本号@@version 198 | replace : { 199 | dist: { 200 | options: { 201 | variables: { 202 | 'version': '<%= pkg.version %>' 203 | }, 204 | prefix : '@@' 205 | }, 206 | files : [ 207 | { 208 | expand: true, 209 | cwd : 'build/', 210 | dest : 'build/', 211 | src : ['**/*'] 212 | } 213 | ] 214 | } 215 | } 216 | 217 | // 合并文件 218 | /* 219 | concat: { 220 | dist: { 221 | src: ['from.css'], 222 | dest: 'build/to.css' 223 | 224 | } 225 | }, 226 | */ 227 | 228 | // YUIDoc: 对build目录中的js文件生成文档,放入doc/中 229 | /* 230 | yuidoc: { 231 | compile: { 232 | name: 'generator-clam', 233 | description: 'A Clam generator for Yeoman', 234 | options: { 235 | paths: 'build/', 236 | outdir: 'doc/' 237 | } 238 | } 239 | } 240 | */ 241 | 242 | }); 243 | 244 | // ======================= 载入使用到的通过NPM安装的模块 ========================== 245 | 246 | grunt.loadNpmTasks('grunt-contrib-clean'); 247 | grunt.loadNpmTasks('grunt-contrib-uglify'); 248 | grunt.loadNpmTasks('grunt-css-combo'); 249 | grunt.loadNpmTasks('grunt-contrib-less'); 250 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 251 | grunt.loadNpmTasks('grunt-kmc'); 252 | grunt.loadNpmTasks('grunt-exec'); 253 | grunt.loadNpmTasks('grunt-contrib-copy'); 254 | grunt.loadNpmTasks('grunt-replace'); 255 | 256 | // 根据需要打开这些配置 257 | //grunt.loadNpmTasks('grunt-kissy-template'); 258 | //grunt.loadNpmTasks('grunt-contrib-connect'); 259 | //grunt.loadNpmTasks('grunt-contrib-concat'); 260 | //grunt.loadNpmTasks('grunt-contrib-yuidoc'); 261 | 262 | // ======================= 注册Grunt 各个操作 ========================== 263 | 264 | /** 265 | * 正式发布 266 | */ 267 | grunt.registerTask('publish', 'clam publish...', function () { 268 | task.run('exec:grunt_publish'); 269 | }); 270 | grunt.registerTask('pub', 'clam publish...', function () { 271 | task.run('exec:grunt_publish'); 272 | }); 273 | grunt.registerTask('build', 'clam publish...', function () { 274 | //task.run('combohtml'); 275 | }); 276 | 277 | /** 278 | * 预发布 279 | */ 280 | grunt.registerTask('prepub', 'clam pre publish...', function () { 281 | task.run('exec:grunt_prepub'); 282 | }); 283 | 284 | 285 | /* 286 | * 获取当前库的信息 287 | **/ 288 | grunt.registerTask('info', 'clam info...', function () { 289 | var abcJSON = {}; 290 | try { 291 | abcJSON = require(path.resolve(process.cwd(), 'abc.json')); 292 | console.log('\n' + abcJSON.repository.url); 293 | } catch (e) { 294 | console.log('未找到abc.json'); 295 | } 296 | }); 297 | 298 | /* 299 | * 获取当前最大版本号,并创建新分支 300 | **/ 301 | grunt.registerTask('newbranch', 'clam newBranch...', function (type) { 302 | var done = this.async(); 303 | exec('git branch -a & git tag', function (err, stdout, stderr, cb) { 304 | var r = getBiggestVersion(stdout.match(/\d+\.\d+\.\d+/ig)); 305 | if (!r) { 306 | r = '0.0.1'; 307 | } else { 308 | r[2]++; 309 | r = r.join('.'); 310 | } 311 | grunt.log.write(('新分支:daily/' + r).green); 312 | grunt.config.set('currentBranch', r); 313 | task.run(['exec:new_branch']); 314 | // 回写入 abc.json 的 version 315 | try { 316 | abcJSON = require(path.resolve(process.cwd(), 'abc.json')); 317 | abcJSON.version = r; 318 | fs.writeJSONFile("abc.json", abcJSON, function (err) { 319 | if (err) { 320 | console.log(err); 321 | } else { 322 | console.log("update abc.json."); 323 | } 324 | }); 325 | } catch (e) { 326 | console.log('未找到abc.json'); 327 | } 328 | done(); 329 | }); 330 | }); 331 | 332 | // ======================= 注册Grunt主流程 ========================== 333 | 334 | return grunt.registerTask('default', 'clam running ...', function (type) { 335 | 336 | var done = this.async(); 337 | 338 | // 获取当前分支 339 | exec('git branch', function (err, stdout, stderr, cb) { 340 | 341 | var reg = /\*\s+daily\/(\S+)/, 342 | match = stdout.match(reg); 343 | 344 | if (!match) { 345 | grunt.log.error('当前分支为 master 或者名字不合法(daily/x.y.z),请切换到daily分支'.red); 346 | grunt.log.error('创建新daily分支:grunt newbranch'.yellow); 347 | return; 348 | } 349 | grunt.log.write(('当前分支:' + match[1]).green); 350 | grunt.config.set('currentBranch', match[1]); 351 | done(); 352 | }); 353 | 354 | // 构建和发布任务 355 | if (!type) { 356 | task.run(['clean:build', 'copy', 'less', 'css_combo', /*'kmc',*/ 'replace', 'uglify', 'cssmin'/*'concat','yuidoc'*/]); 357 | } else if ('publish' === type || 'pub' === type) { 358 | task.run(['exec:tag', 'exec:publish']); 359 | } else if ('prepub' === type) { 360 | task.run(['exec:add', 'exec:commit']); 361 | task.run(['exec:prepub']); 362 | } 363 | 364 | }); 365 | 366 | // ======================= 辅助函数 ========================== 367 | 368 | // 遍历当前目录的文件 369 | function walk(uri, files) { 370 | 371 | var stat = fs.lstatSync(uri); 372 | if (stat.isFile() && !/(build|node_modules|demo|doc|\.git|\.+)[\\|\/]/i.test(uri) && !/grunt.+/i.test(uri)) { 373 | 374 | switch (path.extname(uri)) { 375 | case '.css': 376 | files.css.push(uri); 377 | break; 378 | case '.js': 379 | files.js.push(uri); 380 | break; 381 | case '.less': 382 | files.less.push(uri); 383 | break; 384 | case '.scss': 385 | files.scss.push(uri); 386 | break; 387 | default: 388 | files.other.push(uri); 389 | } 390 | } 391 | if (stat.isDirectory()) { 392 | fs.readdirSync(uri).forEach(function (part) { 393 | walk(path.join(uri, part), files); 394 | }); 395 | } 396 | } 397 | 398 | 399 | // 得到文件结构的数据结构 400 | function doWalk(rootDir) { 401 | var files = { 402 | css : [], 403 | less : [], 404 | scss : [], 405 | js : [], 406 | other: [] // 暂时没用 407 | }; 408 | walk(rootDir, files); 409 | return files; 410 | } 411 | 412 | function getBiggestVersion(A) { 413 | var a = []; 414 | var b = []; 415 | var t = []; 416 | var r = []; 417 | if (!A) { 418 | return [0, 0, 0]; 419 | } 420 | for (var i = 0; i < A.length; i++) { 421 | if (A[i].match(/^\d+\.\d+\.\d+$/)) { 422 | var sp = A[i].split('.'); 423 | a.push([ 424 | Number(sp[0]), Number(sp[1]), Number(sp[2]) 425 | ]); 426 | } 427 | } 428 | 429 | var r = findMax(findMax(findMax(a, 0), 1), 2); 430 | return r[0]; 431 | } 432 | 433 | // a:二维数组,index,比较第几个 434 | // return:返回保留比较后的结果组成的二维数组 435 | function findMax(a, index) { 436 | var t = []; 437 | var b = []; 438 | var r = []; 439 | for (var i = 0; i < a.length; i++) { 440 | t.push(Number(a[i][index])); 441 | } 442 | var max = Math.max.apply(this, t); 443 | for (var i = 0; i < a.length; i++) { 444 | if (a[i][index] === max) { 445 | b.push(i); 446 | } 447 | } 448 | for (var i = 0; i < b.length; i++) { 449 | r.push(a[b[i]]); 450 | } 451 | return r; 452 | } 453 | }; 454 | -------------------------------------------------------------------------------- /WebView/WebView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 11DF46E3194CE33F0072FDE3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11DF46E2194CE33F0072FDE3 /* AppDelegate.swift */; }; 11 | 11DF46E5194CE33F0072FDE3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11DF46E4194CE33F0072FDE3 /* ViewController.swift */; }; 12 | 11DF46E8194CE33F0072FDE3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 11DF46E6194CE33F0072FDE3 /* Main.storyboard */; }; 13 | 11DF46EA194CE33F0072FDE3 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 11DF46E9194CE33F0072FDE3 /* Images.xcassets */; }; 14 | 11DF46F6194CE3400072FDE3 /* WebViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11DF46F5194CE3400072FDE3 /* WebViewTests.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | 11DF46F0194CE3400072FDE3 /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = 11DF46D5194CE33F0072FDE3 /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = 11DF46DC194CE33F0072FDE3; 23 | remoteInfo = WebView; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 11DF46DD194CE33F0072FDE3 /* WebView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WebView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 11DF46E1194CE33F0072FDE3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 11DF46E2194CE33F0072FDE3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 31 | 11DF46E4194CE33F0072FDE3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 32 | 11DF46E7194CE33F0072FDE3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 33 | 11DF46E9194CE33F0072FDE3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 34 | 11DF46EF194CE3400072FDE3 /* WebViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WebViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 11DF46F4194CE3400072FDE3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 11DF46F5194CE3400072FDE3 /* WebViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewTests.swift; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 11DF46DA194CE33F0072FDE3 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | 11DF46EC194CE3400072FDE3 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 11DF46D4194CE33F0072FDE3 = { 58 | isa = PBXGroup; 59 | children = ( 60 | 11DF46DF194CE33F0072FDE3 /* WebView */, 61 | 11DF46F2194CE3400072FDE3 /* WebViewTests */, 62 | 11DF46DE194CE33F0072FDE3 /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | 11DF46DE194CE33F0072FDE3 /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 11DF46DD194CE33F0072FDE3 /* WebView.app */, 70 | 11DF46EF194CE3400072FDE3 /* WebViewTests.xctest */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | 11DF46DF194CE33F0072FDE3 /* WebView */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 11DF46E2194CE33F0072FDE3 /* AppDelegate.swift */, 79 | 11DF46E4194CE33F0072FDE3 /* ViewController.swift */, 80 | 11DF46E6194CE33F0072FDE3 /* Main.storyboard */, 81 | 11DF46E9194CE33F0072FDE3 /* Images.xcassets */, 82 | 11DF46E0194CE33F0072FDE3 /* Supporting Files */, 83 | ); 84 | path = WebView; 85 | sourceTree = ""; 86 | }; 87 | 11DF46E0194CE33F0072FDE3 /* Supporting Files */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 11DF46E1194CE33F0072FDE3 /* Info.plist */, 91 | ); 92 | name = "Supporting Files"; 93 | sourceTree = ""; 94 | }; 95 | 11DF46F2194CE3400072FDE3 /* WebViewTests */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 11DF46F5194CE3400072FDE3 /* WebViewTests.swift */, 99 | 11DF46F3194CE3400072FDE3 /* Supporting Files */, 100 | ); 101 | path = WebViewTests; 102 | sourceTree = ""; 103 | }; 104 | 11DF46F3194CE3400072FDE3 /* Supporting Files */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 11DF46F4194CE3400072FDE3 /* Info.plist */, 108 | ); 109 | name = "Supporting Files"; 110 | sourceTree = ""; 111 | }; 112 | /* End PBXGroup section */ 113 | 114 | /* Begin PBXNativeTarget section */ 115 | 11DF46DC194CE33F0072FDE3 /* WebView */ = { 116 | isa = PBXNativeTarget; 117 | buildConfigurationList = 11DF46F9194CE3400072FDE3 /* Build configuration list for PBXNativeTarget "WebView" */; 118 | buildPhases = ( 119 | 11DF46D9194CE33F0072FDE3 /* Sources */, 120 | 11DF46DA194CE33F0072FDE3 /* Frameworks */, 121 | 11DF46DB194CE33F0072FDE3 /* Resources */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | ); 127 | name = WebView; 128 | productName = WebView; 129 | productReference = 11DF46DD194CE33F0072FDE3 /* WebView.app */; 130 | productType = "com.apple.product-type.application"; 131 | }; 132 | 11DF46EE194CE3400072FDE3 /* WebViewTests */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = 11DF46FC194CE3400072FDE3 /* Build configuration list for PBXNativeTarget "WebViewTests" */; 135 | buildPhases = ( 136 | 11DF46EB194CE3400072FDE3 /* Sources */, 137 | 11DF46EC194CE3400072FDE3 /* Frameworks */, 138 | 11DF46ED194CE3400072FDE3 /* Resources */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | 11DF46F1194CE3400072FDE3 /* PBXTargetDependency */, 144 | ); 145 | name = WebViewTests; 146 | productName = WebViewTests; 147 | productReference = 11DF46EF194CE3400072FDE3 /* WebViewTests.xctest */; 148 | productType = "com.apple.product-type.bundle.unit-test"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | 11DF46D5194CE33F0072FDE3 /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | LastUpgradeCheck = 0600; 157 | ORGANIZATIONNAME = "破锣锅"; 158 | TargetAttributes = { 159 | 11DF46DC194CE33F0072FDE3 = { 160 | CreatedOnToolsVersion = 6.0; 161 | }; 162 | 11DF46EE194CE3400072FDE3 = { 163 | CreatedOnToolsVersion = 6.0; 164 | TestTargetID = 11DF46DC194CE33F0072FDE3; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = 11DF46D8194CE33F0072FDE3 /* Build configuration list for PBXProject "WebView" */; 169 | compatibilityVersion = "Xcode 3.2"; 170 | developmentRegion = English; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = 11DF46D4194CE33F0072FDE3; 177 | productRefGroup = 11DF46DE194CE33F0072FDE3 /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | 11DF46DC194CE33F0072FDE3 /* WebView */, 182 | 11DF46EE194CE3400072FDE3 /* WebViewTests */, 183 | ); 184 | }; 185 | /* End PBXProject section */ 186 | 187 | /* Begin PBXResourcesBuildPhase section */ 188 | 11DF46DB194CE33F0072FDE3 /* Resources */ = { 189 | isa = PBXResourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | 11DF46E8194CE33F0072FDE3 /* Main.storyboard in Resources */, 193 | 11DF46EA194CE33F0072FDE3 /* Images.xcassets in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | 11DF46ED194CE3400072FDE3 /* Resources */ = { 198 | isa = PBXResourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | }; 204 | /* End PBXResourcesBuildPhase section */ 205 | 206 | /* Begin PBXSourcesBuildPhase section */ 207 | 11DF46D9194CE33F0072FDE3 /* Sources */ = { 208 | isa = PBXSourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | 11DF46E5194CE33F0072FDE3 /* ViewController.swift in Sources */, 212 | 11DF46E3194CE33F0072FDE3 /* AppDelegate.swift in Sources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | 11DF46EB194CE3400072FDE3 /* Sources */ = { 217 | isa = PBXSourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | 11DF46F6194CE3400072FDE3 /* WebViewTests.swift in Sources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | /* End PBXSourcesBuildPhase section */ 225 | 226 | /* Begin PBXTargetDependency section */ 227 | 11DF46F1194CE3400072FDE3 /* PBXTargetDependency */ = { 228 | isa = PBXTargetDependency; 229 | target = 11DF46DC194CE33F0072FDE3 /* WebView */; 230 | targetProxy = 11DF46F0194CE3400072FDE3 /* PBXContainerItemProxy */; 231 | }; 232 | /* End PBXTargetDependency section */ 233 | 234 | /* Begin PBXVariantGroup section */ 235 | 11DF46E6194CE33F0072FDE3 /* Main.storyboard */ = { 236 | isa = PBXVariantGroup; 237 | children = ( 238 | 11DF46E7194CE33F0072FDE3 /* Base */, 239 | ); 240 | name = Main.storyboard; 241 | sourceTree = ""; 242 | }; 243 | /* End PBXVariantGroup section */ 244 | 245 | /* Begin XCBuildConfiguration section */ 246 | 11DF46F7194CE3400072FDE3 /* Debug */ = { 247 | isa = XCBuildConfiguration; 248 | buildSettings = { 249 | ALWAYS_SEARCH_USER_PATHS = NO; 250 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 251 | CLANG_CXX_LIBRARY = "libc++"; 252 | CLANG_ENABLE_MODULES = YES; 253 | CLANG_ENABLE_OBJC_ARC = YES; 254 | CLANG_WARN_BOOL_CONVERSION = YES; 255 | CLANG_WARN_CONSTANT_CONVERSION = YES; 256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 257 | CLANG_WARN_EMPTY_BODY = YES; 258 | CLANG_WARN_ENUM_CONVERSION = YES; 259 | CLANG_WARN_INT_CONVERSION = YES; 260 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 261 | CLANG_WARN_UNREACHABLE_CODE = YES; 262 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 263 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 264 | COPY_PHASE_STRIP = NO; 265 | ENABLE_STRICT_OBJC_MSGSEND = YES; 266 | GCC_C_LANGUAGE_STANDARD = gnu99; 267 | GCC_DYNAMIC_NO_PIC = NO; 268 | GCC_OPTIMIZATION_LEVEL = 0; 269 | GCC_PREPROCESSOR_DEFINITIONS = ( 270 | "DEBUG=1", 271 | "$(inherited)", 272 | ); 273 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 276 | GCC_WARN_UNDECLARED_SELECTOR = YES; 277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 278 | GCC_WARN_UNUSED_FUNCTION = YES; 279 | GCC_WARN_UNUSED_VARIABLE = YES; 280 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 281 | METAL_ENABLE_DEBUG_INFO = YES; 282 | ONLY_ACTIVE_ARCH = YES; 283 | SDKROOT = iphoneos; 284 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 285 | }; 286 | name = Debug; 287 | }; 288 | 11DF46F8194CE3400072FDE3 /* Release */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ALWAYS_SEARCH_USER_PATHS = NO; 292 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 293 | CLANG_CXX_LIBRARY = "libc++"; 294 | CLANG_ENABLE_MODULES = YES; 295 | CLANG_ENABLE_OBJC_ARC = YES; 296 | CLANG_WARN_BOOL_CONVERSION = YES; 297 | CLANG_WARN_CONSTANT_CONVERSION = YES; 298 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 299 | CLANG_WARN_EMPTY_BODY = YES; 300 | CLANG_WARN_ENUM_CONVERSION = YES; 301 | CLANG_WARN_INT_CONVERSION = YES; 302 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 303 | CLANG_WARN_UNREACHABLE_CODE = YES; 304 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 305 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 306 | COPY_PHASE_STRIP = YES; 307 | ENABLE_NS_ASSERTIONS = NO; 308 | ENABLE_STRICT_OBJC_MSGSEND = YES; 309 | GCC_C_LANGUAGE_STANDARD = gnu99; 310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 314 | GCC_WARN_UNUSED_FUNCTION = YES; 315 | GCC_WARN_UNUSED_VARIABLE = YES; 316 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 317 | METAL_ENABLE_DEBUG_INFO = NO; 318 | SDKROOT = iphoneos; 319 | VALIDATE_PRODUCT = YES; 320 | }; 321 | name = Release; 322 | }; 323 | 11DF46FA194CE3400072FDE3 /* Debug */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 327 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 328 | INFOPLIST_FILE = WebView/Info.plist; 329 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 330 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 331 | PRODUCT_NAME = "$(TARGET_NAME)"; 332 | }; 333 | name = Debug; 334 | }; 335 | 11DF46FB194CE3400072FDE3 /* Release */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 339 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 340 | INFOPLIST_FILE = WebView/Info.plist; 341 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 342 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 343 | PRODUCT_NAME = "$(TARGET_NAME)"; 344 | }; 345 | name = Release; 346 | }; 347 | 11DF46FD194CE3400072FDE3 /* Debug */ = { 348 | isa = XCBuildConfiguration; 349 | buildSettings = { 350 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/WebView.app/WebView"; 351 | FRAMEWORK_SEARCH_PATHS = ( 352 | "$(SDKROOT)/Developer/Library/Frameworks", 353 | "$(inherited)", 354 | ); 355 | GCC_PREPROCESSOR_DEFINITIONS = ( 356 | "DEBUG=1", 357 | "$(inherited)", 358 | ); 359 | INFOPLIST_FILE = WebViewTests/Info.plist; 360 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 361 | METAL_ENABLE_DEBUG_INFO = YES; 362 | PRODUCT_NAME = "$(TARGET_NAME)"; 363 | TEST_HOST = "$(BUNDLE_LOADER)"; 364 | }; 365 | name = Debug; 366 | }; 367 | 11DF46FE194CE3400072FDE3 /* Release */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/WebView.app/WebView"; 371 | FRAMEWORK_SEARCH_PATHS = ( 372 | "$(SDKROOT)/Developer/Library/Frameworks", 373 | "$(inherited)", 374 | ); 375 | INFOPLIST_FILE = WebViewTests/Info.plist; 376 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 377 | METAL_ENABLE_DEBUG_INFO = NO; 378 | PRODUCT_NAME = "$(TARGET_NAME)"; 379 | TEST_HOST = "$(BUNDLE_LOADER)"; 380 | }; 381 | name = Release; 382 | }; 383 | /* End XCBuildConfiguration section */ 384 | 385 | /* Begin XCConfigurationList section */ 386 | 11DF46D8194CE33F0072FDE3 /* Build configuration list for PBXProject "WebView" */ = { 387 | isa = XCConfigurationList; 388 | buildConfigurations = ( 389 | 11DF46F7194CE3400072FDE3 /* Debug */, 390 | 11DF46F8194CE3400072FDE3 /* Release */, 391 | ); 392 | defaultConfigurationIsVisible = 0; 393 | defaultConfigurationName = Release; 394 | }; 395 | 11DF46F9194CE3400072FDE3 /* Build configuration list for PBXNativeTarget "WebView" */ = { 396 | isa = XCConfigurationList; 397 | buildConfigurations = ( 398 | 11DF46FA194CE3400072FDE3 /* Debug */, 399 | 11DF46FB194CE3400072FDE3 /* Release */, 400 | ); 401 | defaultConfigurationIsVisible = 0; 402 | }; 403 | 11DF46FC194CE3400072FDE3 /* Build configuration list for PBXNativeTarget "WebViewTests" */ = { 404 | isa = XCConfigurationList; 405 | buildConfigurations = ( 406 | 11DF46FD194CE3400072FDE3 /* Debug */, 407 | 11DF46FE194CE3400072FDE3 /* Release */, 408 | ); 409 | defaultConfigurationIsVisible = 0; 410 | }; 411 | /* End XCConfigurationList section */ 412 | }; 413 | rootObject = 11DF46D5194CE33F0072FDE3 /* Project object */; 414 | } 415 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A Bridge Between H5 And Native. 3 | * By: Guokai @benbencc 4 | * Created: 2013-7-24 5 | * */ 6 | 7 | // 面向Android和iOS的JS调用原理: 8 | 9 | // Android 通过桥(客户端暴露在WebView全局对象下的一个对象,内挂各种API方法) 10 | // 的方式进行调用,如`window['Android_Bridge']['method'](JSON)`。iOS通过自定 11 | // 义Scheme(如`native://method?data=JSON`)方式调用。需要回调的接口需要将函 12 | // 数名称在调用时一并传给客户端,同时将回调函数通过唯一名称挂在全局,待客户端 13 | // 执行回调后移除该全局函数。 14 | 15 | (function() { 16 | 17 | // Bridge是为H5和客户端交互通讯而产生的一个中间件,即一个JavaScript的SDK。负责 18 | // 处理H5和客户端的方法调用、通信及H5页面自身的降级处理(非内嵌在客户端的情况)。 19 | 20 | var Bridge = function() { 21 | this.init.apply(this, arguments); 22 | }; 23 | 24 | Bridge.prototype = { 25 | version: '0.0.1', 26 | 27 | // 桥初始化,初始化过程中会:主动探测UA,判断页面所属的生存环境,如果是在客户端中, 28 | // 会探测获取客户端的平台类型和版本号,初始化消息队列,并主动探测网络类型,并同所属 29 | // 环境主动握手交换双方所需的额外基础信息。 30 | 31 | init: function(bridgeName) { 32 | var that = this; 33 | 34 | that.platform = 'h5'; 35 | that.bridgeName = bridgeName || 'ali_trip_webview_bridge'; 36 | that.bridge = window[that.bridgeName]; 37 | that.userAgentDetect.apply(this, arguments); 38 | that.messageQueueInit(); 39 | that.deviceInfoDetect(); 40 | that.connectionInfoDetect(); 41 | that.handShake.apply(this, arguments); 42 | 43 | that.notification.superthat = that; 44 | }, 45 | 46 | // 消息队列初始化,针对iOS的实现机制单独做的处理,iOS会主动轮询 47 | // 消息队列中的操作,并批量取回处理。 48 | 49 | messageQueueInit: function() { 50 | var that = this; 51 | 52 | if(that.platform === 'ios') { 53 | window.messageQueue = []; 54 | window.messageQueueFetch = function() { 55 | var response; 56 | response = window.messageQueue.length ? JSON.stringify(window.messageQueue) : ''; 57 | window.messageQueue = []; 58 | 59 | if(response) { 60 | return response; 61 | } 62 | }; 63 | } 64 | }, 65 | 66 | // 握手协议,用于页面同所属生存环境在首次初始化时进行基础信息交换。 67 | 68 | handShake: function(callback) { 69 | var that = this; 70 | 71 | that.pushBack('bridge:', 'ready', { 72 | data: {}, 73 | successCallback: function() { 74 | callback && callback(); 75 | } 76 | }); 77 | }, 78 | 79 | // UA探测,用于检测当前页面是否生存在淘宝旅行客户端内,如果在客户端内 80 | // 则继续获取客户端的平台类型及版本号用于后续的操作。 81 | 82 | // User-Agent Format: 83 | 84 | // Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_1_2 like Mac OS X; zh-cn; AliTrip/2.8.0) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile Safari/528.16 85 | // Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; MI 2 Build/JRO03L; AliTrip/2.8.0) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile Safari/528.16 86 | 87 | // References: 88 | 89 | // http://www.ietf.org/rfc/rfc2616.txt 90 | // http://www.useragentstring.com/ 91 | 92 | userAgentDetect: function() { 93 | var that = this; 94 | var ua = navigator.userAgent; 95 | var match = ua.match(/AliTrip[\s\/][\d\.]+/igm); 96 | 97 | if(match) { 98 | that.platform = ua.match(/(iPad|iPhone|iPod)/igm) ? 'ios' : 'android'; 99 | that.client.version = parseInt(match[0].match(/[\d\.]+/igm)[0].split('.').join('')); 100 | } 101 | }, 102 | 103 | // 设备信息探测,用于检测当前页面的设备信息。 104 | 105 | deviceInfoDetect: function() { 106 | var that = this; 107 | 108 | if(that.platform === 'h5') { 109 | return; 110 | } 111 | 112 | that.pushBack('bridge:', 'client_info', { 113 | successCallback: function(client_info) { 114 | client_info = JSON.parse(client_info || '{}'); 115 | client_info.version = that.client.version; //hack, android 3.6.0以下返回的字段缺失 116 | that.client = client_info; //ttid, push_token, device_id, client_version, client_type 117 | } 118 | }); 119 | }, 120 | 121 | // 网络类型探测,用于检测当前环境的页面类型,如果是生存在浏览器中, 122 | // 则取浏览器的`navigator.connection`属性,如果生存在客户端中,则 123 | // 根据协议接口进行获取,并作为桥的属性后续可被访问到。 124 | 125 | // ``` 126 | // { 127 | // "type" : "2", 128 | // "mapping": { 129 | // "UNKNOWN": 0, 130 | // "ETHERNET": 1, 131 | // "WIFI": 2, 132 | // "CELL_2G": 3, 133 | // "CELL_3G": 4, 134 | // "CELL_4G": 5, 135 | // "CELL": 6, 136 | // "NONE": 7, 137 | // } 138 | // } 139 | // ``` 140 | 141 | connectionInfoDetect: function() { 142 | var that = this; 143 | 144 | if(that.platform === 'h5') { 145 | that.connection = navigator.connection || {}; 146 | return; 147 | } 148 | 149 | that.pushBack('bridge:', 'networktype', { 150 | successCallback: function(conn) { 151 | that.connection.type = conn; 152 | } 153 | }); 154 | }, 155 | 156 | sendURI: function(uri, newProxy) { 157 | var proxy = this.mClientProxy; 158 | 159 | if (newProxy) { 160 | this.buildProxy(uri); 161 | return this; 162 | } 163 | 164 | if (proxy || (proxy = document.querySelector('#J_MClientProxy'))) { 165 | proxy.attr('src', uri); 166 | } else { 167 | proxy = this.buildProxy(uri); 168 | } 169 | 170 | this.mClientProxy = proxy; 171 | 172 | return this; 173 | }, 174 | 175 | buildRandom: function() { 176 | var that = this; 177 | var random = new Date().getTime() + '_' + parseInt(Math.random() * 1000000); 178 | 179 | return random; 180 | }, 181 | 182 | buildProxy: function(uri) { 183 | var that = this; 184 | var guid = that.buildRandom(); 185 | var iframeString = ''; 186 | var proxy = $(iframeString); 187 | 188 | $('body').append(proxy); 189 | return proxy; 190 | }, 191 | 192 | buildCallback: function(fn) { 193 | var that = this; 194 | var guid = that.buildRandom(); 195 | var callbackName = 'Bridge_Callbacks_' + guid; 196 | 197 | window[callbackName] = (function(cb, callbackName) { 198 | return function() { 199 | cb.apply(this, arguments); 200 | delete window[callbackName]; 201 | }; 202 | })(fn, callbackName); 203 | 204 | return callbackName; 205 | }, 206 | 207 | // 主要API,用于页面同客户端的协议回调,`protocol`协议默认为`native:`, 208 | // 可以省略不传,`host`为协议约定的主体,如`app/beep`,`data`为调用时 209 | // 传递给客户端的所需数据对象,对象内可包含`successCallback`和`failCallback`, 210 | // 鉴于iOS回调的实现机制,`newProxy`参数是为了避免在iOS下多次连续回调 211 | // 造成的消息丢失,当设为`true`时,每次均会创建一个新的`iframe`进行发送。 212 | 213 | pushBack: function(protocol, host, data, newProxy) { 214 | var that = this; 215 | var uri = (protocol || 'native:') + '//' + host + '?params='; 216 | var callbackName; 217 | var args = [].slice.call(arguments); 218 | 219 | if(typeof(protocol) !== 'string' || typeof(host) !== 'string') { 220 | protocol = 'native:'; 221 | host = args[0]; 222 | data = args[1]; 223 | newProxy = args[2]; 224 | uri = (protocol || 'native:') + '//' + host + '?params='; 225 | } 226 | 227 | if(that.platform === 'h5') { 228 | return; 229 | } 230 | 231 | data = data || {}; 232 | 233 | for(var i in data) { 234 | if(data.hasOwnProperty(i)) { 235 | if(typeof(data[i]) === 'function') { 236 | callbackName = that.buildCallback(data[i]); 237 | data[i] = callbackName; 238 | } 239 | 240 | if(typeof(data[i]) === 'object' && data[i].hasOwnProperty('length')) { 241 | for(var j = 0; j < data[i].length; j++) { 242 | if(typeof(data[i][j]) === 'function') { 243 | callbackName = that.buildCallback(data[i][j]); 244 | data[i][j] = callbackName; 245 | } 246 | } 247 | } 248 | 249 | if(i !== i.replace(/([A-Z])/g,"_$1").toLowerCase()) { 250 | data[i.replace(/([A-Z])/g,"_$1").toLowerCase()] = data[i]; 251 | delete data[i]; 252 | } 253 | } 254 | } 255 | 256 | if(that.platform === 'android' && that.client.version < 360) { 257 | uri += encodeURIComponent(JSON.stringify(data)); 258 | 259 | if(uri.match(/^native:\/\//igm)) { 260 | that.bridge && that.bridge['startNativeService'] && that.bridge['startNativeService'](uri); 261 | } 262 | 263 | if(uri.match(/^page:\/\//igm)) { 264 | that.bridge && that.bridge['startNativePage'] && that.bridge['startNativePage'](uri); 265 | } 266 | 267 | if(uri.match(/^bridge:\/\//igm)) { 268 | that.bridge && that.bridge['startNativeBridge'] && that.bridge['startNativeBridge'](uri); 269 | } 270 | 271 | return; 272 | } 273 | 274 | if(that.platform === 'android' && that.client.version >= 360) { 275 | uri += encodeURIComponent(JSON.stringify(data)); 276 | prompt('alitrip-android://' + uri); 277 | return; 278 | } 279 | 280 | if(that.platform === 'ios') { 281 | uri += encodeURIComponent(JSON.stringify(data)); 282 | messageQueue.push(uri); 283 | return; 284 | } 285 | 286 | uri += encodeURIComponent(JSON.stringify(data)); 287 | that.sendURI(uri, newProxy); 288 | }, 289 | 290 | getRequestParam: function(uri, param) { 291 | var value; 292 | uri = uri || window.location.href; 293 | value = uri.match(new RegExp('[\?\&]' + param + '=([^\&]*)(\&?)', 'i')); 294 | return value ? decodeURIComponent(value[1]) : value; 295 | }, 296 | 297 | getRequestParams: function(uri) { 298 | var search = location.search.substring(1); 299 | uri = uri || window.location.href; 300 | return search ? JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g,'":"') + '"}', function(key, value) { 301 | return key==="" ? value : decodeURIComponent(value); 302 | }) : {}; 303 | }, 304 | 305 | // 获取URL和客户端传递的所有参数,会优先获取URL中的参数 306 | 307 | getParams: function() { 308 | var that = this; 309 | var params = that.getRequestParams(); 310 | var client = that.client; 311 | 312 | for(var i in client) { 313 | if(client.hasOwnProperty(i) && params.hasOwnProperty(i)) { 314 | params[i] = client[i]; 315 | } 316 | } 317 | 318 | return params; 319 | }, 320 | 321 | notification: { 322 | 323 | // `alert`弹框,对于浏览器的生存环境,仅`message`参数有效,但对于客户端类 324 | // 生存环境,则可以接收回调,以及弹出框的标题和按钮文案,回调中客户端会回 325 | // 传用户相应点击的按钮的索引值。 326 | 327 | alert: function(message, alertCallback, title, buttonLabels) { 328 | var that = this; 329 | var callback = function(ret) { 330 | var buttonIndex = ret.buttonIndex; 331 | 332 | if(Object.prototype.toString.call(alertCallback) === '[object Array]') { 333 | alertCallback[buttonIndex] && alertCallback[buttonIndex].apply(this, arguments); 334 | return; 335 | } 336 | 337 | alertCallback && alertCallback(); 338 | }; 339 | 340 | if(that.superthat.platform === 'h5') { 341 | alert.apply(null, arguments); 342 | return; 343 | } 344 | 345 | if(typeof(buttonLabels) === 'string') { 346 | buttonLabels = [buttonLabels]; 347 | } 348 | 349 | that.superthat.pushBack('bridge:', 'alert', { 350 | message: message, 351 | successCallback: callback, 352 | title: title, 353 | buttonNames: buttonLabels 354 | }); 355 | }, 356 | 357 | // `confirm`确认对话框,对于浏览器的生存环境,仅`message`和`confirmCallback` 358 | // 参数有效,但对于客户端类生存环境,则可以接收回调,以及弹出框的标题和按钮 359 | // 文案,回调中客户端会回传用户相应点击的按钮的索引值。 360 | 361 | confirm: function(message, confirmCallback, title, buttonLabels) { 362 | var that = this; 363 | 364 | if(that.superthat.platform === 'h5') { 365 | if(confirm.apply(null, arguments)) { 366 | confirmCallback && confirmCallback(); 367 | } 368 | 369 | return; 370 | } 371 | 372 | if(typeof(buttonLabels) === 'string') { 373 | buttonLabels = [buttonLabels]; 374 | } 375 | 376 | that.superthat.pushBack('bridge:', 'confirm', { 377 | message: message, 378 | title: title, 379 | successCallback: confirmCallback, 380 | buttonNames: buttonLabels 381 | }); 382 | }, 383 | 384 | // `prompt`提示对话框,对于浏览器的生存环境,仅`message`、`value`和 385 | // `promptCallback`参数有效,但对于客户端类生存环境,则可以接收回调, 386 | // 以及弹出框的标题和按钮文案,回调中客户端会回传用户相应点击的按钮 387 | // 的索引值。 388 | 389 | prompt: function(message, value, promptCallback, title, buttonLabels) { 390 | var that = this; 391 | 392 | if(that.superthat.platform === 'h5') { 393 | prompt.apply(null, arguments); 394 | return; 395 | } 396 | 397 | if(typeof(buttonLabels) === 'string') { 398 | buttonLabels = [buttonLabels]; 399 | } 400 | 401 | that.superthat.pushBack('bridge:', 'prompt', { 402 | message: message, 403 | value: value, 404 | successCallback: promptCallback, 405 | title: title, 406 | buttonNames: buttonLabels 407 | }); 408 | }, 409 | 410 | // 弱提示,用于调用客户端的弱提示进行些许文案的提示。 411 | 412 | toast: function(message, milliseconds) { 413 | var that = this; 414 | 415 | if(that.superthat.platform === 'h5') { 416 | return; 417 | } 418 | 419 | that.superthat.pushBack('bridge:', 'toast', { 420 | message: message, 421 | milliseconds: milliseconds 422 | }); 423 | }, 424 | 425 | // 蜂鸣,调用客户端的蜂鸣器按照指定的次数进行蜂鸣。 426 | 427 | beep: function(times) { 428 | var that = this; 429 | 430 | that.superthat.pushBack('bridge:', 'beep', { 431 | times: times 432 | }); 433 | }, 434 | 435 | // 震动,进行设备的震动,持续指定的毫秒时长。 436 | 437 | vibrate: function(milliseconds) { 438 | var that = this; 439 | 440 | that.superthat.pushBack('bridge:', 'vibrate', { 441 | milliseconds: milliseconds 442 | }); 443 | } 444 | }, 445 | 446 | device: {}, 447 | 448 | client: {}, 449 | 450 | connection: {}, 451 | 452 | // 新开页面打开页面,浏览器生存环境下打开新的标签页,客户端生存环境 453 | // 下则调用系统浏览器打开相应新的页面。 454 | 455 | openBrowser: function(url) { 456 | var that = this; 457 | 458 | if(that.platform === 'h5') { 459 | window.open(url); 460 | return; 461 | } 462 | 463 | that.pushBack('bridge:', 'open_system_browser', { 464 | url: url 465 | }); 466 | }, 467 | 468 | // 针对iOS,跳转并打开AppStore的相应地址,对于浏览器而言则在新标签 469 | // 中打开对应的应用地址。 470 | 471 | openAppStore: function(url) { 472 | var that = this; 473 | 474 | if(that.platform === 'h5') { 475 | window.open(url); 476 | return; 477 | } 478 | 479 | that.pushBack('bridge:', 'open_app_store', { 480 | url: url 481 | }); 482 | }, 483 | 484 | // 客户端生存环境下设置WebView顶部的标题显示文案,对于浏览器则直接 485 | // 更改页面的标题文案。子标题为可选参数,只适用在客户端内嵌的情况。 486 | 487 | setTitle: function(title, subtitle) { 488 | var that = this; 489 | 490 | if(that.platform === 'h5') { 491 | document.title = title; 492 | return; 493 | } 494 | 495 | that.pushBack('bridge:', 'set_webview_title', { 496 | title: title, 497 | subtitle: subtitle 498 | }); 499 | }, 500 | 501 | // 跳转到相应的客户端页面,如果有额外的数据则通过`ext`参数传递给客户端。 502 | 503 | open: function(pagename, ext, successCallback, naviType, animeType) { 504 | var that = this; 505 | var params = { 506 | page_name: pagename, 507 | data: ext || {}, 508 | successCallback: successCallback, 509 | naviType: naviType || 0, 510 | animeType: typeof(animeType) !== 'undefined' ? animeType : 4 511 | }; 512 | 513 | that.pushBack('page:', 'goto', params); 514 | }, 515 | 516 | // 返回上一页,对于浏览器而言,直接调用`history.back`返回上一个历史记录, 517 | // 对于客户端而言则通过协议调用返回上一个WebView打开的相应页面。 518 | 519 | back: function(pagename, ext) { 520 | var that = this; 521 | 522 | if(that.platform === 'h5') { 523 | history.back(); 524 | return; 525 | } 526 | 527 | if(pagename) { 528 | that.pushBack('bridge:', 'back', ext); 529 | return; 530 | } 531 | 532 | that.pushBack('bridge:', 'back', ext); 533 | }, 534 | 535 | // 关闭当前的客户端页面,如果有额外的数据则通过`ext`参数传递给客户端。 536 | 537 | close: function(ext) { 538 | var that = this; 539 | 540 | if(that.platform === 'h5') { 541 | window.close(); 542 | return; 543 | } 544 | 545 | that.pushBack('page:', 'close', ext); 546 | }, 547 | 548 | // 客户端极简支付,如果有额外的数据则通过`ext`参数传递给客户端。 549 | 550 | minipay: function(alipayId, nextUrl, ext, successCallback, failCallback) { 551 | var that = this; 552 | var payResult; 553 | var params = { 554 | alipay_id: alipayId, 555 | data: ext || {}, 556 | failCallback: failCallback, 557 | successCallback: function(ret) { 558 | try { 559 | payResult = JSON.parse(ret); 560 | } catch(e) { 561 | // iOS客户端3.5版本返回的JSON格式有误,解析会出问题 562 | if(ret.match(/"ResultStatus":"9000"/igm)) { 563 | payResult = { 564 | ResultStatus: '9000' 565 | }; 566 | } 567 | } 568 | 569 | // iOS SDK返回的字段名首字母是大写,暂时兼容处理 570 | if(that.platform === 'ios') { 571 | payResult.resultStatus = payResult.ResultStatus; 572 | } 573 | 574 | if(payResult.resultStatus === '9000') { 575 | successCallback && successCallback(payResult); 576 | return; 577 | } 578 | 579 | failCallback && failCallback(payResult); 580 | } 581 | }; 582 | 583 | if(that.platform === 'h5' && nextUrl) { 584 | window.location.href = nextUrl; 585 | return; 586 | } 587 | 588 | if(that.client.version < 350 && nextUrl) { 589 | window.location.href = nextUrl; 590 | return; 591 | } 592 | 593 | that.pushBack('bridge:', 'minipay', params); 594 | } 595 | }; 596 | 597 | this.Bridge = Bridge; 598 | 599 | }).call(this); 600 | 601 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | index.js 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
    15 | 16 |
  • 17 |
    18 |

    index.js

    19 |
    20 |
  • 21 | 22 | 23 | 24 |
  • 25 |
    26 | 27 |
    28 | 29 |
    30 | 31 |
    32 | 33 |
    /*
      34 |  * A Bridge Between H5 And Native.
      35 |  * By: Guokai @benbencc
      36 |  * Created: 2013-7-24
      37 |  * */
    38 | 39 |
  • 40 | 41 | 42 |
  • 43 |
    44 | 45 |
    46 | 47 |
    48 |

    面向Android和iOS的JS调用原理:

    49 | 50 |
    51 | 52 |
  • 53 | 54 | 55 |
  • 56 |
    57 | 58 |
    59 | 60 |
    61 |

    Android 通过桥(客户端暴露在WebView全局对象下的一个对象,内挂各种API方法) 62 | 的方式进行调用,如window['Android_Bridge']['method'](JSON)。iOS通过自定 63 | 义Scheme(如native://method?data=JSON)方式调用。需要回调的接口需要将函 64 | 数名称在调用时一并传给客户端,同时将回调函数通过唯一名称挂在全局,待客户端 65 | 执行回调后移除该全局函数。

    66 | 67 |
    68 | 69 |
      70 | (function() {
    71 | 72 |
  • 73 | 74 | 75 |
  • 76 |
    77 | 78 |
    79 | 80 |
    81 |

    Bridge是为H5和客户端交互通讯而产生的一个中间件,即一个JavaScript的SDK。负责 82 | 处理H5和客户端的方法调用、通信及H5页面自身的降级处理(非内嵌在客户端的情况)。

    83 | 84 |
    85 | 86 |
      87 |     var Bridge = function() {
      88 |         this.init.apply(this, arguments);
      89 |     };
      90 | 
      91 |     Bridge.prototype = {
      92 |         version: '0.0.1',
    93 | 94 |
  • 95 | 96 | 97 |
  • 98 |
    99 | 100 |
    101 | 102 |
    103 |

    桥初始化,初始化过程中会:主动探测UA,判断页面所属的生存环境,如果是在客户端中, 104 | 会探测获取客户端的平台类型和版本号,初始化消息队列,并主动探测网络类型,并同所属 105 | 环境主动握手交换双方所需的额外基础信息。

    106 | 107 |
    108 | 109 |
     110 |         init: function(bridgeName) {
     111 |             var that = this;
     112 | 
     113 |             that.platform = 'h5';
     114 |             that.bridgeName = bridgeName || 'ali_trip_webview_bridge';
     115 |             that.bridge = window[that.bridgeName];
     116 |             that.userAgentDetect.apply(this, arguments);
     117 |             that.messageQueueInit();
     118 |             that.deviceInfoDetect();
     119 |             that.connectionInfoDetect();
     120 |             that.handShake.apply(this, arguments);
     121 | 
     122 |             that.notification.superthat = that;
     123 |         },
    124 | 125 |
  • 126 | 127 | 128 |
  • 129 |
    130 | 131 |
    132 | 133 |
    134 |

    消息队列初始化,针对iOS的实现机制单独做的处理,iOS会主动轮询 135 | 消息队列中的操作,并批量取回处理。

    136 | 137 |
    138 | 139 |
     140 |         messageQueueInit: function() {
     141 |             var that = this;
     142 | 
     143 |             if(that.platform === 'ios') {
     144 |                 window.messageQueue = [];
     145 |                 window.messageQueueFetch = function() {
     146 |                     var response;
     147 |                     response = window.messageQueue.length ? JSON.stringify(window.messageQueue) : '';
     148 |                     window.messageQueue = [];
     149 | 
     150 |                     if(response) {
     151 |                         return response;
     152 |                     }
     153 |                 };
     154 |             }
     155 |         },
    156 | 157 |
  • 158 | 159 | 160 |
  • 161 |
    162 | 163 |
    164 | 165 |
    166 |

    握手协议,用于页面同所属生存环境在首次初始化时进行基础信息交换。

    167 | 168 |
    169 | 170 |
     171 |         handShake: function(callback) {
     172 |             var that = this;
     173 | 
     174 |             that.pushBack('bridge:', 'ready', {
     175 |                 data: {},
     176 |                 successCallback: function() {
     177 |                     callback && callback();
     178 |                 }
     179 |             });
     180 |         },
    181 | 182 |
  • 183 | 184 | 185 |
  • 186 |
    187 | 188 |
    189 | 190 |
    191 |

    UA探测,用于检测当前页面是否生存在淘宝旅行客户端内,如果在客户端内 192 | 则继续获取客户端的平台类型及版本号用于后续的操作。

    193 | 194 |
    195 | 196 |
  • 197 | 198 | 199 |
  • 200 |
    201 | 202 |
    203 | 204 |
    205 |

    User-Agent Format:

    206 | 207 |
    208 | 209 |
  • 210 | 211 | 212 |
  • 213 |
    214 | 215 |
    216 | 217 |
    218 |

    Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_1_2 like Mac OS X; zh-cn; AliTrip/2.8.0) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile Safari/528.16 219 | Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; MI 2 Build/JRO03L; AliTrip/2.8.0) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile Safari/528.16

    220 | 221 |
    222 | 223 |
  • 224 | 225 | 226 |
  • 227 |
    228 | 229 |
    230 | 231 |
    232 |

    References:

    233 | 234 |
    235 | 236 |
  • 237 | 238 | 239 |
  • 240 |
    241 | 242 |
    243 | 244 |
    245 |

    http://www.ietf.org/rfc/rfc2616.txt 246 | http://www.useragentstring.com/

    247 | 248 |
    249 | 250 |
     251 |         userAgentDetect: function() {
     252 |             var that = this;
     253 |             var ua = navigator.userAgent;
     254 |             var match = ua.match(/AliTrip[\s\/][\d\.]+/igm);
     255 | 
     256 |             if(match) {
     257 |                 that.platform = ua.match(/(iPad|iPhone|iPod)/igm) ? 'ios' : 'android';
     258 |                 that.client.version = parseInt(match[0].match(/[\d\.]+/igm)[0].split('.').join(''));
     259 |             }
     260 |         },
    261 | 262 |
  • 263 | 264 | 265 |
  • 266 |
    267 | 268 |
    269 | 270 |
    271 |

    设备信息探测,用于检测当前页面的设备信息。

    272 | 273 |
    274 | 275 |
     276 |         deviceInfoDetect: function() {
     277 |             var that = this;
     278 | 
     279 |             if(that.platform === 'h5') {
     280 |                 return;
     281 |             }
     282 | 
     283 |             that.pushBack('bridge:', 'client_info', {
     284 |                 successCallback: function(client_info) {
     285 |                     client_info = JSON.parse(client_info || '{}');
     286 |                     client_info.version = that.client.version; //hack, android 3.6.0以下返回的字段缺失
     287 |                     that.client = client_info; //ttid, push_token, device_id, client_version, client_type
     288 |                 }
     289 |             });
     290 |         },
    291 | 292 |
  • 293 | 294 | 295 |
  • 296 |
    297 | 298 |
    299 | 300 |
    301 |

    网络类型探测,用于检测当前环境的页面类型,如果是生存在浏览器中, 302 | 则取浏览器的navigator.connection属性,如果生存在客户端中,则 303 | 根据协议接口进行获取,并作为桥的属性后续可被访问到。

    304 | 305 |
    306 | 307 |
  • 308 | 309 | 310 |
  • 311 |
    312 | 313 |
    314 | 315 |
    316 |
       {
     317 |        "type" : "2",
     318 |        "mapping": {
     319 |            "UNKNOWN": 0,
     320 |            "ETHERNET": 1,
     321 |            "WIFI": 2,
     322 |            "CELL_2G": 3,
     323 |            "CELL_3G": 4,
     324 |            "CELL_4G": 5,
     325 |            "CELL": 6,
     326 |            "NONE": 7,
     327 |        }
     328 |    }
     329 | 
    330 |
    331 | 332 |
     333 |         connectionInfoDetect: function() {
     334 |             var that = this;
     335 | 
     336 |             if(that.platform === 'h5') {
     337 |                 that.connection = navigator.connection || {};
     338 |                 return;
     339 |             }
     340 | 
     341 |             that.pushBack('bridge:', 'networktype', {
     342 |                 successCallback: function(conn) {
     343 |                     that.connection.type = conn;
     344 |                 }
     345 |             });
     346 |         },
     347 | 
     348 |         sendURI: function(uri, newProxy) {
     349 |             var proxy = this.mClientProxy;
     350 |             
     351 |             if (newProxy) {
     352 |                 this.buildProxy(uri);
     353 |                 return this;
     354 |             }
     355 | 
     356 |             if (proxy || (proxy = document.querySelector('#J_MClientProxy'))) {
     357 |                 proxy.attr('src', uri);
     358 |             } else {
     359 |                 proxy = this.buildProxy(uri);
     360 |             }
     361 | 
     362 |             this.mClientProxy = proxy;
     363 |             
     364 |             return this;
     365 |         },
     366 | 
     367 |         buildRandom: function() {
     368 |             var that = this;
     369 |             var random = new Date().getTime() + '_' + parseInt(Math.random() * 1000000);
     370 | 
     371 |             return random;
     372 |         },
     373 |         
     374 |         buildProxy: function(uri) {
     375 |             var that = this;
     376 |             var guid = that.buildRandom();
     377 |             var iframeString = '<iframe id="J_MClientProxy_' + guid + '" class="hidden mclient-proxy" style="width:0;height:0;opacity:0;display:none;" src="' + uri + '"></iframe>';
     378 |             var proxy = $(iframeString);
     379 | 
     380 |             $('body').append(proxy);
     381 |             return proxy;
     382 |         },
     383 | 
     384 |         buildCallback: function(fn) {
     385 |             var that = this;
     386 |             var guid = that.buildRandom();
     387 |             var callbackName = 'Bridge_Callbacks_' + guid;
     388 | 
     389 |             window[callbackName] = (function(cb, callbackName) {
     390 |                 return function() {
     391 |                     cb.apply(this, arguments);
     392 |                     delete window[callbackName];
     393 |                 };
     394 |             })(fn, callbackName);
     395 | 
     396 |             return callbackName;
     397 |         },
    398 | 399 |
  • 400 | 401 | 402 |
  • 403 |
    404 | 405 |
    406 | 407 |
    408 |

    主要API,用于页面同客户端的协议回调,protocol协议默认为native:, 409 | 可以省略不传,host为协议约定的主体,如app/beepdata为调用时 410 | 传递给客户端的所需数据对象,对象内可包含successCallbackfailCallback, 411 | 鉴于iOS回调的实现机制,newProxy参数是为了避免在iOS下多次连续回调 412 | 造成的消息丢失,当设为true时,每次均会创建一个新的iframe进行发送。

    413 | 414 |
    415 | 416 |
     417 |         pushBack: function(protocol, host, data, newProxy) {
     418 |             var that = this;
     419 |             var uri = (protocol || 'native:') + '//' + host + '?params=';
     420 |             var callbackName;
     421 |             var args = [].slice.call(arguments);
     422 | 
     423 |             if(typeof(protocol) !== 'string' || typeof(host) !== 'string') {
     424 |                 protocol = 'native:';
     425 |                 host = args[0];
     426 |                 data = args[1];
     427 |                 newProxy = args[2];
     428 |                 uri = (protocol || 'native:') + '//' + host + '?params=';
     429 |             }
     430 | 
     431 |             if(that.platform === 'h5') {
     432 |                 return;
     433 |             }
     434 | 
     435 |             data = data || {};
     436 | 
     437 |             for(var i in data) {
     438 |                 if(data.hasOwnProperty(i)) {
     439 |                     if(typeof(data[i]) === 'function') {
     440 |                         callbackName = that.buildCallback(data[i]);
     441 |                         data[i] = callbackName;
     442 |                     }
     443 | 
     444 |                     if(typeof(data[i]) === 'object' && data[i].hasOwnProperty('length')) {
     445 |                         for(var j = 0; j < data[i].length; j++) {
     446 |                             if(typeof(data[i][j]) === 'function') {
     447 |                                 callbackName = that.buildCallback(data[i][j]);
     448 |                                 data[i][j] = callbackName;
     449 |                             }
     450 |                         }
     451 |                     }
     452 | 
     453 |                     if(i !== i.replace(/([A-Z])/g,"_$1").toLowerCase()) {
     454 |                         data[i.replace(/([A-Z])/g,"_$1").toLowerCase()] = data[i];
     455 |                         delete data[i];
     456 |                     }
     457 |                 }
     458 |             }
     459 | 
     460 |             if(that.platform === 'android' && that.client.version < 360) {
     461 |                 uri += encodeURIComponent(JSON.stringify(data));
     462 | 
     463 |                 if(uri.match(/^native:\/\//igm)) {
     464 |                     that.bridge && that.bridge['startNativeService'] && that.bridge['startNativeService'](uri);
     465 |                 }
     466 | 
     467 |                 if(uri.match(/^page:\/\//igm))  {
     468 |                     that.bridge && that.bridge['startNativePage'] && that.bridge['startNativePage'](uri);
     469 |                 }
     470 | 
     471 |                 if(uri.match(/^bridge:\/\//igm)) {
     472 |                     that.bridge && that.bridge['startNativeBridge'] && that.bridge['startNativeBridge'](uri);
     473 |                 }
     474 | 
     475 |                 return;
     476 |             }
     477 | 
     478 |             if(that.platform === 'android' && that.client.version >= 360) {
     479 |                 uri += encodeURIComponent(JSON.stringify(data));
     480 |                 prompt('alitrip-android://' + uri);
     481 |                 return;
     482 |             }
     483 | 
     484 |             if(that.platform === 'ios') {
     485 |                 uri += encodeURIComponent(JSON.stringify(data));
     486 |                 messageQueue.push(uri);
     487 |                 return;
     488 |             }
     489 | 
     490 |             uri += encodeURIComponent(JSON.stringify(data));
     491 |             that.sendURI(uri, newProxy);
     492 |         },
     493 | 
     494 |         getRequestParam: function(uri, param) {
     495 |             var value;
     496 |             uri = uri || window.location.href;
     497 |             value = uri.match(new RegExp('[\?\&]' + param + '=([^\&]*)(\&?)', 'i'));
     498 |             return value ? decodeURIComponent(value[1]) : value;
     499 |         },
     500 | 
     501 |         getRequestParams: function(uri) {
     502 |             var search = location.search.substring(1);
     503 |             uri = uri || window.location.href;
     504 |             return search ? JSON.parse('{"' + search.replace(/&/g, '","').replace(/=/g,'":"') + '"}', function(key, value) {
     505 |                 return key==="" ? value : decodeURIComponent(value);
     506 |             }) : {};
     507 |         },
    508 | 509 |
  • 510 | 511 | 512 |
  • 513 |
    514 | 515 |
    516 | 517 |
    518 |

    获取URL和客户端传递的所有参数,会优先获取URL中的参数

    519 | 520 |
    521 | 522 |
     523 |         getParams: function() {
     524 |             var that = this;
     525 |             var params = that.getRequestParams();
     526 |             var client = that.client;
     527 | 
     528 |             for(var i in client) {
     529 |                 if(client.hasOwnProperty(i) && params.hasOwnProperty(i)) {
     530 |                     params[i] = client[i];
     531 |                 }
     532 |             }
     533 | 
     534 |             return params;
     535 |         },
     536 | 
     537 |         notification: {
    538 | 539 |
  • 540 | 541 | 542 |
  • 543 |
    544 | 545 |
    546 | 547 |
    548 |

    alert弹框,对于浏览器的生存环境,仅message参数有效,但对于客户端类 549 | 生存环境,则可以接收回调,以及弹出框的标题和按钮文案,回调中客户端会回 550 | 传用户相应点击的按钮的索引值。

    551 | 552 |
    553 | 554 |
     555 |             alert: function(message, alertCallback, title, buttonLabels) {
     556 |                 var that = this;
     557 |                 var callback = function(ret) {
     558 |                     var buttonIndex = ret.buttonIndex;
     559 | 
     560 |                     if(Object.prototype.toString.call(alertCallback) === '[object Array]') {
     561 |                         alertCallback[buttonIndex] && alertCallback[buttonIndex].apply(this, arguments);
     562 |                         return;
     563 |                     }
     564 | 
     565 |                     alertCallback && alertCallback();
     566 |                 };
     567 | 
     568 |                 if(that.superthat.platform === 'h5') {
     569 |                     alert.apply(null, arguments);
     570 |                     return;
     571 |                 }
     572 | 
     573 |                 if(typeof(buttonLabels) === 'string') {
     574 |                     buttonLabels = [buttonLabels];
     575 |                 }
     576 | 
     577 |                 that.superthat.pushBack('bridge:', 'alert', {
     578 |                     message: message,
     579 |                     successCallback: callback,
     580 |                     title: title,
     581 |                     buttonNames: buttonLabels
     582 |                 });
     583 |             },
    584 | 585 |
  • 586 | 587 | 588 |
  • 589 |
    590 | 591 |
    592 | 593 |
    594 |

    confirm确认对话框,对于浏览器的生存环境,仅messageconfirmCallback 595 | 参数有效,但对于客户端类生存环境,则可以接收回调,以及弹出框的标题和按钮 596 | 文案,回调中客户端会回传用户相应点击的按钮的索引值。

    597 | 598 |
    599 | 600 |
     601 |             confirm: function(message, confirmCallback, title, buttonLabels) {
     602 |                 var that = this;
     603 | 
     604 |                 if(that.superthat.platform === 'h5') {
     605 |                     if(confirm.apply(null, arguments)) {
     606 |                         confirmCallback && confirmCallback();
     607 |                     }
     608 | 
     609 |                     return;
     610 |                 }
     611 | 
     612 |                 if(typeof(buttonLabels) === 'string') {
     613 |                     buttonLabels = [buttonLabels];
     614 |                 }
     615 | 
     616 |                 that.superthat.pushBack('bridge:', 'confirm', {
     617 |                     message: message,
     618 |                     title: title,
     619 |                     successCallback: confirmCallback,
     620 |                     buttonNames: buttonLabels
     621 |                 });
     622 |             },
    623 | 624 |
  • 625 | 626 | 627 |
  • 628 |
    629 | 630 |
    631 | 632 |
    633 |

    prompt提示对话框,对于浏览器的生存环境,仅messagevalue和 634 | promptCallback参数有效,但对于客户端类生存环境,则可以接收回调, 635 | 以及弹出框的标题和按钮文案,回调中客户端会回传用户相应点击的按钮 636 | 的索引值。

    637 | 638 |
    639 | 640 |
     641 |             prompt: function(message, value, promptCallback, title, buttonLabels) {
     642 |                 var that = this;
     643 | 
     644 |                 if(that.superthat.platform === 'h5') {
     645 |                     prompt.apply(null, arguments);
     646 |                     return;
     647 |                 }
     648 | 
     649 |                 if(typeof(buttonLabels) === 'string') {
     650 |                     buttonLabels = [buttonLabels];
     651 |                 }
     652 | 
     653 |                 that.superthat.pushBack('bridge:', 'prompt', {
     654 |                     message: message,
     655 |                     value: value,
     656 |                     successCallback: promptCallback,
     657 |                     title: title,
     658 |                     buttonNames: buttonLabels
     659 |                 });
     660 |             },
    661 | 662 |
  • 663 | 664 | 665 |
  • 666 |
    667 | 668 |
    669 | 670 |
    671 |

    弱提示,用于调用客户端的弱提示进行些许文案的提示。

    672 | 673 |
    674 | 675 |
     676 |             toast: function(message, milliseconds) {
     677 |                 var that = this;
     678 | 
     679 |                 if(that.superthat.platform === 'h5') {
     680 |                     return;
     681 |                 }
     682 | 
     683 |                 that.superthat.pushBack('bridge:', 'toast', {
     684 |                     message: message,
     685 |                     milliseconds: milliseconds
     686 |                 });
     687 |             },
    688 | 689 |
  • 690 | 691 | 692 |
  • 693 |
    694 | 695 |
    696 | 697 |
    698 |

    蜂鸣,调用客户端的蜂鸣器按照指定的次数进行蜂鸣。

    699 | 700 |
    701 | 702 |
     703 |             beep: function(times) {
     704 |                 var that = this;
     705 | 
     706 |                 that.superthat.pushBack('bridge:', 'beep', {
     707 |                     times: times
     708 |                 });
     709 |             },
    710 | 711 |
  • 712 | 713 | 714 |
  • 715 |
    716 | 717 |
    718 | 719 |
    720 |

    震动,进行设备的震动,持续指定的毫秒时长。

    721 | 722 |
    723 | 724 |
     725 |             vibrate: function(milliseconds) {
     726 |                 var that = this;
     727 | 
     728 |                 that.superthat.pushBack('bridge:', 'vibrate', {
     729 |                     milliseconds: milliseconds
     730 |                 });
     731 |             }
     732 |         },
     733 | 
     734 |         device: {},
     735 | 
     736 |         client: {},
     737 | 
     738 |         connection: {},
    739 | 740 |
  • 741 | 742 | 743 |
  • 744 |
    745 | 746 |
    747 | 748 |
    749 |

    新开页面打开页面,浏览器生存环境下打开新的标签页,客户端生存环境 750 | 下则调用系统浏览器打开相应新的页面。

    751 | 752 |
    753 | 754 |
     755 |         openBrowser: function(url) {
     756 |             var that = this;
     757 | 
     758 |             if(that.platform === 'h5') {
     759 |                 window.open(url);
     760 |                 return;
     761 |             }
     762 | 
     763 |             that.pushBack('bridge:', 'open_system_browser', {
     764 |                 url: url
     765 |             });
     766 |         },
    767 | 768 |
  • 769 | 770 | 771 |
  • 772 |
    773 | 774 |
    775 | 776 |
    777 |

    针对iOS,跳转并打开AppStore的相应地址,对于浏览器而言则在新标签 778 | 中打开对应的应用地址。

    779 | 780 |
    781 | 782 |
     783 |         openAppStore: function(url) {
     784 |             var that = this;
     785 | 
     786 |             if(that.platform === 'h5') {
     787 |                 window.open(url);
     788 |                 return;
     789 |             }
     790 | 
     791 |             that.pushBack('bridge:', 'open_app_store', {
     792 |                 url: url
     793 |             });
     794 |         },
    795 | 796 |
  • 797 | 798 | 799 |
  • 800 |
    801 | 802 |
    803 | 804 |
    805 |

    客户端生存环境下设置WebView顶部的标题显示文案,对于浏览器则直接 806 | 更改页面的标题文案。子标题为可选参数,只适用在客户端内嵌的情况。

    807 | 808 |
    809 | 810 |
     811 |         setTitle: function(title, subtitle) {
     812 |             var that = this;
     813 | 
     814 |             if(that.platform === 'h5') {
     815 |                 document.title = title;
     816 |                 return;
     817 |             }
     818 | 
     819 |             that.pushBack('bridge:', 'set_webview_title', {
     820 |                 title: title,
     821 |                 subtitle: subtitle
     822 |             });
     823 |         },
    824 | 825 |
  • 826 | 827 | 828 |
  • 829 |
    830 | 831 |
    832 | 833 |
    834 |

    跳转到相应的客户端页面,如果有额外的数据则通过ext参数传递给客户端。

    835 | 836 |
    837 | 838 |
     839 |         open: function(pagename, ext, successCallback, naviType, animeType) {
     840 |             var that = this;
     841 |             var params = {
     842 |                 page_name: pagename,
     843 |                 data: ext || {},
     844 |                 successCallback: successCallback,
     845 |                 naviType: naviType || 0,
     846 |                 animeType: typeof(animeType) !== 'undefined' ? animeType : 4
     847 |             };
     848 | 
     849 |             that.pushBack('page:', 'goto', params);
     850 |         },
    851 | 852 |
  • 853 | 854 | 855 |
  • 856 |
    857 | 858 |
    859 | 860 |
    861 |

    返回上一页,对于浏览器而言,直接调用history.back返回上一个历史记录, 862 | 对于客户端而言则通过协议调用返回上一个WebView打开的相应页面。

    863 | 864 |
    865 | 866 |
     867 |         back: function(pagename, ext) {
     868 |             var that = this;
     869 | 
     870 |             if(that.platform === 'h5') {
     871 |                 history.back();
     872 |                 return;
     873 |             }
     874 | 
     875 |             if(pagename) {
     876 |                 that.pushBack('bridge:', 'back', ext);
     877 |                 return;
     878 |             }
     879 | 
     880 |             that.pushBack('bridge:', 'back', ext);
     881 |         },
    882 | 883 |
  • 884 | 885 | 886 |
  • 887 |
    888 | 889 |
    890 | 891 |
    892 |

    关闭当前的客户端页面,如果有额外的数据则通过ext参数传递给客户端。

    893 | 894 |
    895 | 896 |
     897 |         close: function(ext) {
     898 |             var that = this;
     899 | 
     900 |             if(that.platform === 'h5') {
     901 |                 window.close();
     902 |                 return;
     903 |             }
     904 | 
     905 |             that.pushBack('page:', 'close', ext);
     906 |         },
    907 | 908 |
  • 909 | 910 | 911 |
  • 912 |
    913 | 914 |
    915 | 916 |
    917 |

    客户端极简支付,如果有额外的数据则通过ext参数传递给客户端。

    918 | 919 |
    920 | 921 |
     922 |         minipay: function(alipayId, nextUrl, ext, successCallback, failCallback) {
     923 |             var that = this;
     924 |             var payResult;
     925 |             var params = {
     926 |                 alipay_id: alipayId,
     927 |                 data: ext || {},
     928 |                 failCallback: failCallback,
     929 |                 successCallback: function(ret) {
     930 |                     try {
     931 |                         payResult = JSON.parse(ret);
     932 |                     } catch(e) {
    933 | 934 |
  • 935 | 936 | 937 |
  • 938 |
    939 | 940 |
    941 | 942 |
    943 |

    iOS客户端3.5版本返回的JSON格式有误,解析会出问题

    944 | 945 |
    946 | 947 |
                            if(ret.match(/"ResultStatus":"9000"/igm)) {
     948 |                             payResult = {
     949 |                                 ResultStatus: '9000'
     950 |                             };
     951 |                         }
     952 |                     }
    953 | 954 |
  • 955 | 956 | 957 |
  • 958 |
    959 | 960 |
    961 | 962 |
    963 |

    iOS SDK返回的字段名首字母是大写,暂时兼容处理

    964 | 965 |
    966 | 967 |
                        if(that.platform === 'ios') {
     968 |                         payResult.resultStatus = payResult.ResultStatus;
     969 |                     }
     970 | 
     971 |                     if(payResult.resultStatus === '9000') {
     972 |                         successCallback && successCallback(payResult);
     973 |                         return;
     974 |                     }
     975 | 
     976 |                     failCallback && failCallback(payResult);
     977 |                 }
     978 |             };
     979 | 
     980 |             if(that.platform === 'h5' && nextUrl) {
     981 |                 window.location.href = nextUrl;
     982 |                 return;
     983 |             }
     984 | 
     985 |             if(that.client.version < 350 && nextUrl) {
     986 |                 window.location.href = nextUrl;
     987 |                 return;
     988 |             }
     989 | 
     990 |             that.pushBack('bridge:', 'minipay', params);
     991 |         }
     992 |     };
     993 | 
     994 |     this.Bridge = Bridge;
     995 | 
     996 | }).call(this);
    997 | 998 |
  • 999 | 1000 |
1001 |
1002 | 1003 | 1004 | --------------------------------------------------------------------------------