├── CHANGELOG.md ├── README.md ├── package.json ├── plugin.xml ├── src └── ios │ ├── UtilsPlugin.h │ └── UtilsPlugin.m └── www ├── bit6.js ├── index-bit6.js ├── phonegap-websocket.js ├── push-wrappers.js └── utils.js /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.11.0 [2017-05-05] 2 | 3 | ### Release Notes 4 | Changed plugin name, added package.json, updated README 5 | 6 | ### Bugfixes 7 | - Fixed the issue with sending video attachments for which the thumbnail generation fails. 8 | 9 | ## 0.10.0 [2016-12-23] 10 | 11 | ### Release Notes 12 | Phonertc module is removed. For Webrtc support on iOS and on Adnroid<5 iosrtc and Crosswalk webview plugins are used. 13 | 14 | 15 | ### Breaking Changes 16 | - For voice/video calls (iOS, Android<5) the plugins mentioned above are required. 17 | - For real time notifications support on Android<4.4 Crosswalk Webview is required. 18 | 19 | ### Bugfixes 20 | - Fixed the issue with missing remote video during iOS calls 21 | - iOS 10 support 22 | 23 | ## 0.9.9 [2016-09-02] 24 | 25 | ### Release Notes 26 | 27 | This release adds support for camera selection for video calls and audio recording during the calls. 28 | 29 | ### Features 30 | 31 | - Added support for camera selection on initiating a video call 32 | - Added support for call recording 33 | 34 | ### Bugfixes 35 | 36 | - Fixed the issue with push key cleanup. Now after logout the user will not get push notifications. 37 | 38 | 39 | ## 0.9.8 [2016-05-25] 40 | 41 | ### Release Notes 42 | 43 | This release adds support for custom push handling, switchCamera api and a mechanism to keep the user logged in. 44 | 45 | ### Features 46 | 47 | - Added support for custom push handling/implementation. 48 | - Added switchCamera method to Bit6 api: supported only on Android < 5.0 49 | - Added a mechanism to keep the user logged in after app restart. 50 | 51 | ### Bugfixes 52 | 53 | - Fixed the issue with opening the app by tapping on a notification when the app is closed. 54 | 55 | 56 | ## 0.9.7 [2016-04-28] 57 | 58 | ### Release Notes 59 | 60 | This release adds support for new push plugin (phonegap-plugin-push) while keeping the compatibility with the previously supported push plugins. 61 | 62 | ### Features 63 | 64 | - Added support for phonegap-plugin-push 65 | - Added push alerts localizatoin support on iOS. Works with new phonegap-plugin-push only 66 | 67 | ### Bugfixes 68 | 69 | - Fixed the call issue appearing on some Android devices caused by a missing permission 70 | - Fixed issues with Intel XDK Emulator (iOS, Android) and debugging in AppPreview (Android only) 71 | - Fixed camera selection on Android to use the front camera for video calls 72 | 73 | ## 0.9.6.1 [2016-03-15] 74 | 75 | ### Release Notes 76 | 77 | This release adds support for cordova-ios 4.x, reduces plugin size, and fixes an issue with iOS push notifications for Ad Hoc builds. 78 | 79 | ### Breaking Changes 80 | 81 | - iOS simulator build is not supported - i386 slice is removed from iOS WebRTC library 82 | - Push plugin dependency must be specified manually in the application - see 'Push notifications' section in README 83 | 84 | ### Features 85 | 86 | - Added support for cordova-ios 4.x 87 | - Significantly reduced plugin size 88 | - Updated WebRTC lib: Android - m49 89 | 90 | ### Bugfixes 91 | 92 | - Fixed iOS push issue for Ad Hoc builds 93 | 94 | 95 | ## 0.9.6 [2016-02-11] 96 | 97 | ### Release Notes 98 | 99 | This release has improved API for video elements, updated native WebRTC libs and some important bug fixes. 100 | 101 | ### Features 102 | 103 | - New video element layout mechanism 104 | - External authentication support 105 | - Automatic selection of iOS APNS environment 106 | - Updated WebRTC libs : iOS - m49, Andoid - m48 107 | 108 | ### Bugfixes 109 | 110 | - Fixed the call setup issue between devices (caused by incorrect buffering of ICE candidates) 111 | - Fixed the whitelist plugin warning about security policy tag 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bit6 Cordova Plugin 2 | ------------------- 3 | [![GitHub version](https://badge.fury.io/gh/bit6%2Fbit6-cordova.svg)](https://github.com/bit6/bit6-cordova) 4 | 5 | Add voice/video calling, texting, and rich media messaging into any mobile or web application. 6 | 7 | Get Bit6 sample app running in 10 minutes - follow our Quick Start guides for: 8 | * [Cordova CLI](http://docs.bit6.com/start/cordova-cli/) 9 | * [Intel XDK](http://docs.bit6.com/start/cordova-xdk/) 10 | * [Telerik App Builder](http://docs.bit6.com/start/cordova-telerik/) 11 | 12 | ### Prerequisites 13 | * Get the API Key at [Bit6 Dashboard](https://dashboard.bit6.com). 14 | 15 | ### Installation 16 | ```sh 17 | $ cordova plugin add https://github.com/bit6/bit6-cordova 18 | ``` 19 | 20 | ### Configuration 21 | Initialize Bit6 Plugin with your API key. 22 | ```js 23 | document.addEventListener("deviceready", onDeviceReady, false); 24 | function onDeviceReady() { 25 | var opts = {'apikey': 'yourApiKey'}; 26 | var b6 = Bit6.init(opts); 27 | // Bit6 SDK has been initialized 28 | } 29 | ``` 30 | 31 | ### Video/Voice calls on iOS 32 | 33 | To enable call support on iOS add [iosrtc](https://github.com/eface2face/cordova-plugin-iosrtc) plugin 34 | 35 | ``` 36 | cordova plugin add cordova-plugin-iosrtc 37 | ``` 38 | 39 | Note: Check the plugin requirements on [this page](https://github.com/eface2face/cordova-plugin-iosrtc) and [building page](https://github.com/eface2face/cordova-plugin-iosrtc/blob/master/docs/Building.md) 40 | 41 | Note: no need to add iosrtc if your app does not use audio/video calls. 42 | 43 | 44 | ### Video/Voice calls on Android < 5 45 | 46 | To enable call support on Android < 5 add [Crosswalk Webview](https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview) plugin 47 | 48 | ``` 49 | cordova plugin add cordova-plugin-crosswalk-webview 50 | ``` 51 | 52 | ### Real time notifications on Android<4.4 53 | 54 | For real time notifications (websocket) on Android<4.4 Crosswalk Webview is required. 55 | 56 | ### Permissions on Android >= 6 57 | 58 | For call support on Android >= 6 grant `Camera` and `Microphone` permissions from `Settings->Apps->YourApp->Permissions`. 59 | 60 | `Storage` permission is required to support message attachments. 61 | 62 | ### Quickstart 63 | Create a user with a password authentication: 64 | ```js 65 | b6.session.signup({identity: 'usr:john', password: 'secret'}, function(err) { 66 | if (err) { 67 | console.log('login error', err); 68 | } else { 69 | console.log('login successful'); 70 | } 71 | }); 72 | ``` 73 | Send a message to another user: 74 | ```js 75 | b6.compose('usr:tom').text('Hello!').send(function(err) { 76 | if (err) { 77 | console.log('error', err); 78 | } else { 79 | console.log('message sent'); 80 | } 81 | }); 82 | ``` 83 | Make a video call: 84 | ```js 85 | // Start an outgoing call and get a call controller (Dialog) 86 | var d = b6.startCall('usr:tom', {audio: true, video: true}); 87 | ``` 88 | 89 | ### Documentation 90 | Bit6 Cordova Plugin exposes the same API as [Bit6 JS SDK](https://github.com/bit6/bit6-js-sdk). Check Bit6 [JS documentation](http://docs.bit6.com/guides/js/). 91 | 92 | ### Demo app 93 | The complete source code is available in the [demo repo](https://github.com/bit6/bit6-cordova-demo). Check out the same demo app running with JS SDK at http://demo.bit6.com. 94 | 95 | ### Supported platforms 96 | * iOS 97 | * Android 98 | * Browser 99 | 100 | ### Push notifications 101 | 102 | Push Notification support is required for receiving incoming calls and messages. 103 | 104 | To enable this functionality please add one of these push plugins to your project: 105 | 106 | * [phonegap-plugin-push](https://github.com/phonegap/phonegap-plugin-push) (recommended) 107 | ```sh 108 | # Set any value for SENDER_ID. Bit6 plugin will override it with the correct one. 109 | $ cordova plugin add phonegap-plugin-push --variable SENDER_ID="XXXXXXX" 110 | ``` 111 | 112 | * [Telerik Push Plugin](https://github.com/Telerik-Verified-Plugins/PushNotification) 113 | ```sh 114 | $ cordova plugin add https://github.com/Telerik-Verified-Plugins/PushNotification 115 | ``` 116 | 117 | * [Legacy PhoneGap Push Plugin](https://github.com/phonegap-build/PushPlugin) If you use cordova-ios 3.x 118 | 119 | 120 | Then complete platform-specific configuration: 121 | 122 | * __iOS APNs__ ([detailed guide](http://docs.bit6.com/guides/push-apns/)) 123 | 1. Generate APNS certificate in iTunes Connect. 124 | 2. Export it into a p12 file. 125 | 3. Add the file to your app in [Bit6 Dashboard](https://dashboard.bit6.com). 126 | 127 | * __Android GCM__ ([detailed guide](http://docs.bit6.com/guides/push-gcm/)) 128 | 1. Get the project number and server key from [Google Dev Console](http://developer.android.com/google/gcm/gs.html). 129 | 2. Add project number and server key for your app in [Bit6 Dashboard](https://dashboard.bit6.com). 130 | 131 | ### Building with Xcode 7 132 | Please disable Bitcode support when building your Cordova app with Xcode. 133 | Go to `Build Settings`, set `Enable Bitcode` to `No`. 134 | 135 | ### Third-party libraries 136 | Bit6 plugin leverages code from the excellent [WebRTC](http://www.webrtc.org/), [PhoneRTC](https://github.com/alongubkin/phonertc) and [phonegap-websocket](https://github.com/mkuklis/phonegap-websocket/) projects. 137 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-bit6", 3 | "version": "0.11.0", 4 | "description": "Add voice and video calling, text and media messaging to your app", 5 | "cordova": { 6 | "id": "cordova-plugin-bit6", 7 | "platforms": [ 8 | "ios", 9 | "android", 10 | "browser" 11 | ] 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/bit6/bit6-cordova.git" 16 | }, 17 | "keywords": [ 18 | "webrtc", 19 | "messaging", 20 | "communication", 21 | "text", 22 | "ecosystem:cordova", 23 | "cordova-ios", 24 | "cordova-android" 25 | ], 26 | "engines": [ 27 | { 28 | "name": "cordova-ios", 29 | "version": ">=3.0.0" 30 | } 31 | ], 32 | "author": "bit6", 33 | "license": "Apache 2.0", 34 | "bugs": { 35 | "url": "https://github.com/bit6/bit6-cordova/issues" 36 | }, 37 | "homepage": "https://github.com/bit6/bit6-cordova#readme" 38 | } 39 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | Bit6 8 | Add voice and video calling, text and media messaging to your app 9 | Apache 2.0 10 | webrtc,messaging,communication,text 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 | 39 | Requires camera for video calls 40 | 41 | 42 | Requires microphone for calls 43 | 44 | 45 | 46 | 47 | 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 | -------------------------------------------------------------------------------- /src/ios/UtilsPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface UtilsPlugin : CDVPlugin 4 | 5 | - (void) isApnsProduction: (CDVInvokedUrlCommand*)command; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /src/ios/UtilsPlugin.m: -------------------------------------------------------------------------------- 1 | #import "UtilsPlugin.h" 2 | 3 | @implementation UtilsPlugin 4 | 5 | - (void) pluginInitialize 6 | { 7 | [super pluginInitialize]; 8 | } 9 | 10 | 11 | -(void) isApnsProduction: (CDVInvokedUrlCommand*)command 12 | { 13 | static BOOL isDevelopment = NO; 14 | #if TARGET_IPHONE_SIMULATOR 15 | isDevelopment = YES; 16 | #else 17 | NSData *data = [NSData dataWithContentsOfFile:[NSBundle.mainBundle pathForResource:@"embedded" ofType:@"mobileprovision"]]; 18 | if (data) { 19 | const char *bytes = [data bytes]; 20 | NSMutableString *profile = [[NSMutableString alloc] initWithCapacity:data.length]; 21 | for (NSUInteger i = 0; i < data.length; i++) { 22 | [profile appendFormat:@"%c", bytes[i]]; 23 | } 24 | NSString *cleared = [[profile componentsSeparatedByCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet] componentsJoinedByString:@""]; 25 | isDevelopment = [cleared rangeOfString:@"get-task-allow"].length > 0; 26 | } 27 | #endif 28 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool: !isDevelopment]; 29 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 30 | } 31 | 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /www/bit6.js: -------------------------------------------------------------------------------- 1 | // bit6 - v0.11.0 2 | 3 | (function() { 4 | var slice = [].slice; 5 | 6 | window.bit6 = {}; 7 | 8 | bit6.EventEmitter = (function() { 9 | function EventEmitter() { 10 | this.events = {}; 11 | } 12 | 13 | EventEmitter.prototype.emit = function() { 14 | var args, event, i, len, listener, ref; 15 | event = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; 16 | if (!this.events[event]) { 17 | return false; 18 | } 19 | ref = this.events[event]; 20 | for (i = 0, len = ref.length; i < len; i++) { 21 | listener = ref[i]; 22 | listener.apply(null, args); 23 | } 24 | return true; 25 | }; 26 | 27 | EventEmitter.prototype.addListener = function(event, listener) { 28 | var base; 29 | this.emit('newListener', event, listener); 30 | ((base = this.events)[event] != null ? base[event] : base[event] = []).push(listener); 31 | return this; 32 | }; 33 | 34 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 35 | 36 | EventEmitter.prototype.once = function(event, listener) { 37 | var fn; 38 | fn = (function(_this) { 39 | return function() { 40 | _this.removeListener(event, fn); 41 | return listener.apply(null, arguments); 42 | }; 43 | })(this); 44 | this.on(event, fn); 45 | return this; 46 | }; 47 | 48 | EventEmitter.prototype.removeListener = function(event, listener) { 49 | var l; 50 | if (!this.events[event]) { 51 | return this; 52 | } 53 | this.events[event] = (function() { 54 | var i, len, ref, results; 55 | ref = this.events[event]; 56 | results = []; 57 | for (i = 0, len = ref.length; i < len; i++) { 58 | l = ref[i]; 59 | if (l !== listener) { 60 | results.push(l); 61 | } 62 | } 63 | return results; 64 | }).call(this); 65 | return this; 66 | }; 67 | 68 | EventEmitter.prototype.off = EventEmitter.prototype.removeListener; 69 | 70 | EventEmitter.prototype.removeAllListeners = function(event) { 71 | delete this.events[event]; 72 | return this; 73 | }; 74 | 75 | return EventEmitter; 76 | 77 | })(); 78 | 79 | }).call(this); 80 | 81 | (function() { 82 | bit6.Conversation = (function() { 83 | function Conversation(id) { 84 | this.id = id; 85 | this.unread = 0; 86 | this.updated = 0; 87 | this.messages = []; 88 | this.modified = true; 89 | this.uri = this.id; 90 | } 91 | 92 | Conversation.prototype.isGroup = function() { 93 | return this.id.indexOf('grp:') === 0; 94 | }; 95 | 96 | Conversation.prototype.getUnreadCount = function() { 97 | return this.unread; 98 | }; 99 | 100 | Conversation.prototype.getLastMessage = function() { 101 | var n; 102 | n = this.messages.length; 103 | if (n > 0) { 104 | return this.messages[n - 1]; 105 | } else { 106 | return null; 107 | } 108 | }; 109 | 110 | Conversation.prototype.appendMessage = function(m) { 111 | this.messages.push(m); 112 | if (this.updated < m.created) { 113 | this.updated = m.created; 114 | } 115 | this._updateUnreadCount(); 116 | this.modified = true; 117 | return this.modified; 118 | }; 119 | 120 | Conversation.prototype.updateMessage = function(m) { 121 | this._updateUnreadCount(); 122 | return this.modified; 123 | }; 124 | 125 | Conversation.prototype.removeMessage = function(m) { 126 | var i, idx, j, len, n, o, ref, removed; 127 | n = this.messages.length; 128 | if (n > 0 && this.messages[n - 1].id === m.id) { 129 | this.modified = true; 130 | } 131 | idx = -1; 132 | ref = this.messages; 133 | for (i = j = 0, len = ref.length; j < len; i = ++j) { 134 | o = ref[i]; 135 | if (o.id === m.id) { 136 | idx = i; 137 | break; 138 | } 139 | } 140 | if (idx >= 0) { 141 | removed = this.messages.splice(idx, 1); 142 | } 143 | this._updateUnreadCount(); 144 | return this.modified; 145 | }; 146 | 147 | Conversation.prototype._updateUnreadCount = function() { 148 | var j, len, m, num, ref; 149 | num = 0; 150 | ref = this.messages; 151 | for (j = 0, len = ref.length; j < len; j++) { 152 | m = ref[j]; 153 | if (m.canMarkRead()) { 154 | num++; 155 | } 156 | } 157 | this.modified || (this.modified = num !== this.unread); 158 | return this.unread = num; 159 | }; 160 | 161 | Conversation.prototype.domId = function() { 162 | if (!this.id) { 163 | console.log('ConvId is null', this); 164 | return null; 165 | } 166 | return bit6.Session.base64urlEncode(this.id); 167 | }; 168 | 169 | Conversation.fromDomId = function(t) { 170 | if (!t) { 171 | return t; 172 | } 173 | return bit6.Session.base64urlDecode(t); 174 | }; 175 | 176 | return Conversation; 177 | 178 | })(); 179 | 180 | }).call(this); 181 | 182 | (function() { 183 | var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 184 | hasProp = {}.hasOwnProperty; 185 | 186 | bit6.Dialog = (function(superClass) { 187 | extend(Dialog, superClass); 188 | 189 | function Dialog(client, outgoing, other, invite, options) { 190 | var base, base1, base2, i, j, len, len1, myaddr, myname, ref, ref1, ref2, t; 191 | this.client = client; 192 | this.outgoing = outgoing; 193 | this.other = other; 194 | this.invite = invite; 195 | this.options = options; 196 | Dialog.__super__.constructor.apply(this, arguments); 197 | this.me = this.client.session.identity; 198 | if (this.options == null) { 199 | this.options = {}; 200 | } 201 | ref = ['audio', 'video', 'screen', 'data']; 202 | for (i = 0, len = ref.length; i < len; i++) { 203 | t = ref[i]; 204 | if ((base = this.options)[t] == null) { 205 | base[t] = false; 206 | } 207 | } 208 | if ((base1 = this.options).offnet == null) { 209 | base1.offnet = false; 210 | } 211 | if (this.options.offnet || this.other.indexOf('grp:') === 0) { 212 | this.options.mode = 'mix'; 213 | } 214 | if ((base2 = this.options).mode == null) { 215 | base2.mode = 'p2p'; 216 | } 217 | this.options.data = this.options.mode === 'p2p'; 218 | this.remoteOptions = {}; 219 | ref1 = ['audio', 'video', 'offnet']; 220 | for (j = 0, len1 = ref1.length; j < len1; j++) { 221 | t = ref1[j]; 222 | this.remoteOptions[t] = this.options[t] && !this.outgoing; 223 | } 224 | this.params = { 225 | callID: null, 226 | userVariables: { 227 | mode: this.options.mode, 228 | offnet: this.options.offnet 229 | } 230 | }; 231 | myaddr = this.me + '@' + this.client.apikey; 232 | myname = (ref2 = this.client.session.displayName) != null ? ref2 : this.me; 233 | if (this.outgoing) { 234 | this.state = 'req'; 235 | this.params.destination_number = this.other + '@' + this.client.apikey; 236 | this.params.caller_id_name = myname; 237 | this.params.caller_id_number = myaddr; 238 | this.params.callID = bit6.JsonRpc.generateGUID(); 239 | } else { 240 | this.state = 'pre-answer'; 241 | this.params.callee_id_name = myname; 242 | this.params.callee_id_number = myaddr; 243 | } 244 | this._recording = false; 245 | this.transfers = []; 246 | this.renegotiating = false; 247 | } 248 | 249 | Dialog.prototype.supports = function(c) { 250 | switch (c) { 251 | case 'recording': 252 | return this.options.mode === 'mix'; 253 | } 254 | return false; 255 | }; 256 | 257 | Dialog.prototype.connect = function(opts) { 258 | var i, len, newv, oldv, ref, t; 259 | if (opts == null) { 260 | opts = {}; 261 | } 262 | ref = ['audio', 'video', 'screen']; 263 | for (i = 0, len = ref.length; i < len; i++) { 264 | t = ref[i]; 265 | if (opts[t] == null) { 266 | continue; 267 | } 268 | newv = opts[t]; 269 | oldv = this.options[t]; 270 | if (oldv === newv) { 271 | continue; 272 | } 273 | this.options[t] = newv; 274 | } 275 | this.client._ensureRtcCapture(this.options, (function(_this) { 276 | return function(err) { 277 | if (err != null) { 278 | _this.emit('error', 'Unable to start media'); 279 | return _this.hangup(); 280 | } 281 | return _this._onMediaReady(); 282 | }; 283 | })(this)); 284 | return this; 285 | }; 286 | 287 | Dialog.prototype.hasVideoStreams = function() { 288 | return this.options.video || this.remoteOptions.video; 289 | }; 290 | 291 | Dialog.prototype.recording = function(flag) { 292 | var args, m; 293 | if ((flag == null) || this._recording === flag) { 294 | return this._recording; 295 | } 296 | this._recording = flag; 297 | args = { 298 | callID: this.params.callID, 299 | me: this.me, 300 | other: this.other, 301 | outgoing: this.outgoing 302 | }; 303 | m = flag ? 'startRecording' : 'stopRecording'; 304 | return this.client._sendNotification('control', m, args); 305 | }; 306 | 307 | Dialog.prototype._onMediaReady = function() { 308 | var msg; 309 | if (!this.renegotiating) { 310 | if (!this.outgoing) { 311 | this._sendAcceptRejectIncomingCall(true); 312 | } 313 | this.emit('progress'); 314 | } 315 | if (this.rtc == null) { 316 | this._createRtc(); 317 | } 318 | this.rtc.update(this.client.capture, this.options, this.remoteOptions); 319 | if (this.renegotiating && !this.outgoing) { 320 | msg = { 321 | audio: this.options.audio, 322 | video: this.options.video 323 | }; 324 | return this.sendJson('reneg2', msg); 325 | } 326 | }; 327 | 328 | Dialog.prototype._createRtc = function() { 329 | var iceServers; 330 | this.rtc = this.client._createRtc(); 331 | this.rtc.on('offerAnswer', (function(_this) { 332 | return function(offerAnswer) { 333 | return _this._sendOfferAnswer(offerAnswer, function(err, result) { 334 | if (offerAnswer.type === 'answer') { 335 | return _this.emit('answer'); 336 | } 337 | }); 338 | }; 339 | })(this)); 340 | this.rtc.on('video', (function(_this) { 341 | return function(v, op) { 342 | return _this.client._emitVideoEvent(v, _this, op); 343 | }; 344 | })(this)); 345 | this.rtc.on('dcOpen', (function(_this) { 346 | return function() { 347 | return _this._startNextPendingTransfer(); 348 | }; 349 | })(this)); 350 | this.rtc.on('transfer', (function(_this) { 351 | return function(tr) { 352 | var n, o; 353 | if (!tr.outgoing) { 354 | n = tr.info.name; 355 | } 356 | if ('offer2' === n || 'answer2' === n) { 357 | if (tr.completed()) { 358 | o = tr.json(); 359 | _this._gotRemoteOfferAnswer(o.type, o); 360 | } 361 | } else if ('reneg2' === n) { 362 | if (tr.completed()) { 363 | o = tr.json(); 364 | if (o.audio != null) { 365 | _this.remoteOptions.audio = o.audio; 366 | } 367 | if (o.video != null) { 368 | _this.remoteOptions.video = o.video; 369 | } 370 | _this.connect(); 371 | } 372 | } else { 373 | _this.emit('transfer', tr); 374 | } 375 | if (tr.err) { 376 | 377 | } else if (tr.completed() && tr.outgoing) { 378 | return _this._startNextPendingTransfer(); 379 | } 380 | }; 381 | })(this)); 382 | iceServers = this.client.session.config.rtc.iceServers; 383 | return this.rtc.init(this.outgoing, iceServers); 384 | }; 385 | 386 | Dialog.prototype._startNextPendingTransfer = function() { 387 | var i, len, ref, results, t; 388 | ref = this.transfers; 389 | results = []; 390 | for (i = 0, len = ref.length; i < len; i++) { 391 | t = ref[i]; 392 | if (t.pending()) { 393 | this.rtc.startOutgoingTransfer(t); 394 | break; 395 | } else { 396 | results.push(void 0); 397 | } 398 | } 399 | return results; 400 | }; 401 | 402 | Dialog.prototype.sendFile = function(info, data) { 403 | var tr; 404 | tr = new bit6.Transfer(true, info, data); 405 | this.transfers.push(tr); 406 | return tr._ensureSourceData((function(_this) { 407 | return function() { 408 | if (tr.err != null) { 409 | _this.emit('transfer', tr); 410 | } 411 | if (tr.data != null) { 412 | return _this.rtc.startOutgoingTransfer(tr); 413 | } 414 | }; 415 | })(this)); 416 | }; 417 | 418 | Dialog.prototype.sendJson = function(name, o) { 419 | var tr; 420 | tr = new bit6.Transfer(true, { 421 | name: name 422 | }); 423 | tr.json(o); 424 | this.transfers.push(tr); 425 | if (tr.data != null) { 426 | return this.rtc.startOutgoingTransfer(tr); 427 | } 428 | }; 429 | 430 | Dialog.prototype.hangup = function() { 431 | if (this.rtc) { 432 | this.rtc.stop(); 433 | this.rtc = null; 434 | this._sendHangupCall(); 435 | } else if (!this.outgoing) { 436 | this._sendAcceptRejectIncomingCall(false); 437 | } 438 | return this.emit('end'); 439 | }; 440 | 441 | Dialog.prototype._sendOfferAnswer = function(offerAnswer, cb) { 442 | var msg, ref, ref1; 443 | msg = offerAnswer; 444 | if (msg.type === 'offer') { 445 | if (this.renegotiating) { 446 | return this.sendJson('offer2', msg); 447 | } else { 448 | this.state = 'sent-offer'; 449 | msg.dialogParams = this.params; 450 | return (ref = this.client.rpc) != null ? ref.call('verto.invite', msg, cb) : void 0; 451 | } 452 | } else if (msg.type === 'answer') { 453 | if (this.renegotiating) { 454 | return this.sendJson('answer2', msg); 455 | } else { 456 | this.state = 'sent-answer'; 457 | msg.dialogParams = this.params; 458 | this.renegotiating = true; 459 | return (ref1 = this.client.rpc) != null ? ref1.call('verto.answer', msg, cb) : void 0; 460 | } 461 | } 462 | }; 463 | 464 | Dialog.prototype._sendAcceptRejectIncomingCall = function(accept) { 465 | var ref, type; 466 | type = accept ? 'accept' : 'reject'; 467 | return this.client._sendNotification((ref = this.invite) != null ? ref.rdest : void 0, type); 468 | }; 469 | 470 | Dialog.prototype._sendHangupCall = function() { 471 | var msg, ref; 472 | this.state = 'sent-bye'; 473 | msg = { 474 | dialogParams: this.params 475 | }; 476 | return (ref = this.client.rpc) != null ? ref.call('verto.bye', msg, (function(_this) { 477 | return function(err, result) {}; 478 | })(this)) : void 0; 479 | }; 480 | 481 | Dialog.prototype.handleRpcCall = function(method, params) { 482 | switch (method) { 483 | case 'verto.bye': 484 | return this._gotHangup(params); 485 | case 'verto.invite': 486 | this.params.callID = params.callID; 487 | this.params.caller_id_name = params.caller_id_name; 488 | this.params.caller_id_number = params.caller_id_number; 489 | return this._gotRemoteOfferAnswer('offer', params); 490 | case 'verto.answer': 491 | this._gotRemoteOfferAnswer('answer', params); 492 | return this.emit('answer'); 493 | } 494 | }; 495 | 496 | Dialog.prototype._gotRemoteOfferAnswer = function(type, offerAnswer) { 497 | this.state = 'got-' + type; 498 | offerAnswer.type = type; 499 | if (type === 'answer') { 500 | this.renegotiating = true; 501 | } 502 | if (this.rtc != null) { 503 | return this.rtc.gotRemoteOfferAnswer(offerAnswer, this.client.capture); 504 | } else { 505 | return console.log('Error: RTC not inited'); 506 | } 507 | }; 508 | 509 | Dialog.prototype._gotHangup = function(d) { 510 | this.state = 'got-bye'; 511 | if (this.rtc != null) { 512 | this.rtc.stop(); 513 | this.rtc = null; 514 | return this.emit('end'); 515 | } 516 | }; 517 | 518 | return Dialog; 519 | 520 | })(bit6.EventEmitter); 521 | 522 | }).call(this); 523 | 524 | (function() { 525 | bit6.Group = (function() { 526 | function Group(id) { 527 | this.id = id; 528 | this.meta = null; 529 | this.permissions = null; 530 | this.members = []; 531 | this.updated = 0; 532 | } 533 | 534 | Group.prototype.update = function(o, forceUpdate) { 535 | var k, v; 536 | if (!forceUpdate) { 537 | if (o.updated == null) { 538 | return false; 539 | } 540 | if (this.updated === o.updated) { 541 | return false; 542 | } 543 | } 544 | for (k in o) { 545 | v = o[k]; 546 | this[k] = v; 547 | } 548 | return true; 549 | }; 550 | 551 | Group.prototype._updateMemberProfile = function(ident, profile) { 552 | var i, len, m, ref; 553 | ref = this.members; 554 | for (i = 0, len = ref.length; i < len; i++) { 555 | m = ref[i]; 556 | if (m.id === ident) { 557 | m.profile = profile; 558 | return true; 559 | } 560 | } 561 | return false; 562 | }; 563 | 564 | Group.prototype.getMember = function(ident) { 565 | var i, len, m, ref; 566 | ref = this.members; 567 | for (i = 0, len = ref.length; i < len; i++) { 568 | m = ref[i]; 569 | if (m.id === ident) { 570 | return m; 571 | } 572 | } 573 | return null; 574 | }; 575 | 576 | Group.prototype.getConversationId = function() { 577 | return 'grp:' + this.id; 578 | }; 579 | 580 | return Group; 581 | 582 | })(); 583 | 584 | }).call(this); 585 | 586 | (function() { 587 | bit6.JsonRpc = (function() { 588 | JsonRpc.prototype.reconnectDelay = 4000; 589 | 590 | function JsonRpc(options) { 591 | var base; 592 | this.options = options; 593 | if ((base = this.options).sessid == null) { 594 | base.sessid = bit6.JsonRpc.generateGUID(); 595 | } 596 | this.currentId = 1; 597 | this.callbacks = {}; 598 | this.queue = []; 599 | this.closed = false; 600 | } 601 | 602 | JsonRpc.prototype.connect = function() { 603 | this.closed = false; 604 | if (this.ws != null) { 605 | return; 606 | } 607 | this.ws = new WebSocket(this.options.wsUrl); 608 | this.ws.onopen = (function(_this) { 609 | return function() { 610 | var i, len, m, ref; 611 | if (_this.queue.length > 0) { 612 | ref = _this.queue; 613 | for (i = 0, len = ref.length; i < len; i++) { 614 | m = ref[i]; 615 | _this.ws.send(m); 616 | } 617 | return _this.queue = []; 618 | } else { 619 | return _this.call('login', {}, function(err, result) { 620 | if (err) { 621 | return console.log('rpc login err=', err, 'result=', result); 622 | } 623 | }); 624 | } 625 | }; 626 | })(this); 627 | this.ws.onmessage = (function(_this) { 628 | return function(e) { 629 | var cb, error, ex, m; 630 | try { 631 | m = JSON.parse(e.data); 632 | if ((m.result != null) || (m.error != null)) { 633 | if (m.id != null) { 634 | cb = _this.callbacks[m.id]; 635 | delete _this.callbacks[m.id]; 636 | cb(m.error, m.result); 637 | } 638 | } 639 | if ((m.method != null) && (m.params != null)) { 640 | if (m.id != null) { 641 | return _this.options.onRpcCall(m.method, m.params, function(err, result) { 642 | var t; 643 | t = { 644 | jsonrpc: '2.0', 645 | id: m.id 646 | }; 647 | if (err != null) { 648 | t.error = err; 649 | } 650 | if (result != null) { 651 | t.result = result; 652 | } 653 | return _this._send(t); 654 | }); 655 | } else { 656 | return _this.options.onRpcCall(m.method, m.params); 657 | } 658 | } 659 | } catch (error) { 660 | ex = error; 661 | console.log('Exception parsing JSON response ' + ex); 662 | return console.log(' -- RAW {{{' + e.data + '}}}'); 663 | } 664 | }; 665 | })(this); 666 | this.ws.onclose = (function(_this) { 667 | return function() { 668 | if (_this.closed) { 669 | return; 670 | } 671 | _this.ws = null; 672 | _this.queue = []; 673 | return setTimeout(function() { 674 | return _this.connect(); 675 | }, _this.reconnectDelay); 676 | }; 677 | })(this); 678 | return this.ws.onerror = (function(_this) { 679 | return function() { 680 | return console.log('Rpc ws got error. Socket state=' + _this.ws.readyState); 681 | }; 682 | })(this); 683 | }; 684 | 685 | JsonRpc.prototype.notify = function(m, params) { 686 | return this.call(m, params, false); 687 | }; 688 | 689 | JsonRpc.prototype.call = function(m, params, cb) { 690 | var req; 691 | if (this.ws == null) { 692 | this.connect(); 693 | } 694 | req = { 695 | jsonrpc: '2.0', 696 | method: m, 697 | params: params 698 | }; 699 | req.params.login = this.options.login; 700 | req.params.passwd = this.options.password; 701 | if (this.options.sessid != null) { 702 | req.params.sessid = this.options.sessid; 703 | } 704 | if (cb !== false) { 705 | req.id = this.currentId++; 706 | this.callbacks[req.id] = cb; 707 | } 708 | return this._send(req); 709 | }; 710 | 711 | JsonRpc.prototype._send = function(req) { 712 | var data; 713 | data = JSON.stringify(req); 714 | if ((this.ws != null) && this.ws.readyState === 1) { 715 | return this.ws.send(data); 716 | } else { 717 | return this.queue.push(data); 718 | } 719 | }; 720 | 721 | JsonRpc.prototype.close = function() { 722 | this.closed = true; 723 | if (this.ws != null) { 724 | this.ws.close(); 725 | } 726 | return this.ws = null; 727 | }; 728 | 729 | JsonRpc.generateGUID = function() { 730 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 731 | var r, v; 732 | r = Math.random() * 16 | 0; 733 | v = c === 'x' ? r : r & 0x3 | 0x8; 734 | return v.toString(16); 735 | }); 736 | }; 737 | 738 | return JsonRpc; 739 | 740 | })(); 741 | 742 | }).call(this); 743 | 744 | (function() { 745 | var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 746 | hasProp = {}.hasOwnProperty, 747 | slice = [].slice; 748 | 749 | bit6.Client = (function(superClass) { 750 | var endpoints; 751 | 752 | extend(Client, superClass); 753 | 754 | Client.version = '0.11.0'; 755 | 756 | endpoints = { 757 | prod: 'https://api.bit6.com', 758 | dev: 'https://api.b6dev.net', 759 | local: 'http://127.0.0.1:3000' 760 | }; 761 | 762 | function Client(opts) { 763 | var hasWebRTC, ref, ref1, ref2, url; 764 | Client.__super__.constructor.apply(this, arguments); 765 | if ((opts != null ? opts.apikey : void 0) == null) { 766 | throw 'Missing required "apikey" option'; 767 | } 768 | this.apikey = opts.apikey; 769 | url = (ref = (ref1 = opts.url) != null ? ref1 : opts.env) != null ? ref : 'prod'; 770 | this.apiurl = (ref2 = endpoints[url]) != null ? ref2 : url; 771 | hasWebRTC = (window.RTCPeerConnection != null) || (window.mozRTCPeerConnection != null) || (window.webkitRTCPeerConnection != null); 772 | this.caps = { 773 | audio: hasWebRTC, 774 | video: hasWebRTC, 775 | websocket: typeof WebSocket !== "undefined" && WebSocket !== null, 776 | attachment: (typeof Blob !== "undefined" && Blob !== null) && (typeof FormData !== "undefined" && FormData !== null) && (typeof FileReader !== "undefined" && FileReader !== null) 777 | }; 778 | this._clear(); 779 | this.session = new bit6.Session(this); 780 | } 781 | 782 | Client.prototype._clear = function() { 783 | this.lastSince = 0; 784 | this.me = {}; 785 | this.messages = {}; 786 | this.conversations = {}; 787 | this.groups = {}; 788 | this.presence = {}; 789 | this.lastTypingSent = 0; 790 | this.capture = null; 791 | return this.dialogs = []; 792 | }; 793 | 794 | Client.prototype._onLogin = function(cb) { 795 | this._connectRt(); 796 | return this._loadMe((function(_this) { 797 | return function(err) { 798 | return cb(null); 799 | }; 800 | })(this)); 801 | }; 802 | 803 | Client.prototype._onBeforeLogout = function(cb) { 804 | this._disconnectRt(); 805 | this._clear(); 806 | return cb(null); 807 | }; 808 | 809 | Client.prototype._loadMe = function(cb) { 810 | var data; 811 | data = { 812 | embed: 'devices,identities,groups,messages', 813 | since: this.lastSince 814 | }; 815 | return this.api('/me', data, (function(_this) { 816 | return function(err, result, headers) { 817 | var i, k, len, ref, ref1; 818 | if (err) { 819 | return cb(err); 820 | } 821 | _this.lastSince = (ref = headers != null ? headers.etag : void 0) != null ? ref : 0; 822 | ref1 = ['devices', 'identities', 'data', 'profile']; 823 | for (i = 0, len = ref1.length; i < len; i++) { 824 | k = ref1[i]; 825 | if (result[k] != null) { 826 | _this.me[k] = result[k]; 827 | } 828 | } 829 | if (result.groups != null) { 830 | _this._processGroupMemberships(result.groups); 831 | } 832 | _this._processMessages(result.messages); 833 | return cb(); 834 | }; 835 | })(this)); 836 | }; 837 | 838 | Client.prototype.setPrivateData = function(o, cb) { 839 | return this._setDataOrProfile('data', o, cb); 840 | }; 841 | 842 | Client.prototype.setPublicProfile = function(o, cb) { 843 | return this._setDataOrProfile('profile', o, cb); 844 | }; 845 | 846 | Client.prototype._setDataOrProfile = function(name, o, cb) { 847 | var old, ref; 848 | old = (ref = this.me[name]) != null ? ref : null; 849 | this.me[name] = o; 850 | return this.api('/me/' + name, 'POST', o, (function(_this) { 851 | return function(err, x) { 852 | if (err) { 853 | delete _this.me[name]; 854 | if (old) { 855 | _this.me[name] = old; 856 | } 857 | } else { 858 | _this.me[name] = x; 859 | } 860 | return typeof cb === "function" ? cb(err, x) : void 0; 861 | }; 862 | })(this)); 863 | }; 864 | 865 | Client.prototype.getConversation = function(uri) { 866 | var ref; 867 | return (ref = this.conversations[uri]) != null ? ref : null; 868 | }; 869 | 870 | Client.prototype.getConversationByUri = function(uri) { 871 | var ref; 872 | return (ref = this.conversations[uri]) != null ? ref : null; 873 | }; 874 | 875 | Client.prototype.getSortedConversations = function() { 876 | var cc; 877 | cc = this.conversations; 878 | return Object.keys(cc).sort(function(a, b) { 879 | return cc[b].updated - cc[a].updated; 880 | }).map(function(sortedKey) { 881 | return cc[sortedKey]; 882 | }); 883 | }; 884 | 885 | Client.prototype.addEmptyConversation = function(uri) { 886 | var conv, convId, ref; 887 | convId = uri; 888 | conv = (ref = this.conversations[convId]) != null ? ref : null; 889 | if (!conv) { 890 | conv = new bit6.Conversation(convId); 891 | this.conversations[convId] = conv; 892 | conv.updated = Date.now(); 893 | this.emit('conversation', conv, 1); 894 | } 895 | return conv; 896 | }; 897 | 898 | Client.prototype.deleteConversation = function(conv, cb) { 899 | var other; 900 | conv = (conv != null ? conv.id : void 0) ? conv : this.getConversation(conv); 901 | other = encodeURIComponent(conv.id); 902 | return this.api('/me/messages?other=' + other, 'DELETE', (function(_this) { 903 | return function(err, result) { 904 | var i, len, m, msgs, op; 905 | if (err) { 906 | return typeof cb === "function" ? cb(err) : void 0; 907 | } 908 | msgs = conv.messages.slice(); 909 | for (i = 0, len = msgs.length; i < len; i++) { 910 | m = msgs[i]; 911 | m.deleted = Date.now(); 912 | _this._processMessage(m, true); 913 | } 914 | op = 0; 915 | if (!conv.isGroup()) { 916 | op = -1; 917 | delete _this.conversations[conv.id]; 918 | conv.deleted = Date.now(); 919 | } 920 | _this.emit('conversation', conv, op); 921 | return typeof cb === "function" ? cb(null) : void 0; 922 | }; 923 | })(this)); 924 | }; 925 | 926 | Client.prototype.markConversationAsRead = function(conv) { 927 | var i, len, m, num, other, ref; 928 | num = 0; 929 | if (!(conv != null ? conv.messages : void 0)) { 930 | return num; 931 | } 932 | ref = conv.messages; 933 | for (i = 0, len = ref.length; i < len; i++) { 934 | m = ref[i]; 935 | if (!m.canMarkRead()) { 936 | continue; 937 | } 938 | m.status(bit6.Message.READ); 939 | this._processMessage(m); 940 | num++; 941 | } 942 | if (num === 0) { 943 | return; 944 | } 945 | other = encodeURIComponent(conv.uri); 946 | this.api('/me/messages?other=' + other, 'PUT', { 947 | status: 'read' 948 | }, function(err, result) { 949 | if (err) { 950 | return console.log('markAsRead result=', result, 'err=', err); 951 | } 952 | }); 953 | return num; 954 | }; 955 | 956 | Client.prototype.compose = function(dest) { 957 | var m; 958 | m = new bit6.Outgoing(this); 959 | if (dest != null) { 960 | m.to(dest); 961 | } 962 | return m; 963 | }; 964 | 965 | Client.prototype._failOutgoingMessage = function(m) { 966 | m.status(bit6.Message.FAILED); 967 | return this._processMessage(m); 968 | }; 969 | 970 | Client.prototype._sendMessagePost = function(m, cb) { 971 | var tmpId; 972 | tmpId = m.id; 973 | return this.api('/me/messages', 'POST', m._export(), (function(_this) { 974 | return function(err, o) { 975 | var tmp; 976 | if (err) { 977 | _this._failOutgoingMessage(m); 978 | return typeof cb === "function" ? cb(err) : void 0; 979 | } else { 980 | _this._processMessage(o); 981 | tmp = { 982 | id: tmpId, 983 | deleted: Date.now() 984 | }; 985 | _this._processMessage(tmp); 986 | return typeof cb === "function" ? cb(null, _this.messages[o.id]) : void 0; 987 | } 988 | }; 989 | })(this)); 990 | }; 991 | 992 | Client.prototype.markMessageAsRead = function(m) { 993 | if (!m.canMarkRead()) { 994 | return false; 995 | } 996 | m.status(bit6.Message.READ); 997 | this._processMessage(m); 998 | this.api('/me/messages/' + m.id, 'PUT', { 999 | status: 'read' 1000 | }, function(err, result) {}); 1001 | return true; 1002 | }; 1003 | 1004 | Client.prototype.deleteMessage = function(m, cb) { 1005 | m = (m != null ? m.id : void 0) ? m : this.messages[m]; 1006 | return this.api('/me/messages/' + m.id, 'DELETE', (function(_this) { 1007 | return function(err, result) { 1008 | if (err) { 1009 | return typeof cb === "function" ? cb(err) : void 0; 1010 | } 1011 | m.deleted = Date.now(); 1012 | _this._processMessage(m); 1013 | return typeof cb === "function" ? cb(null) : void 0; 1014 | }; 1015 | })(this)); 1016 | }; 1017 | 1018 | Client.prototype._processMessages = function(messages) { 1019 | var c, i, id, len, o, ref, results; 1020 | if (!messages) { 1021 | return; 1022 | } 1023 | for (i = 0, len = messages.length; i < len; i++) { 1024 | o = messages[i]; 1025 | this._processMessage(o, true); 1026 | } 1027 | ref = this.conversations; 1028 | results = []; 1029 | for (id in ref) { 1030 | c = ref[id]; 1031 | if (c.modified) { 1032 | c.modified = false; 1033 | results.push(this.emit('conversation', c, 0)); 1034 | } else { 1035 | results.push(void 0); 1036 | } 1037 | } 1038 | return results; 1039 | }; 1040 | 1041 | Client.prototype._processMessage = function(o, noConvUpdateEvents) { 1042 | var conv, convId, m, oldConv, op, ref, ref1; 1043 | m = (ref = this.messages[o.id]) != null ? ref : null; 1044 | op = 0; 1045 | if (!m) { 1046 | if ((o != null ? o.deleted : void 0) > 0) { 1047 | return null; 1048 | } 1049 | if (o instanceof bit6.Message) { 1050 | m = o; 1051 | } else { 1052 | m = new bit6.Message(o); 1053 | } 1054 | this.messages[m.id] = m; 1055 | op = 1; 1056 | } 1057 | convId = m.getConversationId(); 1058 | conv = oldConv = (ref1 = this.conversations[convId]) != null ? ref1 : null; 1059 | if (!conv) { 1060 | conv = new bit6.Conversation(convId); 1061 | this.conversations[convId] = conv; 1062 | } 1063 | if (op > 0) { 1064 | conv.appendMessage(m); 1065 | } else { 1066 | m.updateMessage(o); 1067 | if ((o != null ? o.deleted : void 0) > 0) { 1068 | delete this.messages[m.id]; 1069 | conv.removeMessage(m); 1070 | op = -1; 1071 | } else { 1072 | conv.updateMessage(m); 1073 | } 1074 | } 1075 | if (!oldConv) { 1076 | conv.modified = false; 1077 | this.emit('conversation', conv, 1); 1078 | } 1079 | this.emit('message', m, op); 1080 | if (conv.modified && !noConvUpdateEvents) { 1081 | conv.modified = false; 1082 | this.emit('conversation', conv, 0); 1083 | } 1084 | return m; 1085 | }; 1086 | 1087 | Client.prototype.getGroup = function(id) { 1088 | var ref; 1089 | if ((id != null ? id.indexOf('grp:') : void 0) === 0) { 1090 | id = id.substring(4); 1091 | } 1092 | return (ref = this.groups[id]) != null ? ref : null; 1093 | }; 1094 | 1095 | Client.prototype.getGroupById = function(id) { 1096 | return this.getGroup(id); 1097 | }; 1098 | 1099 | Client.prototype.createGroup = function(info, cb) { 1100 | if (info == null) { 1101 | info = {}; 1102 | } 1103 | info.identity = this.session.identity; 1104 | return this.api('/groups', 'POST', info, (function(_this) { 1105 | return function(err, o) { 1106 | if (err) { 1107 | return cb(err); 1108 | } 1109 | return _this._loadMe(function(err) { 1110 | if (err) { 1111 | return cb(err); 1112 | } 1113 | return cb(null, _this.getGroup(o.id)); 1114 | }); 1115 | }; 1116 | })(this)); 1117 | }; 1118 | 1119 | Client.prototype.joinGroup = function(g, role, cb) { 1120 | return this.inviteGroupMember(g, 'me', role, cb); 1121 | }; 1122 | 1123 | Client.prototype.leaveGroup = function(g, cb) { 1124 | return this.kickGroupMember(g, 'me', cb); 1125 | }; 1126 | 1127 | Client.prototype.inviteGroupMember = function(g, ident, role, cb) { 1128 | var gid, memberInfo; 1129 | gid = g.id != null ? g.id : g; 1130 | if (ident === 'me') { 1131 | ident = this.session.identity; 1132 | } 1133 | memberInfo = { 1134 | id: ident, 1135 | role: role 1136 | }; 1137 | return this.api('/groups/' + gid + '/members', 'POST', memberInfo, (function(_this) { 1138 | return function(err, member) { 1139 | if (err) { 1140 | return cb(err); 1141 | } 1142 | return _this._loadMe(function(err) { 1143 | if (err) { 1144 | return cb(err); 1145 | } 1146 | return cb(null); 1147 | }); 1148 | }; 1149 | })(this)); 1150 | }; 1151 | 1152 | Client.prototype.kickGroupMember = function(g, m, cb) { 1153 | if (g.id == null) { 1154 | g = this.getGroup(g); 1155 | } 1156 | if (g == null) { 1157 | return cb(null); 1158 | } 1159 | if (m === 'me') { 1160 | m = g.me.identity; 1161 | } 1162 | if (!m.id) { 1163 | m = g.getMember(m); 1164 | } 1165 | if (m == null) { 1166 | return cb(null); 1167 | } 1168 | return this.api('/groups/' + g.id + '/members/' + m.id, 'DELETE', (function(_this) { 1169 | return function(err) { 1170 | if (err) { 1171 | return cb(err); 1172 | } 1173 | return _this._loadMe(function(err) { 1174 | if (err) { 1175 | return cb(err); 1176 | } 1177 | return cb(null); 1178 | }); 1179 | }; 1180 | })(this)); 1181 | }; 1182 | 1183 | Client.prototype._processGroupMemberships = function(infos) { 1184 | var g, groupsToSync, i, id, info, isUpdated, j, len, len1, me, o, op, ref, ref1, results, tmp; 1185 | tmp = this.groups; 1186 | this.groups = {}; 1187 | groupsToSync = []; 1188 | for (i = 0, len = infos.length; i < len; i++) { 1189 | info = infos[i]; 1190 | me = { 1191 | identity: info.identity, 1192 | role: info.role 1193 | }; 1194 | o = (ref = info != null ? info.group : void 0) != null ? ref : { 1195 | id: info.id 1196 | }; 1197 | o.me = me; 1198 | op = 0; 1199 | g = (ref1 = tmp[o.id]) != null ? ref1 : null; 1200 | if (g == null) { 1201 | op = 1; 1202 | g = new bit6.Group(o.id); 1203 | } else { 1204 | delete tmp[o.id]; 1205 | } 1206 | this.groups[g.id] = g; 1207 | isUpdated = g.update(o); 1208 | if (isUpdated) { 1209 | this.emit('group', g, op); 1210 | } 1211 | this._ensureConversationForGroup(g, isUpdated); 1212 | if (isUpdated && me.role !== 'left') { 1213 | groupsToSync.push(g.id); 1214 | } 1215 | } 1216 | for (id in tmp) { 1217 | g = tmp[id]; 1218 | this.emit('group', g, -1); 1219 | } 1220 | results = []; 1221 | for (j = 0, len1 = groupsToSync.length; j < len1; j++) { 1222 | id = groupsToSync[j]; 1223 | results.push(this._loadGroupWithMembers(id, false)); 1224 | } 1225 | return results; 1226 | }; 1227 | 1228 | Client.prototype._loadGroupWithMembers = function(id, reloadMembshipsOnFail, cb) { 1229 | return this.api('/groups/' + id, { 1230 | embed: 'members' 1231 | }, (function(_this) { 1232 | return function(err, result) { 1233 | _this._processGroup(id, result); 1234 | if (err && reloadMembshipsOnFail) { 1235 | return _this._loadMe(function(err) { 1236 | return typeof cb === "function" ? cb(err) : void 0; 1237 | }); 1238 | } else { 1239 | return typeof cb === "function" ? cb(err, result) : void 0; 1240 | } 1241 | }; 1242 | })(this)); 1243 | }; 1244 | 1245 | Client.prototype._processGroup = function(id, o) { 1246 | var g, i, len, m, op, ref, ref1, ref2; 1247 | g = this.groups[id]; 1248 | if (g == null) { 1249 | console.log('syncGroup - could not find Group in local DB', id, 'data=', o); 1250 | return; 1251 | } 1252 | op = 0; 1253 | if (o == null) { 1254 | if ((ref = g.me) != null) { 1255 | ref.role = 'left'; 1256 | } 1257 | } else { 1258 | g.update(o, true); 1259 | } 1260 | if ((ref1 = g.me) != null ? ref1.identity : void 0) { 1261 | ref2 = g.members; 1262 | for (i = 0, len = ref2.length; i < len; i++) { 1263 | m = ref2[i]; 1264 | if (m.id === g.me.identity) { 1265 | g.me.role = m.role; 1266 | break; 1267 | } 1268 | } 1269 | } 1270 | this._ensureConversationForGroup(g, op >= 0); 1271 | return this.emit('group', g, op); 1272 | }; 1273 | 1274 | Client.prototype._ensureConversationForGroup = function(g, isGroupUpdated) { 1275 | var conv, convId, op; 1276 | op = 0; 1277 | convId = g.getConversationId(); 1278 | conv = this.conversations[convId]; 1279 | if (!conv) { 1280 | this.conversations[convId] = conv = new bit6.Conversation(convId); 1281 | conv.updated = g.updated; 1282 | op = 1; 1283 | } 1284 | if (op || isGroupUpdated) { 1285 | return this.emit('conversation', conv, op); 1286 | } 1287 | }; 1288 | 1289 | Client.prototype.sendTypingNotification = function(to) { 1290 | var now; 1291 | now = Date.now(); 1292 | if (now - this.lastTypingSent > 7000) { 1293 | this.lastTypingSent = now; 1294 | return this._sendNotification(to, 'typing'); 1295 | } 1296 | }; 1297 | 1298 | Client.prototype.sendNotification = function(to, type, data) { 1299 | if ((type != null ? type.length : void 0) < 1 || type.charAt(0) === '_') { 1300 | return false; 1301 | } 1302 | return this._sendNotification(to, '_' + type, data); 1303 | }; 1304 | 1305 | Client.prototype._sendNotification = function(to, type, data) { 1306 | var m, msg, ref; 1307 | msg = { 1308 | type: type, 1309 | from: this.session.identity 1310 | }; 1311 | if (data != null) { 1312 | msg.data = data; 1313 | } 1314 | m = { 1315 | to: to, 1316 | body: JSON.stringify(msg) 1317 | }; 1318 | if ((ref = this.rpc) != null) { 1319 | ref.call('verto.info', { 1320 | msg: m 1321 | }, (function(_this) { 1322 | return function(err, result) {}; 1323 | })(this)); 1324 | } 1325 | return true; 1326 | }; 1327 | 1328 | Client.prototype.startCall = function(to, opts) { 1329 | return this._createDialog(true, to, null, opts); 1330 | }; 1331 | 1332 | Client.prototype.startPhoneCall = function(phone) { 1333 | var to; 1334 | to = 'tel:' + phone; 1335 | return this.startCall(to, { 1336 | audio: true, 1337 | offnet: true 1338 | }); 1339 | }; 1340 | 1341 | Client.prototype.findDialogByCallID = function(callID) { 1342 | var c, i, len, ref, ref1; 1343 | ref = this.dialogs; 1344 | for (i = 0, len = ref.length; i < len; i++) { 1345 | c = ref[i]; 1346 | if (((ref1 = c.params) != null ? ref1.callID : void 0) === callID) { 1347 | return c; 1348 | } 1349 | } 1350 | return null; 1351 | }; 1352 | 1353 | Client.prototype.findDialogByOther = function(other) { 1354 | var c, i, len, ref; 1355 | ref = this.dialogs; 1356 | for (i = 0, len = ref.length; i < len; i++) { 1357 | c = ref[i]; 1358 | if (c.other === other) { 1359 | return c; 1360 | } 1361 | } 1362 | return null; 1363 | }; 1364 | 1365 | Client.prototype.findDialogByRdest = function(rdest) { 1366 | var c, i, len, ref, ref1; 1367 | ref = this.dialogs; 1368 | for (i = 0, len = ref.length; i < len; i++) { 1369 | c = ref[i]; 1370 | if (((ref1 = c.invite) != null ? ref1.rdest : void 0) === rdest) { 1371 | return c; 1372 | } 1373 | } 1374 | return null; 1375 | }; 1376 | 1377 | Client.prototype._deleteDialog = function(d) { 1378 | var c, i, idx, len, ref, ref1; 1379 | ref = this.dialogs; 1380 | for (idx = i = 0, len = ref.length; i < len; idx = ++i) { 1381 | c = ref[idx]; 1382 | if (c === d) { 1383 | this.dialogs.splice(idx, 1); 1384 | break; 1385 | } 1386 | } 1387 | if (this.dialogs.length === 0) { 1388 | if ((ref1 = this.capture) != null) { 1389 | ref1.stop(); 1390 | } 1391 | return this.capture = null; 1392 | } 1393 | }; 1394 | 1395 | Client.prototype._createDialog = function(outgoing, other, invite, opts) { 1396 | var c; 1397 | c = this.findDialogByOther(other); 1398 | if (c) { 1399 | return c; 1400 | } 1401 | c = new bit6.Dialog(this, outgoing, other, invite, opts); 1402 | this.dialogs.push(c); 1403 | c.on('error', (function(_this) { 1404 | return function(msg) { 1405 | return console.log('Dialog error: ', c, msg); 1406 | }; 1407 | })(this)); 1408 | c.on('end', (function(_this) { 1409 | return function() { 1410 | return _this._deleteDialog(c); 1411 | }; 1412 | })(this)); 1413 | return c; 1414 | }; 1415 | 1416 | Client.prototype._createDeviceId = function() { 1417 | return 'web-' + bit6.JsonRpc.generateGUID(); 1418 | }; 1419 | 1420 | Client.prototype._createRtc = function() { 1421 | return new bit6.Rtc(); 1422 | }; 1423 | 1424 | Client.prototype._createRtcCapture = function() { 1425 | return new bit6.RtcCapture(); 1426 | }; 1427 | 1428 | Client.prototype._ensureRtcCapture = function(opts, cb) { 1429 | if (!this.capture) { 1430 | this.capture = this._createRtcCapture(); 1431 | this.capture.on('video', (function(_this) { 1432 | return function(v, op) { 1433 | return _this._emitVideoEvent(v, null, op); 1434 | }; 1435 | })(this)); 1436 | } 1437 | return this.capture.request(opts, cb); 1438 | }; 1439 | 1440 | Client.prototype._emitVideoEvent = function(v, d, op) { 1441 | return this.emit('video', v, d, op); 1442 | }; 1443 | 1444 | Client.prototype.getNameFromIdentity = function(ident) { 1445 | var g, m, r, ref, ref1, t; 1446 | t = ident; 1447 | if (ident == null) { 1448 | console.log('getNameFromId null', ident); 1449 | return null; 1450 | } 1451 | r = ident.split(':'); 1452 | if (r.length !== 2) { 1453 | return t; 1454 | } 1455 | switch (r[0]) { 1456 | case 'usr': 1457 | case 'pstn': 1458 | case 'mailto': 1459 | case 'tel': 1460 | t = r[1]; 1461 | break; 1462 | case 'grp': 1463 | g = this.getGroup(ident); 1464 | if (!g) { 1465 | console.log('Group not found: ' + ident); 1466 | t = 'Group not found'; 1467 | } else { 1468 | t = (ref = (ref1 = g.meta) != null ? ref1.title : void 0) != null ? ref : null; 1469 | if (!t && g.members.length) { 1470 | r = (function() { 1471 | var i, len, ref2, results; 1472 | ref2 = g.members; 1473 | results = []; 1474 | for (i = 0, len = ref2.length; i < len; i++) { 1475 | m = ref2[i]; 1476 | if (m.role === 'user' || m.role === 'admin') { 1477 | results.push(this.getNameFromIdentity(m.id)); 1478 | } 1479 | } 1480 | return results; 1481 | }).call(this); 1482 | if (r.length < 4) { 1483 | t = r.join(', '); 1484 | } else { 1485 | t = r[0] + ', ' + r[1] + ' + ' + (r.length - 2) + ' more'; 1486 | } 1487 | } 1488 | if (t == null) { 1489 | t = 'Untitled Group'; 1490 | } 1491 | } 1492 | } 1493 | return t; 1494 | }; 1495 | 1496 | Client.prototype._handleRtMessage = function(m) { 1497 | var g, gid, i, j, len, len1, needToReloadMemberships, old, p, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, results; 1498 | switch (m.type) { 1499 | case 'push': 1500 | return this._handlePushRtMessage(m.data); 1501 | case 'update': 1502 | if (((ref = m.data) != null ? ref.messages : void 0) != null) { 1503 | this._processMessages(m.data.messages); 1504 | } 1505 | if (((ref1 = m.data) != null ? (ref2 = ref1.groups) != null ? ref2.length : void 0 : void 0) > 0) { 1506 | needToReloadMemberships = false; 1507 | ref3 = m.data.groups; 1508 | for (i = 0, len = ref3.length; i < len; i++) { 1509 | g = ref3[i]; 1510 | if (!this.groups[g.id]) { 1511 | needToReloadMemberships = true; 1512 | break; 1513 | } 1514 | } 1515 | if (needToReloadMemberships) { 1516 | return this._loadMe(function(err) {}); 1517 | } else { 1518 | ref4 = m.data.groups; 1519 | results = []; 1520 | for (j = 0, len1 = ref4.length; j < len1; j++) { 1521 | g = ref4[j]; 1522 | results.push(this._loadGroupWithMembers(g.id, true)); 1523 | } 1524 | return results; 1525 | } 1526 | } 1527 | break; 1528 | case 'presence': 1529 | if ((m != null ? m.from : void 0) && ((m != null ? m.data : void 0) != null)) { 1530 | old = this.presence[m.from]; 1531 | this.presence[m.from] = m.data; 1532 | if (m.data.status == null) { 1533 | this.presence[m.from].status = (ref5 = old != null ? old.status : void 0) != null ? ref5 : 'offline'; 1534 | } 1535 | p = (ref6 = (ref7 = m.data) != null ? ref7.profile : void 0) != null ? ref6 : null; 1536 | if (p && (this.groups != null)) { 1537 | ref8 = this.groups; 1538 | for (gid in ref8) { 1539 | g = ref8[gid]; 1540 | if (g._updateMemberProfile(m.from, p)) { 1541 | this.emit('group', g, 0); 1542 | } 1543 | } 1544 | } 1545 | return this.emit('notification', m); 1546 | } 1547 | break; 1548 | case 'typing': 1549 | return this.emit('notification', m); 1550 | default: 1551 | if ((m != null ? (ref9 = m.type) != null ? ref9.length : void 0 : void 0) > 1 && m.type.charAt(0) === '_') { 1552 | m.type = m.type.substring(1); 1553 | return this.emit('notification', m); 1554 | } 1555 | } 1556 | }; 1557 | 1558 | Client.prototype._handlePushRtMessage = function(d) { 1559 | var c, ch, mtype, opts; 1560 | mtype = bit6.Message.typeFromFlags(d.flags); 1561 | switch (mtype) { 1562 | case bit6.Message.INC_CALL: 1563 | ch = bit6.Message.channelFromFlags(d.flags); 1564 | opts = { 1565 | audio: (ch & bit6.Message.AUDIO) === bit6.Message.AUDIO, 1566 | video: (ch & bit6.Message.VIDEO) === bit6.Message.VIDEO, 1567 | data: (ch & bit6.Message.DATA) === bit6.Message.DATA 1568 | }; 1569 | c = this._createDialog(false, d.sender, d, opts); 1570 | return this.emit('incomingCall', c); 1571 | case bit6.Message.MISSED_CALL: 1572 | console.log('missed call from', d.sender, 'rdest=', d.rdest); 1573 | c = this.findDialogByRdest(d.rdest); 1574 | if (c != null) { 1575 | return c.hangup(); 1576 | } 1577 | break; 1578 | case bit6.Message.ACCEPTED_CALL: 1579 | return console.log('accepted call from', d.sender, 'rdest=', d.rdest); 1580 | case bit6.Message.REJECTED_CALL: 1581 | return console.log('rejected call from', d.sender, 'rdest=', d.rdest); 1582 | case 0x0100: 1583 | case 0x0200: 1584 | case 0x0300: 1585 | case 0x0400: 1586 | return this._loadMe(function(err) { 1587 | if (err) { 1588 | return console.log('LoadMsgDeltas on push done', err); 1589 | } 1590 | }); 1591 | default: 1592 | return console.log('Unknown push: ', d); 1593 | } 1594 | }; 1595 | 1596 | Client.prototype._handleRpcNotify = function(method, params) {}; 1597 | 1598 | Client.prototype._handleRpcCall = function(method, params, done) { 1599 | var c, error, ex, ref, t, x; 1600 | switch (method) { 1601 | case 'verto.info': 1602 | if ((params != null ? (ref = params.msg) != null ? ref.body : void 0 : void 0) != null) { 1603 | try { 1604 | x = JSON.parse(params.msg.body); 1605 | this._handleRtMessage(x); 1606 | } catch (error) { 1607 | ex = error; 1608 | console.log('Exception parsing JSON response verto.info ' + ex); 1609 | console.log(' -- RAW {{{' + params.msg.body + '}}}'); 1610 | } 1611 | } 1612 | break; 1613 | case 'verto.bye': 1614 | c = this.findDialogByCallID(params.callID); 1615 | if (c) { 1616 | c.handleRpcCall(method, params); 1617 | } 1618 | break; 1619 | case 'verto.invite': 1620 | t = params.caller_id_number; 1621 | x = t != null ? t.split('@') : void 0; 1622 | c = this.findDialogByOther(x != null ? x[0] : void 0); 1623 | if (c) { 1624 | c.handleRpcCall(method, params); 1625 | } 1626 | break; 1627 | case 'verto.answer': 1628 | c = this.findDialogByCallID(params.callID); 1629 | if (c) { 1630 | c.handleRpcCall(method, params); 1631 | } 1632 | } 1633 | return done(null, { 1634 | 'method': method 1635 | }); 1636 | }; 1637 | 1638 | Client.prototype._connectRt = function() { 1639 | var opts, ref, ref1, s, servers; 1640 | servers = (ref = this.session.config) != null ? (ref1 = ref.rtc) != null ? ref1.vertoServers : void 0 : void 0; 1641 | if (servers.length < 1) { 1642 | console.log('Error: no Verto servers'); 1643 | return; 1644 | } 1645 | s = servers[0]; 1646 | opts = { 1647 | wsUrl: s.url, 1648 | login: s.username, 1649 | password: s.credential, 1650 | sessid: this.session.device, 1651 | onRpcCall: (function(_this) { 1652 | return function(m, params, done) { 1653 | return _this._handleRpcCall(m, params, done); 1654 | }; 1655 | })(this), 1656 | onRpcNotify: (function(_this) { 1657 | return function(m, params) { 1658 | return _this._handleRpcNotify(m, params); 1659 | }; 1660 | })(this) 1661 | }; 1662 | this.rpc = new bit6.JsonRpc(opts); 1663 | return this.rpc.connect(); 1664 | }; 1665 | 1666 | Client.prototype._disconnectRt = function() { 1667 | var ref; 1668 | if ((ref = this.rpc) != null) { 1669 | ref.close(); 1670 | } 1671 | return this.rpc = null; 1672 | }; 1673 | 1674 | Client.prototype.api = function() { 1675 | var cb, i, params, path; 1676 | path = arguments[0], params = 3 <= arguments.length ? slice.call(arguments, 1, i = arguments.length - 1) : (i = 1, []), cb = arguments[i++]; 1677 | return this._api.apply(this, ['/app/1' + path].concat(slice.call(params), [cb])); 1678 | }; 1679 | 1680 | Client.prototype._api = function() { 1681 | var cb, data, i, m, params, path, url, xhr; 1682 | path = arguments[0], params = 3 <= arguments.length ? slice.call(arguments, 1, i = arguments.length - 1) : (i = 1, []), cb = arguments[i++]; 1683 | if (params.length === 1) { 1684 | if (typeof params[0] === 'string') { 1685 | m = params[0]; 1686 | } 1687 | if (typeof params[0] === 'object') { 1688 | data = params[0]; 1689 | } 1690 | } else if (params.length >= 2) { 1691 | m = params[0], data = params[1]; 1692 | } 1693 | if (m == null) { 1694 | m = 'get'; 1695 | } 1696 | if (data == null) { 1697 | data = {}; 1698 | } 1699 | m = m.toLowerCase(); 1700 | url = this.apiurl + path; 1701 | url += path.indexOf('?') < 0 ? '?' : '&'; 1702 | url += 'apikey=' + this.apikey; 1703 | if (m !== 'post') { 1704 | url += '&_method=' + m; 1705 | } 1706 | url += '&envelope=1'; 1707 | if (this.session.token) { 1708 | data._auth = 'bearer ' + this.session.token; 1709 | } 1710 | xhr = new XMLHttpRequest(); 1711 | xhr.open('POST', url, true); 1712 | xhr.setRequestHeader('Content-Type', 'application/json'); 1713 | xhr.onreadystatechange = function() { 1714 | var error, ex, isXhrOk, r; 1715 | if (xhr.readyState === 4) { 1716 | r = null; 1717 | try { 1718 | r = JSON.parse(xhr.response); 1719 | } catch (error) { 1720 | ex = error; 1721 | if (typeof cb === "function") { 1722 | cb({ 1723 | 'status': 501, 1724 | 'message': 'Cannot parse response', 1725 | 'data': xhr.response 1726 | }); 1727 | } 1728 | return; 1729 | } 1730 | if ((r != null ? r.status : void 0) == null) { 1731 | if (typeof cb === "function") { 1732 | cb({ 1733 | 'status': 501, 1734 | 'message': 'Incorrect envelope', 1735 | 'data': xhr.response 1736 | }); 1737 | } 1738 | return; 1739 | } 1740 | isXhrOk = xhr.status === 200 || xhr.status === 0; 1741 | if (isXhrOk && r.status >= 200 && r.status < 300) { 1742 | return typeof cb === "function" ? cb(null, r.response, r.headers) : void 0; 1743 | } else { 1744 | return typeof cb === "function" ? cb(r.response, null, r.headers) : void 0; 1745 | } 1746 | } 1747 | }; 1748 | return xhr.send(JSON.stringify(data)); 1749 | }; 1750 | 1751 | Client.prototype.urlencodeJsObject = function(obj, prefix) { 1752 | var k, p, str, v; 1753 | str = []; 1754 | str = (function() { 1755 | var results; 1756 | results = []; 1757 | for (p in obj) { 1758 | v = obj[p]; 1759 | k = prefix ? prefix + '[' + p + ']' : p; 1760 | if (typeof v === 'object') { 1761 | results.push(this.urlencodeJsObject(v, k)); 1762 | } else { 1763 | results.push(encodeURIComponent(k) + "=" + encodeURIComponent(v)); 1764 | } 1765 | } 1766 | return results; 1767 | }).call(this); 1768 | return str.join('&'); 1769 | }; 1770 | 1771 | Client.normalizeIdentityUri = function(u) { 1772 | var filter, k, matcher, pos, v; 1773 | pos = u.indexOf(':'); 1774 | if (pos < 0) { 1775 | return null; 1776 | } 1777 | k = u.substr(0, pos); 1778 | k = k.trim().toLowerCase(); 1779 | v = u.substr(pos + 1); 1780 | filter = matcher = null; 1781 | switch (k) { 1782 | case 'mailto': 1783 | v = v.toLowerCase(); 1784 | filter = /[^a-z0-9._%+-@]/; 1785 | matcher = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,8}$/; 1786 | break; 1787 | case 'grp': 1788 | filter = /[\s]/; 1789 | matcher = /[0-9a-zA-Z._]{22}/; 1790 | break; 1791 | case 'tel': 1792 | filter = /[^\\d+]/; 1793 | matcher = /^\+[1-9]{1}[0-9]{8,15}$/; 1794 | break; 1795 | case 'uid': 1796 | v = v.toLowerCase(); 1797 | filter = /[^0-9a-f-]/; 1798 | matcher = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/; 1799 | break; 1800 | case 'usr': 1801 | v = v.toLowerCase(); 1802 | filter = /[^a-z0-9.]/; 1803 | matcher = /^[a-z0-9.]+$/; 1804 | break; 1805 | default: 1806 | filter = /[^a-zA-Z0-9._%+-@]/; 1807 | } 1808 | if (filter) { 1809 | v = v.replace(filter, ''); 1810 | } 1811 | if (v.search(matcher) < 0) { 1812 | return null; 1813 | } 1814 | return k + ':' + v; 1815 | }; 1816 | 1817 | return Client; 1818 | 1819 | })(bit6.EventEmitter); 1820 | 1821 | }).call(this); 1822 | 1823 | (function() { 1824 | bit6.Message = (function() { 1825 | var CHANNEL_MASK, INCOMING, STATUS_MASK, TYPE_MASK, callStatusAsString, messageStatusAsString; 1826 | 1827 | INCOMING = 0x1000; 1828 | 1829 | STATUS_MASK = 0x000f; 1830 | 1831 | CHANNEL_MASK = 0x00f0; 1832 | 1833 | TYPE_MASK = 0x0f00; 1834 | 1835 | Message.DELETED = 0x0000; 1836 | 1837 | Message.SENDING = 0x0001; 1838 | 1839 | Message.SENT = 0x0002; 1840 | 1841 | Message.FAILED = 0x0003; 1842 | 1843 | Message.DELIVERED = 0x0004; 1844 | 1845 | Message.READ = 0x0005; 1846 | 1847 | Message.ANSWER = 0x0001; 1848 | 1849 | Message.MISSED = 0x0002; 1850 | 1851 | Message.NOANSWER = 0x0004; 1852 | 1853 | Message.SMS = 0x0010; 1854 | 1855 | Message.AUDIO = 0x0010; 1856 | 1857 | Message.VIDEO = 0x0080; 1858 | 1859 | Message.DATA = 0x0020; 1860 | 1861 | Message.TEXT = 0x0100; 1862 | 1863 | Message.ATTACH = 0x0200; 1864 | 1865 | Message.GEOLOC = 0x0300; 1866 | 1867 | Message.CUSTOM = 0x0400; 1868 | 1869 | Message.CALL = 0x0500; 1870 | 1871 | Message.INC_CALL = 0x0500; 1872 | 1873 | Message.MISSED_CALL = 0x0600; 1874 | 1875 | messageStatusAsString = { 1876 | 0x0001: 'Sending', 1877 | 0x0002: 'Sent', 1878 | 0x0003: 'Failed', 1879 | 0x0004: 'Delivered', 1880 | 0x0005: 'Read' 1881 | }; 1882 | 1883 | callStatusAsString = { 1884 | 0x0001: 'Answered', 1885 | 0x0002: 'Missed', 1886 | 0x0003: 'Failed', 1887 | 0x0004: 'No answer' 1888 | }; 1889 | 1890 | function Message(o) { 1891 | this.id = null; 1892 | this.me = null; 1893 | this.other = null; 1894 | this.flags = 0; 1895 | if (o != null) { 1896 | this.populate(o); 1897 | } 1898 | } 1899 | 1900 | Message.prototype.populate = function(o) { 1901 | var j, k, len, ref, results; 1902 | ref = ['id', 'flags', 'me', 'other', 'content', 'data', 'created', 'updated', 'deleted']; 1903 | results = []; 1904 | for (j = 0, len = ref.length; j < len; j++) { 1905 | k = ref[j]; 1906 | if ((o != null ? o[k] : void 0) != null) { 1907 | results.push(this[k] = o[k]); 1908 | } else { 1909 | results.push(void 0); 1910 | } 1911 | } 1912 | return results; 1913 | }; 1914 | 1915 | Message.prototype.updateMessage = function(t) { 1916 | if (t.deleted != null) { 1917 | this.deleted = t.deleted; 1918 | } 1919 | if (t.flags) { 1920 | this.flags = t.flags; 1921 | } 1922 | if (t.others == null) { 1923 | return; 1924 | } 1925 | if (this.others != null) { 1926 | return this._updateMessageOthers(this.others, t.others); 1927 | } else { 1928 | return this.others = t.others; 1929 | } 1930 | }; 1931 | 1932 | Message.prototype._updateMessageOthers = function(old, others) { 1933 | var i, idx, j, l, len, len1, o, ref, results, s, t; 1934 | results = []; 1935 | for (j = 0, len = others.length; j < len; j++) { 1936 | t = others[j]; 1937 | idx = -1; 1938 | for (i = l = 0, len1 = old.length; l < len1; i = ++l) { 1939 | o = old[i]; 1940 | if (o.uri === t.uri) { 1941 | idx = i; 1942 | break; 1943 | } 1944 | } 1945 | if (idx < 0) { 1946 | results.push(old.push(t)); 1947 | } else { 1948 | s = (ref = old[idx]) != null ? ref.status : void 0; 1949 | if (!s || s < t.status) { 1950 | results.push(old[idx].status = t.status); 1951 | } else { 1952 | results.push(void 0); 1953 | } 1954 | } 1955 | } 1956 | return results; 1957 | }; 1958 | 1959 | Message.prototype.incoming = function() { 1960 | return (this.flags & INCOMING) !== 0; 1961 | }; 1962 | 1963 | Message.prototype.status = function(s) { 1964 | if (s != null) { 1965 | this.flags = (this.flags & (~STATUS_MASK)) | (s & STATUS_MASK); 1966 | } 1967 | return this.flags & STATUS_MASK; 1968 | }; 1969 | 1970 | Message.prototype.channel = function(s) { 1971 | if (s != null) { 1972 | this.flags = (this.flags & (~CHANNEL_MASK)) | (s & CHANNEL_MASK); 1973 | } 1974 | return this.flags & CHANNEL_MASK; 1975 | }; 1976 | 1977 | Message.prototype.type = function(s) { 1978 | if (s != null) { 1979 | this.flags = (this.flags & (~TYPE_MASK)) | (s & TYPE_MASK); 1980 | } 1981 | return this.flags & TYPE_MASK; 1982 | }; 1983 | 1984 | Message.prototype.isCall = function() { 1985 | return (this.flags & TYPE_MASK) === Message.CALL; 1986 | }; 1987 | 1988 | Message.prototype.canMarkRead = function() { 1989 | return this.incoming() && !this.isCall() && this.status() < bit6.Message.READ; 1990 | }; 1991 | 1992 | Message.prototype.getConversationId = function() { 1993 | var convId; 1994 | convId = (this.me != null) && this.me.indexOf('grp:') === 0 ? this.me : this.other; 1995 | if (!convId) { 1996 | console.log('msgConvId is null', convId); 1997 | } 1998 | return convId; 1999 | }; 2000 | 2001 | Message.prototype.getStatusString = function() { 2002 | var r, s, t; 2003 | t = this.flags & TYPE_MASK; 2004 | s = this.flags & STATUS_MASK; 2005 | r = t === Message.CALL ? callStatusAsString : messageStatusAsString; 2006 | return r[s]; 2007 | }; 2008 | 2009 | Message.prototype.domId = function() { 2010 | return 'm' + this.id; 2011 | }; 2012 | 2013 | Message.fromDomId = function(t) { 2014 | if (t.length > 0 && t.charAt(0) === 'm') { 2015 | return t.substring(1); 2016 | } else { 2017 | return t; 2018 | } 2019 | }; 2020 | 2021 | Message.typeFromFlags = function(s) { 2022 | return s & TYPE_MASK; 2023 | }; 2024 | 2025 | Message.channelFromFlags = function(s) { 2026 | return s & CHANNEL_MASK; 2027 | }; 2028 | 2029 | Message.statusFromFlags = function(s) { 2030 | return s & STATUS_MASK; 2031 | }; 2032 | 2033 | return Message; 2034 | 2035 | })(); 2036 | 2037 | }).call(this); 2038 | 2039 | (function() { 2040 | var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 2041 | hasProp = {}.hasOwnProperty; 2042 | 2043 | bit6.Outgoing = (function(superClass) { 2044 | extend(Outgoing, superClass); 2045 | 2046 | function Outgoing(client) { 2047 | this.client = client; 2048 | Outgoing.__super__.constructor.apply(this, arguments); 2049 | this.id = bit6.JsonRpc.generateGUID(); 2050 | this.created = this.updated = Date.now(); 2051 | this.flags = bit6.Message.TEXT | bit6.Message.SENDING; 2052 | this.from(this.client.session.identity, this.client.session.displayName); 2053 | } 2054 | 2055 | Outgoing.prototype._export = function() { 2056 | var i, k, len, n, x; 2057 | n = ['id', 'flags', 'me', 'from_name', 'other', 'content', 'data']; 2058 | x = {}; 2059 | for (i = 0, len = n.length; i < len; i++) { 2060 | k = n[i]; 2061 | if (this[k]) { 2062 | x[k] = this[k]; 2063 | } 2064 | } 2065 | return x; 2066 | }; 2067 | 2068 | Outgoing.prototype.hasAttachment = function() { 2069 | return this.attachFile != null; 2070 | }; 2071 | 2072 | Outgoing.prototype.to = function(other) { 2073 | this.other = other; 2074 | return this; 2075 | }; 2076 | 2077 | Outgoing.prototype.from = function(from, name) { 2078 | this.me = from; 2079 | if (name) { 2080 | this.from_name = name; 2081 | } 2082 | return this; 2083 | }; 2084 | 2085 | Outgoing.prototype.text = function(text) { 2086 | this.content = text; 2087 | return this; 2088 | }; 2089 | 2090 | Outgoing.prototype.attach = function(f) { 2091 | this.attachFile = f; 2092 | this.flags = bit6.Message.ATTACH | this.status(); 2093 | this.progress = 0; 2094 | this.total = -1; 2095 | return this; 2096 | }; 2097 | 2098 | Outgoing.prototype.send = function(cb) { 2099 | if (!this.hasAttachment()) { 2100 | this.client._processMessage(this); 2101 | return this.client._sendMessagePost(this, cb); 2102 | } 2103 | this._loadAttachmentAndThumbnail((function(_this) { 2104 | return function(err) { 2105 | _this.client._processMessage(_this); 2106 | if (err != null) { 2107 | return typeof cb === "function" ? cb(err) : void 0; 2108 | } 2109 | return _this._getUploadParams(function(err, params) { 2110 | if (err != null) { 2111 | return typeof cb === "function" ? cb(err) : void 0; 2112 | } 2113 | return _this._uploadAttachmentAndThumbnail(params, function(err) { 2114 | if (err != null) { 2115 | return typeof cb === "function" ? cb(err) : void 0; 2116 | } 2117 | return _this.client._sendMessagePost(_this, cb); 2118 | }); 2119 | }); 2120 | }; 2121 | })(this)); 2122 | return this; 2123 | }; 2124 | 2125 | Outgoing.prototype._getUploadParams = function(cb) { 2126 | var opts, ref, ref1; 2127 | if (((ref = this.data) != null ? ref.type : void 0) == null) { 2128 | return cb('Attachment type is not specified', null); 2129 | } 2130 | opts = { 2131 | type: this.data.type 2132 | }; 2133 | opts.thumb = (ref1 = this.thumbBlob) != null ? ref1.type : void 0; 2134 | return this.client.api('/me/messages/attach', 'post', opts, cb); 2135 | }; 2136 | 2137 | Outgoing.prototype._loadAttachmentAndThumbnail = function(cb) { 2138 | return bit6.Transfer.readFileAsArrayBuffer(this.attachFile, (function(_this) { 2139 | return function(err, info, data) { 2140 | if (err) { 2141 | console.log('Read file ', info, 'err=', err); 2142 | } 2143 | if (err != null) { 2144 | return cb(err); 2145 | } 2146 | return _this._handleAttachmentFileLoaded(info, data, cb); 2147 | }; 2148 | })(this)); 2149 | }; 2150 | 2151 | Outgoing.prototype._uploadAttachmentAndThumbnail = function(params, cb) { 2152 | var f, fileUploadedSize, x; 2153 | f = this.attachFile; 2154 | fileUploadedSize = 0; 2155 | x = params.uploads.attach; 2156 | return bit6.Outgoing.uploadFile(x.endpoint, x.params, f, (function(_this) { 2157 | return function(err) { 2158 | if (err) { 2159 | console.log('Main attach uploaded err=', err); 2160 | } 2161 | if (err != null) { 2162 | return cb(err); 2163 | } 2164 | _this.data.attach = params.keys.attach; 2165 | if ((_this.data.thumb == null) || (_this.thumbBlob == null)) { 2166 | return cb(null); 2167 | } 2168 | x = params.uploads.thumb; 2169 | return bit6.Outgoing.uploadFile(x.endpoint, x.params, _this.thumbBlob, function(err) { 2170 | if (err) { 2171 | console.log('Thumb uploaded err=', err); 2172 | } 2173 | if (err != null) { 2174 | return cb(err); 2175 | } 2176 | _this.data.thumb = params.keys.thumb; 2177 | return cb(null); 2178 | }, function(val, total) { 2179 | _this.progress = val + fileUploadedSize; 2180 | _this.total = total + fileUploadedSize; 2181 | return _this.client.emit('message', _this, 0); 2182 | }); 2183 | }; 2184 | })(this), (function(_this) { 2185 | return function(val, total) { 2186 | fileUploadedSize = val; 2187 | _this.progress = val; 2188 | _this.total = total; 2189 | return _this.client.emit('message', _this, 0); 2190 | }; 2191 | })(this)); 2192 | }; 2193 | 2194 | Outgoing.prototype._handleAttachmentFileLoaded = function(info, data, cb) { 2195 | var blob, isAudio, isImage, isVideo, media, ref, srcUrl, t; 2196 | t = (ref = info != null ? info.type : void 0) != null ? ref : ''; 2197 | isImage = t.indexOf('image/') === 0; 2198 | isVideo = t.indexOf('video/') === 0; 2199 | isAudio = t.indexOf('audio/') === 0; 2200 | if (isImage || isVideo) { 2201 | blob = new Blob([data], info); 2202 | srcUrl = (window.URL || window.webkitURL).createObjectURL(blob); 2203 | media = null; 2204 | if (isImage) { 2205 | media = new Image(); 2206 | media.onload = (function(_this) { 2207 | return function() { 2208 | return _this._attachmentMediaLoaded(media, info, cb); 2209 | }; 2210 | })(this); 2211 | } else { 2212 | media = document.createElement('video'); 2213 | media.setAttribute('preload', ''); 2214 | media.setAttribute('muted', ''); 2215 | media.onloadedmetadata = (function(_this) { 2216 | return function() { 2217 | return console.log('Video meta loaded', media.videoWidth, media.videoHeight, media.duration); 2218 | }; 2219 | })(this); 2220 | media.onloadeddata = (function(_this) { 2221 | return function() { 2222 | console.log('Video loaded', media); 2223 | return _this._attachmentMediaLoaded(media, info, cb); 2224 | }; 2225 | })(this); 2226 | media.onerror = (function(_this) { 2227 | return function() { 2228 | console.log('Error on video loading. Still can send the file'); 2229 | return _this._attachmentMediaLoaded(media, info, cb); 2230 | }; 2231 | })(this); 2232 | } 2233 | return media.src = srcUrl; 2234 | } else if (isAudio) { 2235 | this.data = { 2236 | type: info.type 2237 | }; 2238 | return cb(null); 2239 | } else { 2240 | return cb('Cannot handle files with type' + t); 2241 | } 2242 | }; 2243 | 2244 | Outgoing.prototype._attachmentMediaLoaded = function(media, info, cb) { 2245 | (window.URL || window.webkitURL).revokeObjectURL(media.src); 2246 | return bit6.Outgoing.createThumbnail(media, (function(_this) { 2247 | return function(err, thumbDataUrl) { 2248 | if (err) { 2249 | console.log('Thumb created err=', err); 2250 | } 2251 | if (err != null) { 2252 | return cb(err); 2253 | } 2254 | if (thumbDataUrl != null) { 2255 | _this.thumbBlob = bit6.Transfer.dataUrlToBlob(thumbDataUrl); 2256 | } 2257 | _this.data = { 2258 | type: info.type, 2259 | attach: thumbDataUrl, 2260 | thumb: thumbDataUrl 2261 | }; 2262 | return cb(null); 2263 | }; 2264 | })(this)); 2265 | }; 2266 | 2267 | Outgoing.createThumbnail = function(media, cb) { 2268 | var canvas, ctx, dataUrl, maxHeight, maxWidth, ref, ref1, ref2, th, tw; 2269 | maxWidth = 320; 2270 | maxHeight = 320; 2271 | tw = (ref = media != null ? media.videoWidth : void 0) != null ? ref : media.width; 2272 | th = (ref1 = media != null ? media.videoHeight : void 0) != null ? ref1 : media.height; 2273 | if (tw > th) { 2274 | if (tw > maxWidth) { 2275 | th *= maxWidth / tw; 2276 | tw = maxWidth; 2277 | } 2278 | } else { 2279 | if (th > maxHeight) { 2280 | tw *= maxHeight / th; 2281 | th = maxHeight; 2282 | } 2283 | } 2284 | canvas = document.createElement('canvas'); 2285 | canvas.width = tw; 2286 | canvas.height = th; 2287 | ctx = canvas.getContext('2d'); 2288 | ctx.drawImage(media, 0, 0, tw, th); 2289 | dataUrl = canvas.toDataURL('image/jpeg'); 2290 | if (((ref2 = dataUrl.split(":")[1]) != null ? ref2.length : void 0) < 2) { 2291 | dataUrl = null; 2292 | } 2293 | return cb(null, dataUrl); 2294 | }; 2295 | 2296 | Outgoing.uploadFile = function(endpoint, params, f, cb, progressCb) { 2297 | var b; 2298 | b = new bit6.Outgoing.UploadDataBuilder(params, f); 2299 | return b.build(function(err, uploadData, contentType) { 2300 | if (err != null) { 2301 | return cb(err); 2302 | } 2303 | return bit6.Outgoing._upload(endpoint, uploadData, contentType, cb, progressCb); 2304 | }); 2305 | }; 2306 | 2307 | Outgoing._upload = function(endpoint, uploadData, contentType, cb, progressCb) { 2308 | var ref, up, xhr; 2309 | xhr = new XMLHttpRequest(); 2310 | xhr.open('POST', endpoint, true); 2311 | xhr.onload = function(e) { 2312 | if (xhr.status >= 200 && xhr.status < 300) { 2313 | return cb(null); 2314 | } else { 2315 | return cb({ 2316 | status: xhr.status, 2317 | text: xhr.statusText, 2318 | info: xhr.responseText 2319 | }); 2320 | } 2321 | }; 2322 | xhr.onerror = function(e) { 2323 | console.log('xhr error', e); 2324 | return cb(e); 2325 | }; 2326 | xhr.onabort = function(e) { 2327 | console.log('xhr cancel'); 2328 | return cb(e); 2329 | }; 2330 | xhr.onprogress = function(e) { 2331 | return console.log('xhr progress', e); 2332 | }; 2333 | up = (ref = xhr != null ? xhr.upload : void 0) != null ? ref : null; 2334 | if (up) { 2335 | up.onload = function(e) { 2336 | return console.log('xhr upload complete', e); 2337 | }; 2338 | up.onerror = function(e) { 2339 | return console.log('xhr upload error', e); 2340 | }; 2341 | up.onabort = function(e) { 2342 | return console.log('xhr upload cancel', e); 2343 | }; 2344 | up.onprogress = function(e) { 2345 | var total; 2346 | total = e.lengthComputable ? e.total : -1; 2347 | return typeof progressCb === "function" ? progressCb(e.loaded, total) : void 0; 2348 | }; 2349 | } 2350 | if (contentType != null) { 2351 | xhr.setRequestHeader('Content-Type', contentType); 2352 | } 2353 | return xhr.send(uploadData); 2354 | }; 2355 | 2356 | return Outgoing; 2357 | 2358 | })(bit6.Message); 2359 | 2360 | bit6.Outgoing.UploadDataBuilder = (function() { 2361 | function UploadDataBuilder(params, f) { 2362 | if (!this._needsFormDataShim()) { 2363 | return this._createFormData(params, f); 2364 | } 2365 | this._initMultiPart(params, f); 2366 | } 2367 | 2368 | UploadDataBuilder.prototype._createFormData = function(params, f) { 2369 | var k, v; 2370 | this.fd = new FormData(); 2371 | for (k in params) { 2372 | v = params[k]; 2373 | this.fd.append(k, v); 2374 | } 2375 | return this.fd.append('file', f); 2376 | }; 2377 | 2378 | UploadDataBuilder.prototype._initMultiPart = function(params, f) { 2379 | var k, v; 2380 | this.boundary = Array(21).join('-') + (+new Date() * (1e16 * Math.random())).toString(36); 2381 | this.parts = []; 2382 | for (k in params) { 2383 | v = params[k]; 2384 | this._append(k, v); 2385 | } 2386 | if (f instanceof File) { 2387 | return this.fileToLoad = f; 2388 | } else { 2389 | return this._append('file', f); 2390 | } 2391 | }; 2392 | 2393 | UploadDataBuilder.prototype._append = function(name, value, filename) { 2394 | this.parts.push('--' + this.boundary + '\r\nContent-Disposition: form-data; name="' + name + '"'); 2395 | if (value instanceof Blob) { 2396 | if (filename == null) { 2397 | filename = 'blob'; 2398 | } 2399 | this.parts.push('; filename="' + filename + '"\r\nContent-Type: ' + value.type); 2400 | } 2401 | this.parts.push('\r\n\r\n'); 2402 | this.parts.push(value); 2403 | return this.parts.push('\r\n'); 2404 | }; 2405 | 2406 | UploadDataBuilder.prototype.build = function(cb) { 2407 | if (this.fd != null) { 2408 | return cb(null, this.fd); 2409 | } 2410 | return this._ensureFileLoaded('file', this.fileToLoad, (function(_this) { 2411 | return function(err) { 2412 | if (err) { 2413 | return cb(err); 2414 | } 2415 | return _this._completeMultiPart(cb); 2416 | }; 2417 | })(this)); 2418 | }; 2419 | 2420 | UploadDataBuilder.prototype._ensureFileLoaded = function(name, f, cb) { 2421 | if (f == null) { 2422 | return cb(null); 2423 | } 2424 | return bit6.Transfer.readFileAsArrayBuffer(f, (function(_this) { 2425 | return function(err, info, data) { 2426 | if (err) { 2427 | return cb(err); 2428 | } 2429 | _this._append(name, new Blob([data], { 2430 | type: info.type 2431 | }, info.name)); 2432 | return cb(null); 2433 | }; 2434 | })(this)); 2435 | }; 2436 | 2437 | UploadDataBuilder.prototype._completeMultiPart = function(cb) { 2438 | var data, fr; 2439 | this.parts.push('--' + this.boundary + '--'); 2440 | data = new Blob(this.parts); 2441 | fr = new FileReader(); 2442 | fr.onload = (function(_this) { 2443 | return function() { 2444 | return cb(null, fr.result, 'multipart/form-data; boundary=' + _this.boundary); 2445 | }; 2446 | })(this); 2447 | fr.onerror = function(err) { 2448 | return cb(err); 2449 | }; 2450 | return fr.readAsArrayBuffer(data); 2451 | }; 2452 | 2453 | UploadDataBuilder.prototype._needsFormDataShim = function() { 2454 | var flag, ua, v; 2455 | ua = navigator.userAgent; 2456 | v = navigator.vendor; 2457 | flag = ~ua.indexOf('Android') && ~v.indexOf('Google') && !~ua.indexOf('Chrome'); 2458 | return flag && ua.match(/AppleWebKit\/(\d+)/).pop() <= 534; 2459 | }; 2460 | 2461 | return UploadDataBuilder; 2462 | 2463 | })(); 2464 | 2465 | }).call(this); 2466 | 2467 | (function() { 2468 | var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 2469 | hasProp = {}.hasOwnProperty; 2470 | 2471 | bit6.RtcCapture = (function(superClass) { 2472 | extend(RtcCapture, superClass); 2473 | 2474 | function RtcCapture() { 2475 | RtcCapture.__super__.constructor.apply(this, arguments); 2476 | this.options = { 2477 | audio: false, 2478 | video: false, 2479 | screen: false 2480 | }; 2481 | this.preparingScreen = false; 2482 | this.preparingMedia = false; 2483 | this.errorScreen = null; 2484 | this.errorMedia = null; 2485 | this.cbs = []; 2486 | this.localStream = null; 2487 | this.localEl = null; 2488 | this.localScreenStream = null; 2489 | } 2490 | 2491 | RtcCapture.prototype.getStreams = function(opts) { 2492 | var arr; 2493 | arr = []; 2494 | if (opts.audio || opts.video) { 2495 | if (this.localStream) { 2496 | arr.push(this.localStream); 2497 | } 2498 | } 2499 | if (opts.screen) { 2500 | if (this.localScreenStream) { 2501 | arr.push(this.localScreenStream); 2502 | } 2503 | } 2504 | return arr; 2505 | }; 2506 | 2507 | RtcCapture.prototype.request = function(opts, cb) { 2508 | return this._prepareScreenSharing(opts != null ? opts.screen : void 0, (function(_this) { 2509 | return function(err) { 2510 | var newAudio, newVideo, ref, ref1; 2511 | if (err) { 2512 | console.log('RtcCapture.request: Could not get ScreenSharing', err); 2513 | } 2514 | newAudio = (ref = opts.audio) != null ? ref : _this.options.audio; 2515 | newVideo = (ref1 = opts.video) != null ? ref1 : _this.options.video; 2516 | return _this._prepareCameraMic(newAudio, newVideo, function(err2) { 2517 | if (err2) { 2518 | console.log('RtcCapture.request: Could not get audio/video', err2); 2519 | } 2520 | return _this._check(cb); 2521 | }); 2522 | }; 2523 | })(this)); 2524 | }; 2525 | 2526 | RtcCapture.prototype._check = function(cb) { 2527 | var cb2, err, i, len, results, x; 2528 | if (this.preparingScreen || this.preparingMedia) { 2529 | this.cbs.push(cb); 2530 | return; 2531 | } 2532 | err = this.errorMedia; 2533 | if (!err && !this.localStream) { 2534 | err = this.errorScreen; 2535 | } 2536 | cb(err); 2537 | if (this.cbs.length > 0) { 2538 | x = this.cbs; 2539 | this.cbs = []; 2540 | results = []; 2541 | for (i = 0, len = x.length; i < len; i++) { 2542 | cb2 = x[i]; 2543 | results.push(cb2(err)); 2544 | } 2545 | return results; 2546 | } 2547 | }; 2548 | 2549 | RtcCapture.prototype.stop = function() { 2550 | var e; 2551 | if (this.localScreenStream) { 2552 | bit6.Rtc.stopMediaStream(this.localScreenStream); 2553 | this.localScreenStream = null; 2554 | } 2555 | if (this.localStream) { 2556 | bit6.Rtc.stopMediaStream(this.localStream); 2557 | this.localStream = null; 2558 | } 2559 | if (this.localEl) { 2560 | e = this.localEl; 2561 | this.localEl = null; 2562 | if (e.src != null) { 2563 | e.src = ''; 2564 | } 2565 | this.emit('video', e, -1); 2566 | } 2567 | return this.removeAllListeners(); 2568 | }; 2569 | 2570 | RtcCapture.prototype._getScreenSharingOpts = function(cb) { 2571 | var onmsg, opts; 2572 | if ((typeof navigator !== "undefined" && navigator !== null ? navigator.mozGetUserMedia : void 0) != null) { 2573 | opts = { 2574 | video: { 2575 | mozMediaSource: 'window', 2576 | mediaSource: 'window' 2577 | } 2578 | }; 2579 | return cb(null, opts); 2580 | } 2581 | onmsg = function(msg) { 2582 | var d; 2583 | d = msg != null ? msg.data : void 0; 2584 | console.log('WebApp.gotMessage', d); 2585 | if ((d != null ? d.state : void 0) !== 'completed') { 2586 | return; 2587 | } 2588 | console.log('WebRTC onmsg done'); 2589 | window.removeEventListener('message', onmsg, false); 2590 | opts = { 2591 | audio: false, 2592 | video: { 2593 | mandatory: { 2594 | chromeMediaSource: 'desktop', 2595 | chromeMediaSourceId: msg.data.streamId, 2596 | maxWidth: window.screen.width, 2597 | maxHeight: window.screen.height 2598 | } 2599 | } 2600 | }; 2601 | return cb(null, opts); 2602 | }; 2603 | window.addEventListener('message', onmsg, false); 2604 | return window.postMessage({ 2605 | requestId: 100, 2606 | data: ['screen', 'window'] 2607 | }, '*'); 2608 | }; 2609 | 2610 | RtcCapture.prototype._prepareScreenSharing = function(flag, cb) { 2611 | if (flag === false && this.localScreenStream) { 2612 | this.localScreenStream = null; 2613 | return cb(null); 2614 | } 2615 | if (!flag === true) { 2616 | return cb(null); 2617 | } 2618 | this.options.screen = true; 2619 | if (this.localScreenStream) { 2620 | return cb(null); 2621 | } 2622 | if (this.preparingScreen) { 2623 | return cb(null); 2624 | } 2625 | this.preparingScreen = true; 2626 | return this._getScreenSharingOpts((function(_this) { 2627 | return function(err, opts) { 2628 | if (err != null) { 2629 | _this.preparingScreen = false; 2630 | _this.errorScreen = err; 2631 | return cb(err); 2632 | } 2633 | return RtcCapture.getUserMedia(opts, function(err, stream) { 2634 | _this.preparingScreen = false; 2635 | if (err != null) { 2636 | _this.errorScreen = err; 2637 | } 2638 | if (err == null) { 2639 | _this.localScreenStream = stream; 2640 | } 2641 | return cb(err); 2642 | }); 2643 | }; 2644 | })(this)); 2645 | }; 2646 | 2647 | RtcCapture.prototype._prepareCameraMic = function(audio, video, cb) { 2648 | var opts; 2649 | if (this.options.audio === audio && this.options.video === video) { 2650 | return cb(null); 2651 | } 2652 | if (this.preparingMedia) { 2653 | return cb(null); 2654 | } 2655 | if (!audio && !video) { 2656 | this.options.audio = this.options.video = false; 2657 | this._handleLocalCameraMicStream(null); 2658 | return cb(null); 2659 | } 2660 | this.options.audio = audio; 2661 | this.options.video = video; 2662 | opts = { 2663 | audio: this.options.audio, 2664 | video: this.options.video 2665 | }; 2666 | this.preparingMedia = true; 2667 | return RtcCapture.getUserMedia(opts, (function(_this) { 2668 | return function(err, stream) { 2669 | _this.preparingMedia = false; 2670 | if (err != null) { 2671 | _this.errorMedia = err; 2672 | } 2673 | _this._handleLocalCameraMicStream(stream); 2674 | return cb(err); 2675 | }; 2676 | })(this)); 2677 | }; 2678 | 2679 | RtcCapture.prototype._handleLocalCameraMicStream = function(s) { 2680 | var e, olds, ref; 2681 | olds = this.localStream; 2682 | this.localStream = s; 2683 | if (olds && olds !== s) { 2684 | bit6.Rtc.stopMediaStream(olds); 2685 | } 2686 | e = (ref = this.localEl) != null ? ref : null; 2687 | if (this.options.video && s !== olds) { 2688 | if (!e) { 2689 | e = document.createElement('video'); 2690 | e.setAttribute('autoplay', 'true'); 2691 | e.setAttribute('muted', 'true'); 2692 | e.muted = true; 2693 | this.emit('video', e, 1); 2694 | } 2695 | return this.localEl = bit6.Rtc.attachMediaStream(e, s); 2696 | } else if (!this.options.video && (e != null)) { 2697 | this.localEl = null; 2698 | e.src = ''; 2699 | return this.emit('video', e, -1); 2700 | } 2701 | }; 2702 | 2703 | RtcCapture.getUserMedia = function(opts, cb) { 2704 | var fn, n, ref, ref1, w; 2705 | w = window; 2706 | n = navigator; 2707 | fn = null; 2708 | if (!fn && (w != null ? w.getUserMedia : void 0)) { 2709 | fn = w.getUserMedia.bind(w); 2710 | } 2711 | if (!fn && n) { 2712 | fn = (ref = (ref1 = n.getUserMedia) != null ? ref1 : n.mozGetUserMedia) != null ? ref : n.webkitGetUserMedia; 2713 | if (fn) { 2714 | fn = fn.bind(n); 2715 | } 2716 | } 2717 | if (fn == null) { 2718 | return cb('WebRTC not supported. Could not find getUserMedia()'); 2719 | } 2720 | return fn(opts, function(stream) { 2721 | return cb(null, stream); 2722 | }, cb); 2723 | }; 2724 | 2725 | return RtcCapture; 2726 | 2727 | })(bit6.EventEmitter); 2728 | 2729 | }).call(this); 2730 | 2731 | (function() { 2732 | var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 2733 | hasProp = {}.hasOwnProperty; 2734 | 2735 | bit6.Rtc = (function(superClass) { 2736 | extend(Rtc, superClass); 2737 | 2738 | function Rtc() { 2739 | return Rtc.__super__.constructor.apply(this, arguments); 2740 | } 2741 | 2742 | Rtc.prototype.init = function(outgoing, iceServers) { 2743 | this.outgoing = outgoing; 2744 | this.pcConstraints = { 2745 | optional: [ 2746 | { 2747 | DtlsSrtpKeyAgreement: true 2748 | } 2749 | ] 2750 | }; 2751 | this.pcConfig = { 2752 | iceServers: iceServers 2753 | }; 2754 | this.pc = null; 2755 | this.remoteEls = {}; 2756 | this.outgoingTransfer = null; 2757 | this.incomingTransfer = null; 2758 | this.bufferedIceCandidates = []; 2759 | this.bufferedIceCandidatesDone = false; 2760 | this.bufferedOfferAnswer = null; 2761 | this.hadIceForAudio = false; 2762 | return this.hadIceForVideo = false; 2763 | }; 2764 | 2765 | Rtc.prototype.update = function(capture, opts, remoteOpts) { 2766 | var m, sdpOpts; 2767 | this.options = opts; 2768 | if (this.outgoing && this._preparePeerConnection(capture)) { 2769 | if (this.options.data && (this.dc == null)) { 2770 | this._createDataChannel(); 2771 | } 2772 | sdpOpts = {}; 2773 | if (remoteOpts.audio || remoteOpts.video) { 2774 | m = {}; 2775 | if (remoteOpts.audio) { 2776 | m.OfferToReceiveAudio = true; 2777 | } 2778 | if (remoteOpts.video) { 2779 | m.OfferToReceiveVideo = true; 2780 | } 2781 | sdpOpts.mandatory = m; 2782 | } 2783 | return this.pc.createOffer((function(_this) { 2784 | return function(offer) { 2785 | return _this._setLocalAndSendOfferAnswer(offer); 2786 | }; 2787 | })(this), (function(_this) { 2788 | return function(err) { 2789 | return console.log('CreateOffer error', err); 2790 | }; 2791 | })(this), sdpOpts); 2792 | } 2793 | }; 2794 | 2795 | Rtc.prototype.stop = function() { 2796 | var e, id, ref, ref1, ref2; 2797 | ref = this.remoteEls; 2798 | for (id in ref) { 2799 | e = ref[id]; 2800 | if (e.src != null) { 2801 | e.src = ''; 2802 | } 2803 | this._removeDomElement(e); 2804 | } 2805 | this.remoteEls = {}; 2806 | if ((ref1 = this.dc) != null) { 2807 | ref1.close(); 2808 | } 2809 | this.dc = null; 2810 | if ((ref2 = this.pc) != null) { 2811 | ref2.close(); 2812 | } 2813 | return this.pc = null; 2814 | }; 2815 | 2816 | Rtc.prototype.getRemoteTrackKinds = function() { 2817 | var hasAudio, hasVideo, i, j, len, len1, ref, ref1, ref2, s, ss, t; 2818 | hasVideo = false; 2819 | hasAudio = false; 2820 | ss = (ref = (ref1 = this.pc) != null ? typeof ref1.getRemoteStreams === "function" ? ref1.getRemoteStreams() : void 0 : void 0) != null ? ref : []; 2821 | for (i = 0, len = ss.length; i < len; i++) { 2822 | s = ss[i]; 2823 | ref2 = s.getTracks(); 2824 | for (j = 0, len1 = ref2.length; j < len1; j++) { 2825 | t = ref2[j]; 2826 | if (t.kind === 'video' && !t.muted) { 2827 | hasVideo = true; 2828 | } 2829 | if (t.kind === 'audio') { 2830 | hasAudio = true; 2831 | } 2832 | } 2833 | } 2834 | return { 2835 | audio: hasAudio, 2836 | video: hasVideo 2837 | }; 2838 | }; 2839 | 2840 | Rtc.prototype._preparePeerConnection = function(capture) { 2841 | var localStreams; 2842 | if (this.pc == null) { 2843 | this.pc = this._createPeerConnection(); 2844 | } 2845 | if (this.pc == null) { 2846 | return false; 2847 | } 2848 | localStreams = capture.getStreams(this.options); 2849 | if ((typeof window !== "undefined" && window !== null ? window.mozRTCPeerConnection : void 0) != null) { 2850 | this._mozSyncLocalStreams(this.pc, localStreams); 2851 | } else { 2852 | this._syncLocalStreams(this.pc, localStreams); 2853 | } 2854 | return true; 2855 | }; 2856 | 2857 | Rtc.prototype._syncLocalStreams = function(pc, localStreams) { 2858 | var i, j, k, len, len1, ref, results, s, toRemove; 2859 | toRemove = {}; 2860 | ref = pc.getLocalStreams(); 2861 | for (i = 0, len = ref.length; i < len; i++) { 2862 | s = ref[i]; 2863 | toRemove[s.id] = s; 2864 | } 2865 | for (j = 0, len1 = localStreams.length; j < len1; j++) { 2866 | s = localStreams[j]; 2867 | if (toRemove[s.id]) { 2868 | delete toRemove[s.id]; 2869 | } else { 2870 | pc.addStream(s); 2871 | } 2872 | } 2873 | results = []; 2874 | for (k in toRemove) { 2875 | s = toRemove[k]; 2876 | results.push(pc.removeStream(s)); 2877 | } 2878 | return results; 2879 | }; 2880 | 2881 | Rtc.prototype._mozSyncLocalStreams = function(pc, localStreams) { 2882 | var i, j, k, l, len, len1, len2, ref, ref1, results, s, t, toRemove; 2883 | toRemove = {}; 2884 | ref = pc.getSenders(); 2885 | for (i = 0, len = ref.length; i < len; i++) { 2886 | s = ref[i]; 2887 | toRemove[s.track.id] = s; 2888 | } 2889 | for (j = 0, len1 = localStreams.length; j < len1; j++) { 2890 | s = localStreams[j]; 2891 | ref1 = s.getTracks(); 2892 | for (l = 0, len2 = ref1.length; l < len2; l++) { 2893 | t = ref1[l]; 2894 | if (toRemove[t.id]) { 2895 | delete toRemove[t.id]; 2896 | } else { 2897 | pc.addTrack(t, s); 2898 | } 2899 | } 2900 | } 2901 | results = []; 2902 | for (k in toRemove) { 2903 | s = toRemove[k]; 2904 | results.push(pc.removeTrack(s)); 2905 | } 2906 | return results; 2907 | }; 2908 | 2909 | Rtc.prototype._createPeerConnection = function() { 2910 | var PeerConnection, error, ex, pc, ref, ref1; 2911 | try { 2912 | PeerConnection = (ref = (ref1 = window.RTCPeerConnection) != null ? ref1 : window.mozRTCPeerConnection) != null ? ref : window.webkitRTCPeerConnection; 2913 | pc = new PeerConnection(this.pcConfig, this.pcConstraints); 2914 | pc.onicecandidate = (function(_this) { 2915 | return function(evt) { 2916 | return _this._handleIceCandidate(evt.candidate); 2917 | }; 2918 | })(this); 2919 | pc.ondatachannel = (function(_this) { 2920 | return function(evt) { 2921 | return _this._createDataChannel(evt.channel); 2922 | }; 2923 | })(this); 2924 | pc.onaddstream = (function(_this) { 2925 | return function(evt) { 2926 | return _this._handleRemoteStreamAdded(evt.stream); 2927 | }; 2928 | })(this); 2929 | pc.onremovestream = (function(_this) { 2930 | return function(evt) { 2931 | return _this._handleRemoteStreamRemoved(evt.stream); 2932 | }; 2933 | })(this); 2934 | pc.onaddtrack = (function(_this) { 2935 | return function(evt) { 2936 | return console.log('onaddtrack', evt); 2937 | }; 2938 | })(this); 2939 | pc.onremovetrack = (function(_this) { 2940 | return function(evt) { 2941 | return console.log('onremovetrack', evt); 2942 | }; 2943 | })(this); 2944 | return pc; 2945 | } catch (error) { 2946 | ex = error; 2947 | console.log('Failed to create PeerConnection, exception: ', ex); 2948 | return null; 2949 | } 2950 | }; 2951 | 2952 | Rtc.prototype._createDataChannel = function(dc) { 2953 | var opts; 2954 | if (!dc) { 2955 | opts = { 2956 | ordered: true 2957 | }; 2958 | dc = this.pc.createDataChannel('mydc', opts); 2959 | } 2960 | this.dc = dc; 2961 | dc.binaryType = 'arraybuffer'; 2962 | dc.onopen = (function(_this) { 2963 | return function() { 2964 | return _this._handleDcOpen(); 2965 | }; 2966 | })(this); 2967 | dc.onclose = (function(_this) { 2968 | return function() { 2969 | return _this._handleDcClose(); 2970 | }; 2971 | })(this); 2972 | dc.onerror = (function(_this) { 2973 | return function(err) { 2974 | return _this._handleDcError(err); 2975 | }; 2976 | })(this); 2977 | return dc.onmessage = (function(_this) { 2978 | return function(evt) { 2979 | return _this._handleDcMessage(evt); 2980 | }; 2981 | })(this); 2982 | }; 2983 | 2984 | Rtc.prototype._handleDcOpen = function() { 2985 | return this.emit('dcOpen'); 2986 | }; 2987 | 2988 | Rtc.prototype._handleDcClose = function() { 2989 | if (this.outgoingTransfer || this.incomingTransfer) { 2990 | return this._handleDcError('DataChannel closed'); 2991 | } 2992 | }; 2993 | 2994 | Rtc.prototype._handleDcError = function(err) { 2995 | var i, len, ref, tr; 2996 | console.log("Data Channel Error:", err); 2997 | ref = [this.outgoingTransfer, this.incomingTransfer]; 2998 | for (i = 0, len = ref.length; i < len; i++) { 2999 | tr = ref[i]; 3000 | if (tr) { 3001 | tr.err = err; 3002 | } 3003 | if (tr) { 3004 | this.emit('transfer', tr); 3005 | } 3006 | } 3007 | return this.outgoingTransfer = this.incomingTransfer = null; 3008 | }; 3009 | 3010 | Rtc.prototype._handleDcMessage = function(evt) { 3011 | var d, done, info; 3012 | d = evt.data; 3013 | if (this.incomingTransfer) { 3014 | if (d.byteLength != null) { 3015 | done = this.incomingTransfer._gotChunk(d); 3016 | this.emit('transfer', this.incomingTransfer); 3017 | if (done) { 3018 | return this.incomingTransfer = null; 3019 | } 3020 | } else { 3021 | return console.log('Error: incoming transfer data', d); 3022 | } 3023 | } else { 3024 | if (d.byteLength != null) { 3025 | return console.log('Error: incoming transfer not created for:', d); 3026 | } else { 3027 | info = JSON.parse(d); 3028 | if (info) { 3029 | this.incomingTransfer = new bit6.Transfer(false, info); 3030 | return this.emit('transfer', this.incomingTransfer); 3031 | } else { 3032 | return console.log('Error: could not parse incoming transfer info:', d); 3033 | } 3034 | } 3035 | } 3036 | }; 3037 | 3038 | Rtc.prototype._handleIceCandidate = function(c) { 3039 | var base, idx; 3040 | if ((c != null ? c.candidate : void 0) != null) { 3041 | idx = c.sdpMLineIndex; 3042 | if ((base = this.bufferedIceCandidates)[idx] == null) { 3043 | base[idx] = []; 3044 | } 3045 | this.bufferedIceCandidates[idx].push(c); 3046 | return this.bufferedIceCandidatesDone = false; 3047 | } else { 3048 | this.bufferedIceCandidatesDone = true; 3049 | return this._maybeSendOfferAnswer(); 3050 | } 3051 | }; 3052 | 3053 | Rtc.prototype._maybeSendOfferAnswer = function() { 3054 | var offerAnswer; 3055 | if (this.bufferedOfferAnswer && this.bufferedIceCandidatesDone) { 3056 | offerAnswer = this._mergeSdp(this.bufferedOfferAnswer, this.bufferedIceCandidates); 3057 | this.bufferedOfferAnswer = null; 3058 | this.bufferedIceCandidates = []; 3059 | if (this.options.audio) { 3060 | this.hadIceForAudio = true; 3061 | } 3062 | if (this.options.video) { 3063 | this.hadIceForVideo = true; 3064 | } 3065 | if ((this.options.audio && !this.hadIceForAudio) || (this.options.video && !this.hadIceForVideo)) { 3066 | this.bufferedIceCandidatesDone = false; 3067 | } 3068 | if (offerAnswer) { 3069 | return this.emit('offerAnswer', offerAnswer); 3070 | } 3071 | } 3072 | }; 3073 | 3074 | Rtc.prototype._mergeSdp = function(offerAnswer, candidatesByMlineIndex) { 3075 | var chunk, chunks, end, i, idx, len, sdp, start; 3076 | if (candidatesByMlineIndex.length === 0) { 3077 | return offerAnswer; 3078 | } 3079 | sdp = offerAnswer.sdp; 3080 | chunks = []; 3081 | start = 0; 3082 | while ((end = sdp.indexOf('m=', start + 1)) >= 0) { 3083 | chunks.push(sdp.substring(start, end)); 3084 | start = end; 3085 | } 3086 | chunks.push(sdp.substring(start)); 3087 | sdp = ''; 3088 | for (idx = i = 0, len = chunks.length; i < len; idx = ++i) { 3089 | chunk = chunks[idx]; 3090 | if (chunk.indexOf('m=audio') === 0) { 3091 | this.hadIceForAudio = true; 3092 | } 3093 | if (chunk.indexOf('m=video') === 0) { 3094 | this.hadIceForVideo = true; 3095 | } 3096 | sdp += chunk; 3097 | if (idx > 0 && (candidatesByMlineIndex[idx - 1] != null)) { 3098 | sdp += this._iceCandidatesToSdp(candidatesByMlineIndex[idx - 1]); 3099 | } 3100 | } 3101 | offerAnswer.sdp = sdp; 3102 | return offerAnswer; 3103 | }; 3104 | 3105 | Rtc.prototype._iceCandidatesToSdp = function(arr) { 3106 | var c, i, len, t; 3107 | t = ''; 3108 | for (i = 0, len = arr.length; i < len; i++) { 3109 | c = arr[i]; 3110 | t += 'a=' + c.candidate + '\r\n'; 3111 | } 3112 | return t; 3113 | }; 3114 | 3115 | Rtc.prototype._handleRemoteStreamAdded = function(s) { 3116 | var e, hasAudio, hasVideo, i, len, ref, ref1, t; 3117 | s.onaddtrack = (function(_this) { 3118 | return function(ee) { 3119 | return _this._handleRemoteTrackAdded(ee.target, ee.track); 3120 | }; 3121 | })(this); 3122 | s.onremovetrack = (function(_this) { 3123 | return function(ee) { 3124 | return _this._handleRemoteTrackRemoved(ee.target, ee.track); 3125 | }; 3126 | })(this); 3127 | hasVideo = false; 3128 | hasAudio = false; 3129 | ref = s.getTracks(); 3130 | for (i = 0, len = ref.length; i < len; i++) { 3131 | t = ref[i]; 3132 | if (t.kind === 'video' && (!t.muted)) { 3133 | hasVideo = true; 3134 | } 3135 | if (t.kind === 'audio') { 3136 | hasAudio = true; 3137 | } 3138 | } 3139 | e = null; 3140 | if (hasVideo) { 3141 | e = document.createElement('video'); 3142 | this.emit('video', e, 1); 3143 | } else if (hasAudio) { 3144 | e = document.createElement('audio'); 3145 | if ((typeof window !== "undefined" && window !== null ? window.webrtcDetectedType : void 0) === 'plugin') { 3146 | if (typeof document !== "undefined" && document !== null) { 3147 | if ((ref1 = document.body) != null) { 3148 | ref1.appendChild(e); 3149 | } 3150 | } 3151 | } 3152 | } 3153 | if (e) { 3154 | e.setAttribute('autoplay', 'true'); 3155 | e = bit6.Rtc.attachMediaStream(e, s); 3156 | } 3157 | return this.remoteEls[s.id] = e; 3158 | }; 3159 | 3160 | Rtc.prototype._handleRemoteStreamRemoved = function(s) { 3161 | var e; 3162 | s.onaddtrack = null; 3163 | s.onremovetrack = null; 3164 | e = this.remoteEls[s.id]; 3165 | delete this.remoteEls[s.id]; 3166 | if (e != null) { 3167 | if (e.src != null) { 3168 | e.src = ''; 3169 | } 3170 | return this._removeDomElement(e); 3171 | } 3172 | }; 3173 | 3174 | Rtc.prototype._handleRemoteTrackAdded = function(s, t) { 3175 | if (t.kind === 'video') { 3176 | this._handleRemoteStreamRemoved(s); 3177 | return this._handleRemoteStreamAdded(s); 3178 | } 3179 | }; 3180 | 3181 | Rtc.prototype._handleRemoteTrackRemoved = function(s, t) { 3182 | if (t.kind === 'video') { 3183 | this._handleRemoteStreamRemoved(s); 3184 | return this._handleRemoteStreamAdded(s); 3185 | } 3186 | }; 3187 | 3188 | Rtc.prototype._removeDomElement = function(e) { 3189 | var i, isAudio, len, nodeName, p, ref, ref1, ref2; 3190 | nodeName = e != null ? (ref = e.nodeName) != null ? typeof ref.toLowerCase === "function" ? ref.toLowerCase() : void 0 : void 0 : void 0; 3191 | isAudio = false; 3192 | if ('object' === nodeName) { 3193 | if (e.children) { 3194 | ref1 = e.children; 3195 | for (i = 0, len = ref1.length; i < len; i++) { 3196 | p = ref1[i]; 3197 | if ('tag' === p.name && 'audio' === p.value) { 3198 | isAudio = true; 3199 | } 3200 | } 3201 | } 3202 | } else if ('audio' === nodeName) { 3203 | isAudio = true; 3204 | } 3205 | if (isAudio) { 3206 | return (ref2 = e.parentNode) != null ? typeof ref2.removeChild === "function" ? ref2.removeChild(e) : void 0 : void 0; 3207 | } else { 3208 | return this.emit('video', e, -1); 3209 | } 3210 | }; 3211 | 3212 | Rtc.prototype._setLocalAndSendOfferAnswer = function(offerAnswer) { 3213 | return this.pc.setLocalDescription(offerAnswer, (function(_this) { 3214 | return function() { 3215 | _this.bufferedOfferAnswer = { 3216 | type: offerAnswer.type, 3217 | sdp: offerAnswer.sdp 3218 | }; 3219 | return _this._maybeSendOfferAnswer(); 3220 | }; 3221 | })(this), function(err) { 3222 | return console.log('Error setting PeerConnection localDescription', err, offerAnswer); 3223 | }); 3224 | }; 3225 | 3226 | Rtc.prototype.gotRemoteOfferAnswer = function(msg, capture) { 3227 | var SessionDescription, offerAnswer, ref; 3228 | SessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription; 3229 | offerAnswer = new SessionDescription(msg); 3230 | switch (msg.type) { 3231 | case 'offer': 3232 | this._preparePeerConnection(capture); 3233 | this.pc.setRemoteDescription(offerAnswer, function() { 3234 | return true; 3235 | }, function(err) { 3236 | return console.log('Error setting PeerConnection remoteDescription for offer', err, offerAnswer); 3237 | }); 3238 | return this.pc.createAnswer((function(_this) { 3239 | return function(answer) { 3240 | return _this._setLocalAndSendOfferAnswer(answer); 3241 | }; 3242 | })(this), (function(_this) { 3243 | return function(err) { 3244 | return true; 3245 | }; 3246 | })(this)); 3247 | case 'answer': 3248 | return (ref = this.pc) != null ? ref.setRemoteDescription(offerAnswer, function() { 3249 | return true; 3250 | }, function(err) { 3251 | return console.log('Error setting PeerConnection remoteDescription for answer', err, offerAnswer); 3252 | }) : void 0; 3253 | } 3254 | }; 3255 | 3256 | Rtc.prototype.gotHangup = function(msg) { 3257 | return this.stop(); 3258 | }; 3259 | 3260 | Rtc.prototype.startOutgoingTransfer = function(tr) { 3261 | var chunk, data, delay, intervalId, sent, total; 3262 | if (this.outgoingTransfer) { 3263 | return false; 3264 | } 3265 | if (!this.dc) { 3266 | return false; 3267 | } 3268 | if (this.dc.readyState !== 'open') { 3269 | return false; 3270 | } 3271 | this.outgoingTransfer = tr; 3272 | this.dc.send(JSON.stringify(tr.info)); 3273 | data = tr.data; 3274 | delay = 10; 3275 | chunk = 16384; 3276 | sent = 0; 3277 | total = data.byteLength; 3278 | intervalId = 0; 3279 | return intervalId = setInterval((function(_this) { 3280 | return function() { 3281 | var clear, end, ref; 3282 | clear = false; 3283 | tr = _this.outgoingTransfer; 3284 | if (!tr || tr.err) { 3285 | clear = true; 3286 | } 3287 | if (!clear) { 3288 | if (((ref = _this.dc) != null ? ref.bufferedAmount : void 0) > chunk * 50) { 3289 | return; 3290 | } 3291 | end = sent + chunk; 3292 | if (end > total) { 3293 | end = total; 3294 | } 3295 | _this.dc.send(data.slice(sent, end)); 3296 | sent = end; 3297 | tr.progress = sent; 3298 | clear = sent >= total; 3299 | } 3300 | if (clear) { 3301 | _this.outgoingTransfer = null; 3302 | clearInterval(intervalId); 3303 | } 3304 | if (tr) { 3305 | return _this.emit('transfer', tr); 3306 | } 3307 | }; 3308 | })(this), delay); 3309 | }; 3310 | 3311 | Rtc.attachMediaStream = function(elem, stream) { 3312 | if ((typeof window !== "undefined" && window !== null ? window.attachMediaStream : void 0) != null) { 3313 | return window.attachMediaStream(elem, stream); 3314 | } 3315 | if ((elem != null ? elem.srcObject : void 0) != null) { 3316 | elem.srcObject = stream; 3317 | } else if ((elem != null ? elem.mozSrcObject : void 0) != null) { 3318 | elem.mozSrcObject = stream; 3319 | } else if ((elem != null ? elem.src : void 0) != null) { 3320 | elem.src = window.URL.createObjectURL(stream); 3321 | } else { 3322 | console.log('Error attaching stream to element', elem); 3323 | } 3324 | return elem; 3325 | }; 3326 | 3327 | Rtc.stopMediaStream = function(s) { 3328 | var i, len, ref, results, t; 3329 | if (s.stop) { 3330 | return s.stop(); 3331 | } else if (s.getTracks) { 3332 | ref = s.getTracks(); 3333 | results = []; 3334 | for (i = 0, len = ref.length; i < len; i++) { 3335 | t = ref[i]; 3336 | results.push(t != null ? t.stop() : void 0); 3337 | } 3338 | return results; 3339 | } 3340 | }; 3341 | 3342 | return Rtc; 3343 | 3344 | })(bit6.EventEmitter); 3345 | 3346 | }).call(this); 3347 | 3348 | (function() { 3349 | var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 3350 | hasProp = {}.hasOwnProperty, 3351 | slice = [].slice; 3352 | 3353 | bit6.Session = (function(superClass) { 3354 | extend(Session, superClass); 3355 | 3356 | Session.prototype._sprops = ['authenticated', 'authInfo', 'config', 'device', 'identity', 'displayName', 'token', 'userid']; 3357 | 3358 | function Session(client) { 3359 | this.client = client; 3360 | Session.__super__.constructor.apply(this, arguments); 3361 | this._clear(); 3362 | } 3363 | 3364 | Session.prototype._clear = function() { 3365 | var i, len, n, ref; 3366 | ref = this._sprops; 3367 | for (i = 0, len = ref.length; i < len; i++) { 3368 | n = ref[i]; 3369 | this[n] = null; 3370 | } 3371 | return this.authenticated = false; 3372 | }; 3373 | 3374 | Session.prototype.logout = function(cb) { 3375 | return this.client._onBeforeLogout((function(_this) { 3376 | return function(err) { 3377 | if (err) { 3378 | return typeof cb === "function" ? cb(err) : void 0; 3379 | } 3380 | _this._clear(); 3381 | _this.emit('deauth'); 3382 | return typeof cb === "function" ? cb(null) : void 0; 3383 | }; 3384 | })(this)); 3385 | }; 3386 | 3387 | Session.prototype.getAuthInfo = function(cb) { 3388 | if (this.authInfo) { 3389 | return cb(null, this.authInfo); 3390 | } 3391 | return this.api('/info', (function(_this) { 3392 | return function(err, info) { 3393 | if (err) { 3394 | return cb(err); 3395 | } 3396 | _this.authInfo = info; 3397 | return cb(err, info); 3398 | }; 3399 | })(this)); 3400 | }; 3401 | 3402 | Session.prototype.login = function(data, cb) { 3403 | data.identity = bit6.Client.normalizeIdentityUri(data.identity); 3404 | return this._auth('login', data, cb); 3405 | }; 3406 | 3407 | Session.prototype.signup = function(data, cb) { 3408 | data.identity = bit6.Client.normalizeIdentityUri(data.identity); 3409 | return this._auth('signup', data, cb); 3410 | }; 3411 | 3412 | Session.prototype.anonymous = function(cb) { 3413 | return this._auth('anonymous', {}, cb); 3414 | }; 3415 | 3416 | Session.prototype.external = function(token, cb) { 3417 | return this._auth('external', { 3418 | token: token 3419 | }, cb); 3420 | }; 3421 | 3422 | Session.prototype.oauth = function(provider, opts, cb) { 3423 | this._ensureOauthRedirectUri(opts); 3424 | return this.getAuthInfo((function(_this) { 3425 | return function(err, info) { 3426 | if (err) { 3427 | return cb(err); 3428 | } 3429 | return _this._auth(provider, opts, cb); 3430 | }; 3431 | })(this)); 3432 | }; 3433 | 3434 | Session.prototype.oauth1_redirect = function(provider, opts, cb) { 3435 | this._ensureOauthRedirectUri(opts); 3436 | return this.api('/' + provider, 'POST', opts, cb); 3437 | }; 3438 | 3439 | Session.prototype._ensureOauthRedirectUri = function(opts) { 3440 | var redirectUrl, ref, ref1, ref2; 3441 | redirectUrl = (ref = opts.redirect_uri) != null ? ref : opts.redirectUri; 3442 | if (redirectUrl == null) { 3443 | return opts.redirect_uri = typeof window !== "undefined" && window !== null ? (ref1 = window.location) != null ? (ref2 = ref1.href) != null ? ref2.split('?')[0] : void 0 : void 0 : void 0; 3444 | } 3445 | }; 3446 | 3447 | Session.prototype.refresh = function(cb) { 3448 | return this._auth('refresh_token', {}, cb); 3449 | }; 3450 | 3451 | Session.prototype.save = function() { 3452 | var i, len, n, ref, x; 3453 | x = {}; 3454 | ref = this._sprops; 3455 | for (i = 0, len = ref.length; i < len; i++) { 3456 | n = ref[i]; 3457 | x[n] = this[n]; 3458 | } 3459 | return x; 3460 | }; 3461 | 3462 | Session.prototype.resume = function(x, cb) { 3463 | var i, len, n, ref, ref1; 3464 | ref = this._sprops; 3465 | for (i = 0, len = ref.length; i < len; i++) { 3466 | n = ref[i]; 3467 | this[n] = (ref1 = x != null ? x[n] : void 0) != null ? ref1 : null; 3468 | } 3469 | return this.refresh(cb); 3470 | }; 3471 | 3472 | Session.prototype._auth = function(type, data, cb) { 3473 | if (data == null) { 3474 | data = {}; 3475 | } 3476 | if (this.device == null) { 3477 | this.device = this.client._createDeviceId(); 3478 | } 3479 | data.device = this.device; 3480 | data.embed = 'config'; 3481 | return this.api('/' + type, 'POST', data, (function(_this) { 3482 | return function(err, result) { 3483 | var i, k, len, ref; 3484 | if (err) { 3485 | ref = ['config', 'identity', 'token', 'userid']; 3486 | for (i = 0, len = ref.length; i < len; i++) { 3487 | k = ref[i]; 3488 | delete _this[k]; 3489 | } 3490 | return cb(err); 3491 | } 3492 | return _this._authDone(result, cb); 3493 | }; 3494 | })(this)); 3495 | }; 3496 | 3497 | Session.prototype._authDone = function(data, cb) { 3498 | var apikey2, claims, claimsStr, error, ex, i, k, len, r, ref, ref1, ref2, uid, x; 3499 | if (data.token == null) { 3500 | return cb('Jwt token is missing'); 3501 | } 3502 | try { 3503 | r = data.token.split('.'); 3504 | claimsStr = r != null ? r[1] : void 0; 3505 | if (claimsStr != null) { 3506 | claims = JSON.parse(bit6.Session.base64urlDecode(claimsStr)); 3507 | } 3508 | } catch (error) { 3509 | ex = error; 3510 | console.log('Error parsing Jwt claims', r[1]); 3511 | } 3512 | x = claims != null ? (ref = claims.sub) != null ? ref.split('@') : void 0 : void 0; 3513 | if ((x != null) && x.length === 2) { 3514 | uid = x[0], apikey2 = x[1]; 3515 | } 3516 | if (!((uid != null) && (apikey2 != null))) { 3517 | return cb('Jwt token has invalid format'); 3518 | } 3519 | this.authenticated = true; 3520 | ref1 = ['config', 'identity', 'token', 'userid']; 3521 | for (i = 0, len = ref1.length; i < len; i++) { 3522 | k = ref1[i]; 3523 | this[k] = (ref2 = data[k]) != null ? ref2 : this[k]; 3524 | } 3525 | this.client._onLogin(cb); 3526 | return this.emit('auth'); 3527 | }; 3528 | 3529 | Session.prototype.api = function() { 3530 | var cb, i, params, path, ref; 3531 | path = arguments[0], params = 3 <= arguments.length ? slice.call(arguments, 1, i = arguments.length - 1) : (i = 1, []), cb = arguments[i++]; 3532 | return (ref = this.client)._api.apply(ref, ['/auth/1' + path].concat(slice.call(params), [cb])); 3533 | }; 3534 | 3535 | Session.base64urlEncode = function(s) { 3536 | s = btoa(s); 3537 | s = s.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); 3538 | return s; 3539 | }; 3540 | 3541 | Session.base64urlDecode = function(s) { 3542 | s += new Array(4 - ((s.length + 3) & 3)).join('='); 3543 | s = s.replace(/\-/g, '+').replace(/_/g, '/'); 3544 | s = atob(s); 3545 | return s; 3546 | }; 3547 | 3548 | return Session; 3549 | 3550 | })(bit6.EventEmitter); 3551 | 3552 | }).call(this); 3553 | 3554 | (function() { 3555 | bit6.Transfer = (function() { 3556 | function Transfer(outgoing, info1, data) { 3557 | var ref; 3558 | this.outgoing = outgoing; 3559 | this.info = info1; 3560 | this.progress = 0; 3561 | this.total = (ref = this.info.size) != null ? ref : 0; 3562 | this.err = null; 3563 | this.data = data != null ? data : null; 3564 | if (!this.outgoing) { 3565 | this.buf = []; 3566 | } 3567 | } 3568 | 3569 | Transfer.prototype.pending = function() { 3570 | return this.outgoing && this.data && !this.err && this.progress === 0; 3571 | }; 3572 | 3573 | Transfer.prototype.completed = function() { 3574 | return this.progress === this.total && this.data && !this.err; 3575 | }; 3576 | 3577 | Transfer.prototype.percentage = function() { 3578 | if (!this.total) { 3579 | return 0; 3580 | } 3581 | return (this.progress * 100 / this.total).toFixed(2); 3582 | }; 3583 | 3584 | Transfer.prototype.json = function(o) { 3585 | var arr, i, j, k, ref, ref1, t; 3586 | if (o) { 3587 | this.info.type = 'application/json'; 3588 | t = JSON.stringify(o); 3589 | arr = new Uint8Array(t.length); 3590 | for (i = j = 0, ref = t.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { 3591 | arr[i] = t.charCodeAt(i); 3592 | } 3593 | this.info.size = arr.byteLength; 3594 | this.data = arr.buffer; 3595 | } else { 3596 | if (this.info.type === !'application/json') { 3597 | return null; 3598 | } 3599 | arr = new Uint8Array(this.data); 3600 | t = ''; 3601 | for (i = k = 0, ref1 = arr.length; 0 <= ref1 ? k < ref1 : k > ref1; i = 0 <= ref1 ? ++k : --k) { 3602 | t += String.fromCharCode(arr[i]); 3603 | } 3604 | o = JSON.parse(t); 3605 | } 3606 | return o; 3607 | }; 3608 | 3609 | Transfer.prototype._ensureSourceData = function(cb) { 3610 | if (this.data != null) { 3611 | return cb(null); 3612 | } 3613 | return bit6.Transfer.readFileAsArrayBuffer(this.info, (function(_this) { 3614 | return function(err, info2, data) { 3615 | if (err != null) { 3616 | _this.err = err; 3617 | } 3618 | if ((data != null) && (info2 != null)) { 3619 | _this.info = info2; 3620 | } 3621 | if (data != null) { 3622 | _this.data = data; 3623 | } 3624 | return cb(err); 3625 | }; 3626 | })(this)); 3627 | }; 3628 | 3629 | Transfer.prototype._gotChunk = function(chunk) { 3630 | this.buf.push(chunk); 3631 | this.progress += chunk.byteLength; 3632 | if (this.progress < this.total) { 3633 | return false; 3634 | } 3635 | this._prepareReceivedData(); 3636 | return true; 3637 | }; 3638 | 3639 | Transfer.prototype._prepareReceivedData = function() { 3640 | var b, j, len, offset, ref, tmp; 3641 | tmp = new Uint8Array(this.progress); 3642 | offset = 0; 3643 | ref = this.buf; 3644 | for (j = 0, len = ref.length; j < len; j++) { 3645 | b = ref[j]; 3646 | tmp.set(new Uint8Array(b), offset); 3647 | offset += b.byteLength; 3648 | } 3649 | this.buf = null; 3650 | return this.data = tmp.buffer; 3651 | }; 3652 | 3653 | Transfer.readFileAsArrayBuffer = function(f, cb) { 3654 | var reader; 3655 | reader = new FileReader(); 3656 | reader.onload = function(e) { 3657 | var data, info; 3658 | if (reader.readyState === FileReader.DONE) { 3659 | data = e.target.result; 3660 | console.log(f.name + ' - read ' + data.byteLength + 'b'); 3661 | info = { 3662 | name: f.name, 3663 | type: f.type, 3664 | size: f.size 3665 | }; 3666 | return cb(null, info, data); 3667 | } 3668 | }; 3669 | reader.onerror = function(e) { 3670 | return cb(e); 3671 | }; 3672 | reader.onabort = function(e) { 3673 | return cb(e); 3674 | }; 3675 | return reader.readAsArrayBuffer(f); 3676 | }; 3677 | 3678 | Transfer.dataUrlToBlob = function(url) { 3679 | var ab, arr, contentType, i, j, marker, parts, raw, rawLength, ref; 3680 | marker = ';base64,'; 3681 | raw = null; 3682 | contentType = null; 3683 | if (url.indexOf(marker) < 0) { 3684 | parts = url.split(','); 3685 | contentType = parts[0].split(':')[1]; 3686 | raw = decodeURIComponent(parts[1]); 3687 | } else { 3688 | parts = url.split(marker); 3689 | contentType = parts[0].split(':')[1]; 3690 | raw = atob(parts[1]); 3691 | rawLength = raw.length; 3692 | ab = new ArrayBuffer(rawLength); 3693 | arr = new Uint8Array(ab); 3694 | for (i = j = 0, ref = rawLength; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { 3695 | arr[i] = raw.charCodeAt(i); 3696 | } 3697 | raw = ab; 3698 | } 3699 | return new Blob([raw], { 3700 | type: contentType 3701 | }); 3702 | }; 3703 | 3704 | return Transfer; 3705 | 3706 | })(); 3707 | 3708 | }).call(this); 3709 | -------------------------------------------------------------------------------- /www/index-bit6.js: -------------------------------------------------------------------------------- 1 | cordova.require("cordova-plugin-bit6.Bit6SDK"); 2 | 3 | var pushWrappers = cordova.require("cordova-plugin-bit6.PushWrappers"); 4 | var utils = cordova.require("cordova-plugin-bit6.Utils"); 5 | 6 | exports.init = function(opts) { 7 | 8 | var b6 = new bit6.Client(opts); 9 | // Init DeviceId and Push support 10 | initPushService(b6, opts.pushSupport); 11 | 12 | // Just for iOS devices, making WebRTC methods available using iosrtc plugin 13 | if (window.device.platform === 'iOS' && cordova.plugins.iosrtc) { 14 | cordova.plugins.iosrtc.registerGlobals(); 15 | } 16 | 17 | return b6; 18 | }; 19 | 20 | function initPushService(b6, customPushSupport) { 21 | // Short name for the platform for Bit6 22 | var plat = device.platform.toLowerCase(); 23 | 24 | // Is this a web-based emulator? 25 | var isEmu = false; 26 | if (navigator.simulator) { 27 | isEmu = true; 28 | } 29 | if (!isEmu && device.isVirtual) { 30 | isEmu = true; 31 | } 32 | 33 | // TODO: Are we sure that simulator is always web-based? 34 | // and not for example device emulator running on a PC? 35 | if (isEmu) { 36 | plat = 'web'; 37 | } else if (plat == 'ios') { 38 | plat = 'ios'; 39 | } else if (plat == 'android') { 40 | plat = 'and'; 41 | } else { 42 | plat = 'web'; 43 | } 44 | 45 | // DeviceId support 46 | if (device && device.platform && device.uuid) { 47 | b6._createDeviceId = function() { 48 | return plat + '-' + device.uuid; 49 | }; 50 | } 51 | 52 | // Push support 53 | pushSupport = null; 54 | if (customPushSupport) { 55 | pushSupport = customPushSupport; 56 | } 57 | // TODO: Move this logic to pushWrappers since it contains both wrapper implementations? 58 | // Add a factory method in the wrappers file? 59 | else if (typeof PushNotification !== 'undefined' && PushNotification.init) { //PluginPush 60 | pushSupport = pushWrappers.pluginPushWrapper; 61 | } else if (window.plugins && window.plugins.pushNotification && window.plugins.pushNotification.register) { //legacy PushPlugin 62 | pushSupport = pushWrappers.legacyPushPluginWrapper; 63 | } 64 | 65 | // Helper function for submitting the push key to the server 66 | var sendPushkeyToServer = function(pushkey, cb) { 67 | var data = { 68 | id: b6.session.device, 69 | pushkey: pushkey, 70 | platform: plat, 71 | sdkv: bit6.Client.version 72 | }; 73 | console.log('Send pushkey: ' + data.pushkey); 74 | console.log('Send device id: ' + data.id); 75 | b6.api('/me/devices', 'POST', data, function(err, res) { 76 | console.log('Dev update err=' + err); 77 | console.log('Dev update res=' + res); 78 | 79 | if (err) { 80 | alert('Dev update err=' + err); 81 | } 82 | 83 | if (cb) { 84 | cb(); 85 | } 86 | }); 87 | }; 88 | 89 | var sendApnsPushkeyToServer = function(key) { 90 | //For iOS adding prefix p_/d_ to the push token for Bit6 server to use correct APNS 91 | // TODO: Do we want to call isApnsProduction() just once during init? 92 | utils.isApnsProduction(function(isProd) { 93 | var prefix = isProd ? 'p_' : 'd_'; 94 | sendPushkeyToServer(prefix + key); 95 | }); 96 | }; 97 | 98 | // Listen to the completion of the auth procedure. 99 | // At that time we will have more info about the push config 100 | b6.session.on('auth', function() { 101 | if (!pushSupport) { 102 | alert("Missing push plugin. \nPlease install one of the push plugins known to Bit6, or provide your push implementation"); 103 | return; 104 | } 105 | 106 | var pushOpts = { 107 | senderId: '' 108 | }; 109 | 110 | if (plat === 'and') { 111 | if (b6.session.config.gcm === undefined) { 112 | alert("There will be errors as long as you don't enable GCM push notifications for this app."); 113 | return; 114 | } 115 | pushOpts.senderId = b6.session.config.gcm.senderId; 116 | } 117 | 118 | pushSupport.register(pushOpts, onPushRegistration, onPushNotification); 119 | }); 120 | 121 | 122 | function onPushNotification(data) { 123 | if (!data) { 124 | alert("Bit6: Push data is missing!") 125 | return; 126 | } 127 | b6._handlePushRtMessage(data); 128 | } 129 | 130 | function onPushRegistration(pushToken) { 131 | if (plat == 'ios') { 132 | sendApnsPushkeyToServer(pushToken); 133 | } else { 134 | sendPushkeyToServer(pushToken); 135 | } 136 | } 137 | 138 | //Override _onBeforeLogout to remove the push key on the server 139 | var sdkBeforeLogout = b6._onBeforeLogout; 140 | b6._onBeforeLogout = function () { 141 | sendPushkeyToServer('', function() { 142 | sdkBeforeLogout.call(b6, function() {}); 143 | }); 144 | }; 145 | } 146 | -------------------------------------------------------------------------------- /www/phonegap-websocket.js: -------------------------------------------------------------------------------- 1 | var exec = require('cordova/exec'); 2 | 3 | function hasWebSocket() { 4 | var m = /Android ([0-9]+)\.([0-9]+)/i.exec(navigator.userAgent); 5 | var hasConstructor = typeof WebSocket === "function"; 6 | // WebRTC will be available either in Crosswalk or Web emulator 7 | // We assume if it is available, WebSocket will also be present 8 | // TODO: Check for PeerConnection instead? 9 | var hasWebRTC = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia || window.getUserMedia; 10 | 11 | console.log('WebSocket hasConstructor: ' + hasConstructor + ', hasWebRTC: ' + (hasWebRTC ? true : false) ); 12 | console.log('WebSocket m: ' + m); 13 | 14 | if (hasWebRTC) { return true } 15 | 16 | // This is not an Android Cordova 17 | if (!m) { return hasConstructor; } 18 | 19 | var x = parseInt(m[1], 10); 20 | var y = parseInt(m[2], 10); 21 | 22 | return hasConstructor && (x > 4 || (x === 4 && y >= 4)); 23 | } 24 | 25 | hasWebSocket() || (function() { 26 | 27 | console.log('WebSocket - building native'); 28 | 29 | var websocketId = 0; 30 | 31 | // Websocket constructor 32 | var WebSocket = window.WebSocket = function(url, protocols, options) { 33 | 34 | var socket = this; 35 | options || (options = {}); 36 | options.headers || (options.headers = {}); 37 | 38 | if (Array.isArray(protocols)) { 39 | protocols = protocols.join(','); 40 | } 41 | 42 | if (protocols) { 43 | options.headers["Sec-WebSocket-Protocol"] = protocols; 44 | } 45 | 46 | this.events = []; 47 | this.options = options; 48 | this.url = url; 49 | this.readyState = WebSocket.CONNECTING; 50 | this.socketId = "_cordova_websocket_" + websocketId; 51 | websocketId += 1; 52 | 53 | cordova.exec( 54 | function (event) { 55 | socket._handleEvent(event); 56 | }, 57 | function (event) { 58 | socket._handleEvent(event); 59 | }, "WebSocket", "connect", [ this.socketId, url, options ]); 60 | }; 61 | 62 | WebSocket.prototype = { 63 | send: function (data) { 64 | if (this.readyState == WebSocket.CLOSED || 65 | this.readyState == WebSocket.CLOSING) return; 66 | 67 | if (data instanceof ArrayBuffer) { 68 | data = arrayBufferToArray(data); 69 | } 70 | else if (data instanceof Blob) { 71 | var reader = new FileReader(); 72 | reader.onloadend = function() { 73 | this.send(reader.result); 74 | }.bind(this); 75 | 76 | reader.readAsArrayBuffer(data); 77 | return; 78 | } 79 | 80 | cordova.exec(noob, noob, "WebSocket", "send", [ this.socketId, data ]); 81 | }, 82 | 83 | close: function () { 84 | if (this.readyState == WebSocket.CLOSED || 85 | this.readyState == WebSocket.CLOSING) return; 86 | 87 | this.readyState = WebSocket.CLOSING; 88 | cordova.exec(noob, noob, "WebSocket", "close", [ this.socketId ]); 89 | }, 90 | 91 | addEventListener: function (type, listener, useCapture) { 92 | this.events[type] || (this.events[type] = []); 93 | this.events[type].push(listener); 94 | }, 95 | 96 | removeEventListener: function (type, listener, useCapture) { 97 | var events; 98 | 99 | if (!this.events[type]) return; 100 | 101 | events = this.events[type]; 102 | 103 | for (var i = events.length - 1; i >= 0; --i) { 104 | if (events[i] === listener) { 105 | events.splice(i, 1); 106 | return; 107 | } 108 | } 109 | }, 110 | 111 | dispatchEvent: function (event) { 112 | var handler; 113 | var events = this.events[event.type] || []; 114 | 115 | for (var i = 0, l = events.length; i < l; i++) { 116 | //FIX (russaa): call event handler in context of the WebSocket instance 117 | events[i].call(this, event); 118 | } 119 | 120 | handler = this["on" + event.type]; 121 | //FIX (russaa): call event handler in context of the WebSocket instance 122 | if (handler) handler.call(this, event); 123 | }, 124 | 125 | _handleEvent: function (event) { 126 | this.readyState = event.readyState; 127 | 128 | if (event.type == "message") { 129 | event = createMessageEvent("message", event.data); 130 | } 131 | else if (event.type == "messageBinary") { 132 | var result = arrayToBinaryType(event.data, this.binaryType); 133 | event = createBinaryMessageEvent("message", result); 134 | } 135 | else { 136 | event = createSimpleEvent(event.type); 137 | } 138 | 139 | this.dispatchEvent(event); 140 | 141 | if (event.readyState == WebSocket.CLOSING || 142 | event.readyState == WebSocket.CLOSED) { 143 | // cleanup socket from internal map 144 | cordova.exec(noob, noob, "WebSocket", "close", [ this.socketId ]); 145 | } 146 | } 147 | }; 148 | 149 | WebSocket.prototype.CONNECTING = WebSocket.CONNECTING = 0; 150 | WebSocket.prototype.OPEN = WebSocket.OPEN = 1; 151 | WebSocket.prototype.CLOSING = WebSocket.CLOSING = 2; 152 | WebSocket.prototype.CLOSED = WebSocket.CLOSED = 3; 153 | 154 | 155 | // helpers 156 | 157 | function noob () {} 158 | 159 | function createSimpleEvent(type) { 160 | var event = document.createEvent("Event"); 161 | event.initEvent(type, false, false); 162 | return event; 163 | } 164 | 165 | function createMessageEvent(type, data) { 166 | var event = document.createEvent("MessageEvent"); 167 | event.initMessageEvent("message", false, false, data, null, null, window, null); 168 | return event; 169 | } 170 | 171 | function createBinaryMessageEvent(type, data) { 172 | // This does not match the WebSocket spec. The Event is suppose to be a 173 | // MessageEvent. But in Android WebView, MessageEvent.initMessageEvent() 174 | // makes a mess of ArrayBuffers. This should work with most clients, as 175 | // long as they don't do something odd with the event. The type is 176 | // correctly set to "message", so client event routing logic should work. 177 | var event = document.createEvent("Event"); 178 | 179 | event.initEvent("message", false, false); 180 | event.data = data; 181 | return event; 182 | } 183 | 184 | function arrayBufferToArray(arrayBuffer) { 185 | var output = []; 186 | var utf8arr = new Uint8Array(arrayBuffer); 187 | 188 | for ( var i = 0, l = utf8arr.length; i < l; i++) { 189 | output.push(utf8arr[i]); 190 | } 191 | 192 | return output; 193 | } 194 | 195 | function arrayToBinaryType(array, binaryType) { 196 | var result = null; 197 | 198 | if (!array || !array.length) return result; 199 | 200 | var typedArr = new Uint8Array(array.length); 201 | 202 | typedArr.set(array); 203 | 204 | if (binaryType === "arraybuffer") { 205 | result = typedArr.buffer; 206 | } 207 | else if (binaryType === "blob") { 208 | if (window.WebKitBlobBuilder) { 209 | var builder = new WebKitBlobBuilder(); 210 | builder.append(typedArr.buffer); 211 | result = builder.getBlob("application/octet-stream"); 212 | } 213 | else { 214 | result = new Blob([ bytearray ], { 215 | type: 'application/octet-stream' 216 | }); 217 | } 218 | } 219 | 220 | return result; 221 | } 222 | 223 | Array.isArray = Array.isArray || function (args) { 224 | return Object.prototype.toString.call(args) === "[object Array]"; 225 | }; 226 | 227 | window.ArrayBuffer = window.ArrayBuffer || function () { 228 | throw "ArrayBuffer not supported on this platform"; 229 | }; 230 | 231 | window.Blob = window.Blob || function () { 232 | throw "Blob not supported on this platform"; 233 | }; 234 | 235 | }()); 236 | 237 | if (typeof module != 'undefined' && module.exports) { 238 | module.exports = window.WebSocket; 239 | } 240 | 241 | -------------------------------------------------------------------------------- /www/push-wrappers.js: -------------------------------------------------------------------------------- 1 | 2 | // Helper functions for success / error responses 3 | var errh = function(r) { 4 | console.log('Push Error: ' + r); 5 | }; 6 | var okh = function(r) { 7 | console.log('Push Success: ' + r); 8 | }; 9 | 10 | 11 | var pluginPushWrapper = { 12 | _push: null, 13 | register: function(opts, registrationCallback, notificationCallback) { 14 | //var params = opts; 15 | var params = { 16 | android: { 17 | senderID: opts.senderId, 18 | clearNotifications: 'true' 19 | }, 20 | ios: { 21 | alert: 'true', 22 | badge: 'true', 23 | sound: 'true', 24 | clearBadge: 'true' 25 | } 26 | }; 27 | 28 | _push = PushNotification.init(params); 29 | _push.on('registration', function(data) { 30 | registrationCallback(data.registrationId); 31 | }); 32 | 33 | _push.on('notification', function(data) { 34 | if (!(data && data.additionalData)) { 35 | alert("Missing Push Data!"); 36 | return; 37 | } 38 | 39 | var bit6Data = data.additionalData.data ? data.additionalData.data : data.additionalData; 40 | 41 | if (data.count) { 42 | _push.setApplicationIconBadgeNumber(okh, errh, data.count); 43 | } 44 | 45 | notificationCallback(bit6Data); 46 | }); 47 | } 48 | }; 49 | 50 | var legacyPushPluginWrapper = { 51 | _push: null, 52 | register: function(opts, registrationCallback, notificationCallback) { 53 | 54 | var params = ''; 55 | if (opts && opts.senderId) { //Android 56 | params = { 57 | senderID: opts.senderId, 58 | ecb: 'onPushGCM' 59 | }; 60 | window.plugins.pushNotification.register(okh, errh, params); 61 | } else { 62 | params = { 63 | badge: 'true', 64 | sound: 'true', 65 | alert: 'true', 66 | ecb: 'onPushAPN' 67 | }; 68 | //Hack to keep the old format of push payload, otherwise telerik/legacy plugin will fail 69 | bit6.Client.version = '0.9.6' 70 | 71 | window.plugins.pushNotification.register(registrationCallback, errh, params); 72 | } 73 | 74 | // Note that the function has to be in global scope! 75 | window.onPushGCM = function(e) { 76 | console.log('Got GCM: ' + e.event); 77 | switch (e.event) { 78 | // Registered with GCM 79 | case 'registered': 80 | if (e.regid && e.regid.length > 0) { 81 | // Notify the server about this GCM regId 82 | registrationCallback(e.regid); 83 | } 84 | break; 85 | // Got a push message 86 | case 'message': 87 | notificationCallback(e.payload); 88 | break; 89 | case 'error': 90 | alert('GCM err: ' + e.msg) 91 | break; 92 | default: 93 | console.log('GCM unknown event: ' + e.event) 94 | break; 95 | } 96 | } 97 | 98 | // Got notification from APN 99 | // Note that the function has to be in global scope! 100 | window.onPushAPN = function(e) { 101 | //FIXME: Getting error when gets push while app is running 102 | if (e.badge) { 103 | window.plugins.pushNotification.setApplicationIconBadgeNumber(okh, e.badge); 104 | } 105 | notificationCallback(e); 106 | } 107 | } 108 | }; 109 | 110 | 111 | exports.pluginPushWrapper = pluginPushWrapper; 112 | exports.legacyPushPluginWrapper = legacyPushPluginWrapper; 113 | 114 | -------------------------------------------------------------------------------- /www/utils.js: -------------------------------------------------------------------------------- 1 | var exec = require('cordova/exec'); 2 | 3 | function isApnsProduction(onSuccess) { 4 | if (cordova.platformId === 'ios') { //This is needed only for ios. 5 | exec(onSuccess, null, 'UtilsPlugin', 'isApnsProduction', []); 6 | } 7 | else { 8 | onSuccess(false); 9 | } 10 | } 11 | 12 | exports.isApnsProduction = isApnsProduction; 13 | --------------------------------------------------------------------------------