├── srv-SocketChat.zip ├── Starter_Project.zip ├── SocketChat.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ ├── simon.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── gabriel.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcuserdata │ ├── simon.xcuserdatad │ │ └── xcschemes │ │ │ ├── xcschememanagement.plist │ │ │ └── SocketChat.xcscheme │ └── gabriel.xcuserdatad │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── SocketChat.xcscheme └── project.pbxproj ├── README.md ├── SocketChat ├── UserCell.swift ├── ChatCell.swift ├── BaseCell.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── AppDelegate.swift ├── SocketIOManager.swift ├── UserCell.xib ├── UsersViewController.swift ├── ChatCell.xib └── ChatViewController.swift └── Source ├── SocketEnginePacketType.swift ├── SocketTypes.swift ├── SocketEngineClient.swift ├── SocketEventHandler.swift ├── SocketAnyEvent.swift ├── SocketIOClientStatus.swift ├── SocketFixUTF8.swift ├── SocketAckEmitter.swift ├── SocketClientSpec.swift ├── SocketLogger.swift ├── SocketAckManager.swift ├── SocketStringReader.swift ├── SocketEngineWebsocket.swift ├── SocketEngineSpec.swift ├── SwiftRegex.swift ├── SocketParsable.swift ├── SocketIOClientOption.swift ├── SocketEnginePollable.swift ├── SocketPacket.swift ├── SocketIOClient.swift └── SocketEngine.swift /srv-SocketChat.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcoda/SocketIOChat/HEAD/srv-SocketChat.zip -------------------------------------------------------------------------------- /Starter_Project.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcoda/SocketIOChat/HEAD/Starter_Project.zip -------------------------------------------------------------------------------- /SocketChat.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SocketChat.xcodeproj/project.xcworkspace/xcuserdata/simon.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcoda/SocketIOChat/HEAD/SocketChat.xcodeproj/project.xcworkspace/xcuserdata/simon.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SocketChat.xcodeproj/project.xcworkspace/xcuserdata/gabriel.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcoda/SocketIOChat/HEAD/SocketChat.xcodeproj/project.xcworkspace/xcuserdata/gabriel.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building an iOS Chat App Using Socket IO 2 | 3 | In order to show to you how to make use of the Socket.IO library (client) for iOS and how to achieve real-time communication with a server, I decided that our best hit would be to create a chat application as the demo app of this tutorial. 4 | 5 | For the full tutorial, please check out http://www.appcoda.com/socket-io-chat-app/ 6 | -------------------------------------------------------------------------------- /SocketChat/UserCell.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // UserCell.swift 4 | // SocketChat 5 | // 6 | // Created by Gabriel Theodoropoulos on 1/31/16. 7 | // Copyright © 2016 AppCoda. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | class UserCell: BaseCell { 13 | 14 | override func awakeFromNib() { 15 | super.awakeFromNib() 16 | // Initialization code 17 | } 18 | 19 | override func setSelected(selected: Bool, animated: Bool) { 20 | super.setSelected(selected, animated: animated) 21 | 22 | // Configure the view for the selected state 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /SocketChat.xcodeproj/xcuserdata/simon.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SocketChat.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C075D7181C5E0E81009F9044 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SocketChat.xcodeproj/xcuserdata/gabriel.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SocketChat.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C075D7181C5E0E81009F9044 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SocketChat/ChatCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatCell.swift 3 | // SocketChat 4 | // 5 | // Created by Gabriel Theodoropoulos on 1/31/16. 6 | // Copyright © 2016 AppCoda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ChatCell: BaseCell { 12 | 13 | @IBOutlet weak var lblChatMessage: UILabel! 14 | 15 | @IBOutlet weak var lblMessageDetails: UILabel! 16 | 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | // Initialization code 21 | 22 | } 23 | 24 | override func setSelected(selected: Bool, animated: Bool) { 25 | super.setSelected(selected, animated: animated) 26 | 27 | // Configure the view for the selected state 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /SocketChat/BaseCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCell.swift 3 | // SocketChat 4 | // 5 | // Created by Gabriel Theodoropoulos on 1/31/16. 6 | // Copyright © 2016 AppCoda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseCell: UITableViewCell { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | 17 | separatorInset = UIEdgeInsetsZero 18 | preservesSuperviewLayoutMargins = false 19 | layoutMargins = UIEdgeInsetsZero 20 | layoutIfNeeded() 21 | 22 | // Set the selection style to None. 23 | selectionStyle = UITableViewCellSelectionStyle.None 24 | } 25 | 26 | override func setSelected(selected: Bool, animated: Bool) { 27 | super.setSelected(selected, animated: animated) 28 | 29 | // Configure the view for the selected state 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /SocketChat/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Source/SocketEnginePacketType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEnginePacketType.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 10/7/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | @objc public enum SocketEnginePacketType: Int { 29 | case Open, Close, Ping, Pong, Message, Upgrade, Noop 30 | } -------------------------------------------------------------------------------- /Source/SocketTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketTypes.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 4/8/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public typealias AckCallback = ([AnyObject]) -> Void 28 | public typealias NormalCallback = ([AnyObject], SocketAckEmitter) -> Void 29 | public typealias OnAckCallback = (timeoutAfter: UInt64, callback: AckCallback) -> Void 30 | 31 | enum Either { 32 | case Left(E) 33 | case Right(V) 34 | } 35 | -------------------------------------------------------------------------------- /Source/SocketEngineClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEngineClient.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/19/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | @objc public protocol SocketEngineClient { 29 | func engineDidError(reason: String) 30 | func engineDidClose(reason: String) 31 | optional func engineDidOpen(reason: String) 32 | func parseEngineMessage(msg: String) 33 | func parseEngineBinaryData(data: NSData) 34 | } 35 | -------------------------------------------------------------------------------- /Source/SocketEventHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventHandler.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/18/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | struct SocketEventHandler { 28 | let event: String 29 | let id: NSUUID 30 | let callback: NormalCallback 31 | 32 | func executeCallback(items: [AnyObject], withAck ack: Int, withSocket socket: SocketIOClient) { 33 | callback(items, SocketAckEmitter(socket: socket, ackNum: ack)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/SocketAnyEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAnyEvent.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/28/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | @objc public final class SocketAnyEvent: NSObject { 28 | public let event: String! 29 | public let items: NSArray? 30 | override public var description: String { 31 | return "SocketAnyEvent: Event: \(event) items: \(items ?? nil)" 32 | } 33 | 34 | init(event: String, items: NSArray?) { 35 | self.event = event 36 | self.items = items 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SocketChat/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SocketChat/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | NSAppTransportSecurity 47 | 48 | NSAllowsArbitraryLoads 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Source/SocketIOClientStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketIOClientStatus.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 8/14/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | @objc public enum SocketIOClientStatus: Int, CustomStringConvertible { 28 | case NotConnected, Closed, Connecting, Connected, Reconnecting 29 | 30 | public var description: String { 31 | switch self { 32 | case NotConnected: 33 | return "Not Connected" 34 | case Closed: 35 | return "Closed" 36 | case Connecting: 37 | return "Connecting" 38 | case Connected: 39 | return "Connected" 40 | case Reconnecting: 41 | return "Reconnecting" 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Source/SocketFixUTF8.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketFixUTF8.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/16/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | func fixDoubleUTF8(string: String) -> String { 29 | if let utf8 = string.dataUsingEncoding(NSISOLatin1StringEncoding), 30 | latin1 = NSString(data: utf8, encoding: NSUTF8StringEncoding) { 31 | return latin1 as String 32 | } else { 33 | return string 34 | } 35 | } 36 | 37 | func doubleEncodeUTF8(string: String) -> String { 38 | if let latin1 = string.dataUsingEncoding(NSUTF8StringEncoding), 39 | utf8 = NSString(data: latin1, encoding: NSISOLatin1StringEncoding) { 40 | return utf8 as String 41 | } else { 42 | return string 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Source/SocketAckEmitter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAckEmitter.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 9/16/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public final class SocketAckEmitter: NSObject { 28 | let socket: SocketIOClient 29 | let ackNum: Int 30 | 31 | init(socket: SocketIOClient, ackNum: Int) { 32 | self.socket = socket 33 | self.ackNum = ackNum 34 | } 35 | 36 | public func with(items: AnyObject...) { 37 | guard ackNum != -1 else { return } 38 | 39 | socket.emitAck(ackNum, withItems: items) 40 | } 41 | 42 | public func with(items: [AnyObject]) { 43 | guard ackNum != -1 else { return } 44 | 45 | socket.emitAck(ackNum, withItems: items) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Source/SocketClientSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketClientSpec.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/3/16. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | protocol SocketClientSpec: class { 26 | var nsp: String { get set } 27 | var waitingData: [SocketPacket] { get set } 28 | 29 | func didConnect() 30 | func didDisconnect(reason: String) 31 | func didError(reason: String) 32 | func handleAck(ack: Int, data: [AnyObject]) 33 | func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int) 34 | func joinNamespace(namespace: String) 35 | } 36 | 37 | extension SocketClientSpec { 38 | func didError(reason: String) { 39 | DefaultSocketLogger.Logger.error("%@", type: "SocketIOClient", args: reason) 40 | 41 | handleEvent("error", data: [reason], isInternalMessage: true, withAck: -1) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /SocketChat/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SocketChat 4 | // 5 | // Created by Gabriel Theodoropoulos on 1/31/16. 6 | // Copyright © 2016 AppCoda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | 31 | SocketIOManager.sharedInstance.closeConnection() 32 | } 33 | 34 | func applicationWillEnterForeground(application: UIApplication) { 35 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 36 | } 37 | 38 | func applicationDidBecomeActive(application: UIApplication) { 39 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 40 | 41 | SocketIOManager.sharedInstance.establishConnection() 42 | } 43 | 44 | func applicationWillTerminate(application: UIApplication) { 45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 46 | } 47 | 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /Source/SocketLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketLogger.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 4/11/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public protocol SocketLogger: class { 28 | /// Whether to log or not 29 | var log: Bool {get set} 30 | 31 | /// Normal log messages 32 | func log(message: String, type: String, args: AnyObject...) 33 | 34 | /// Error Messages 35 | func error(message: String, type: String, args: AnyObject...) 36 | } 37 | 38 | public extension SocketLogger { 39 | func log(message: String, type: String, args: AnyObject...) { 40 | abstractLog("LOG", message: message, type: type, args: args) 41 | } 42 | 43 | func error(message: String, type: String, args: AnyObject...) { 44 | abstractLog("ERROR", message: message, type: type, args: args) 45 | } 46 | 47 | private func abstractLog(logType: String, message: String, type: String, args: [AnyObject]) { 48 | guard log else { return } 49 | 50 | let newArgs = args.map({arg -> CVarArgType in String(arg)}) 51 | let replaced = String(format: message, arguments: newArgs) 52 | 53 | NSLog("%@ %@: %@", logType, type, replaced) 54 | } 55 | } 56 | 57 | class DefaultSocketLogger: SocketLogger { 58 | static var Logger: SocketLogger = DefaultSocketLogger() 59 | 60 | var log = false 61 | } 62 | -------------------------------------------------------------------------------- /Source/SocketAckManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAckManager.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 4/3/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | private struct SocketAck: Hashable, Equatable { 28 | let ack: Int 29 | var callback: AckCallback! 30 | var hashValue: Int { 31 | return ack.hashValue 32 | } 33 | 34 | init(ack: Int) { 35 | self.ack = ack 36 | } 37 | 38 | init(ack: Int, callback: AckCallback) { 39 | self.ack = ack 40 | self.callback = callback 41 | } 42 | } 43 | 44 | private func <(lhs: SocketAck, rhs: SocketAck) -> Bool { 45 | return lhs.ack < rhs.ack 46 | } 47 | 48 | private func ==(lhs: SocketAck, rhs: SocketAck) -> Bool { 49 | return lhs.ack == rhs.ack 50 | } 51 | 52 | struct SocketAckManager { 53 | private var acks = Set(minimumCapacity: 1) 54 | 55 | mutating func addAck(ack: Int, callback: AckCallback) { 56 | acks.insert(SocketAck(ack: ack, callback: callback)) 57 | } 58 | 59 | mutating func executeAck(ack: Int, items: [AnyObject]) { 60 | let callback = acks.remove(SocketAck(ack: ack)) 61 | 62 | dispatch_async(dispatch_get_main_queue()) { 63 | callback?.callback(items) 64 | } 65 | } 66 | 67 | mutating func timeoutAck(ack: Int) { 68 | let callback = acks.remove(SocketAck(ack: ack)) 69 | 70 | dispatch_async(dispatch_get_main_queue()) { 71 | callback?.callback(["NO ACK"]) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Source/SocketStringReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketStringReader.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Lukas Schmidt on 07.09.15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | struct SocketStringReader { 26 | let message: String 27 | var currentIndex: String.Index 28 | var hasNext: Bool { 29 | return currentIndex != message.endIndex 30 | } 31 | 32 | var currentCharacter: String { 33 | return String(message[currentIndex]) 34 | } 35 | 36 | init(message: String) { 37 | self.message = message 38 | currentIndex = message.startIndex 39 | } 40 | 41 | mutating func advanceIndexBy(n: Int) { 42 | currentIndex = currentIndex.advancedBy(n) 43 | } 44 | 45 | mutating func read(readLength: Int) -> String { 46 | let readString = message[currentIndex.. String { 53 | let substring = message[currentIndex.. String { 66 | return read(currentIndex.distanceTo(message.endIndex)) 67 | } 68 | } -------------------------------------------------------------------------------- /Source/SocketEngineWebsocket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEngineWebsocket.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/15/16. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | /// Protocol that is used to implement socket.io WebSocket support 29 | public protocol SocketEngineWebsocket: SocketEngineSpec, WebSocketDelegate { 30 | var ws: WebSocket? { get } 31 | 32 | func sendWebSocketMessage(str: String, withType type: SocketEnginePacketType, withData datas: [NSData]) 33 | } 34 | 35 | // WebSocket methods 36 | extension SocketEngineWebsocket { 37 | func probeWebSocket() { 38 | if ws?.isConnected ?? false { 39 | sendWebSocketMessage("probe", withType: .Ping, withData: []) 40 | } 41 | } 42 | 43 | /// Send message on WebSockets 44 | /// Only call on emitQueue 45 | public func sendWebSocketMessage(str: String, withType type: SocketEnginePacketType, withData datas: [NSData]) { 46 | DefaultSocketLogger.Logger.log("Sending ws: %@ as type: %@", type: "SocketEngine", args: str, type.rawValue) 47 | 48 | ws?.writeString("\(type.rawValue)\(str)") 49 | 50 | for data in datas { 51 | if case let .Left(bin) = createBinaryDataForSend(data) { 52 | ws?.writeData(bin) 53 | } 54 | } 55 | } 56 | 57 | public func websocketDidReceiveMessage(socket: WebSocket, text: String) { 58 | parseEngineMessage(text, fromPolling: false) 59 | } 60 | 61 | public func websocketDidReceiveData(socket: WebSocket, data: NSData) { 62 | parseEngineData(data) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SocketChat/SocketIOManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketIOManager.swift 3 | // SocketChat 4 | // 5 | // Created by Gabriel Theodoropoulos on 1/31/16. 6 | // Copyright © 2016 AppCoda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SocketIOManager: NSObject { 12 | static let sharedInstance = SocketIOManager() 13 | 14 | var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: "http://192.168.1.XXX:3000")!) 15 | 16 | 17 | override init() { 18 | super.init() 19 | } 20 | 21 | 22 | func establishConnection() { 23 | socket.connect() 24 | } 25 | 26 | 27 | func closeConnection() { 28 | socket.disconnect() 29 | } 30 | 31 | 32 | func connectToServerWithNickname(nickname: String, completionHandler: (userList: [[String: AnyObject]]!) -> Void) { 33 | socket.emit("connectUser", nickname) 34 | 35 | socket.on("userList") { ( dataArray, ack) -> Void in 36 | completionHandler(userList: dataArray[0] as! [[String: AnyObject]]) 37 | } 38 | 39 | listenForOtherMessages() 40 | } 41 | 42 | 43 | func exitChatWithNickname(nickname: String, completionHandler: () -> Void) { 44 | socket.emit("exitUser", nickname) 45 | completionHandler() 46 | } 47 | 48 | 49 | func sendMessage(message: String, withNickname nickname: String) { 50 | socket.emit("chatMessage", nickname, message) 51 | } 52 | 53 | 54 | func getChatMessage(completionHandler: (messageInfo: [String: AnyObject]) -> Void) { 55 | socket.on("newChatMessage") { (dataArray, socketAck) -> Void in 56 | var messageDictionary = [String: AnyObject]() 57 | messageDictionary["nickname"] = dataArray[0] as! String 58 | messageDictionary["message"] = dataArray[1] as! String 59 | messageDictionary["date"] = dataArray[2] as! String 60 | 61 | completionHandler(messageInfo: messageDictionary) 62 | } 63 | } 64 | 65 | 66 | private func listenForOtherMessages() { 67 | socket.on("userConnectUpdate") { (dataArray, socketAck) -> Void in 68 | NSNotificationCenter.defaultCenter().postNotificationName("userWasConnectedNotification", object: dataArray[0] as! [String: AnyObject]) 69 | } 70 | 71 | socket.on("userExitUpdate") { (dataArray, socketAck) -> Void in 72 | NSNotificationCenter.defaultCenter().postNotificationName("userWasDisconnectedNotification", object: dataArray[0] as! String) 73 | } 74 | 75 | socket.on("userTypingUpdate") { (dataArray, socketAck) -> Void in 76 | NSNotificationCenter.defaultCenter().postNotificationName("userTypingNotification", object: dataArray[0] as? [String: AnyObject]) 77 | } 78 | } 79 | 80 | 81 | func sendStartTypingMessage(nickname: String) { 82 | socket.emit("startType", nickname) 83 | } 84 | 85 | 86 | func sendStopTypingMessage(nickname: String) { 87 | socket.emit("stopType", nickname) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SocketChat/UserCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Source/SocketEngineSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEngineSpec.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 10/7/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | @objc public protocol SocketEngineSpec { 29 | weak var client: SocketEngineClient? { get set } 30 | var closed: Bool { get } 31 | var connected: Bool { get } 32 | var connectParams: [String: AnyObject]? { get set } 33 | var cookies: [NSHTTPCookie]? { get } 34 | var extraHeaders: [String: String]? { get } 35 | var fastUpgrade: Bool { get } 36 | var forcePolling: Bool { get } 37 | var forceWebsockets: Bool { get } 38 | var parseQueue: dispatch_queue_t! { get } 39 | var pingTimer: NSTimer? { get } 40 | var polling: Bool { get } 41 | var probing: Bool { get } 42 | var emitQueue: dispatch_queue_t! { get } 43 | var handleQueue: dispatch_queue_t! { get } 44 | var sid: String { get } 45 | var socketPath: String { get } 46 | var urlPolling: NSURL { get } 47 | var urlWebSocket: NSURL { get } 48 | var websocket: Bool { get } 49 | 50 | init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) 51 | 52 | func close(reason: String) 53 | func didError(error: String) 54 | func doFastUpgrade() 55 | func flushWaitingForPostToWebSocket() 56 | func open() 57 | func parseEngineData(data: NSData) 58 | func parseEngineMessage(message: String, fromPolling: Bool) 59 | func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) 60 | } 61 | 62 | extension SocketEngineSpec { 63 | var urlPollingWithSid: NSURL { 64 | let com = NSURLComponents(URL: urlPolling, resolvingAgainstBaseURL: false)! 65 | com.query = com.query! + "&sid=\(sid)" 66 | 67 | return com.URL! 68 | } 69 | 70 | func createBinaryDataForSend(data: NSData) -> Either { 71 | if websocket { 72 | var byteArray = [UInt8](count: 1, repeatedValue: 0x4) 73 | let mutData = NSMutableData(bytes: &byteArray, length: 1) 74 | 75 | mutData.appendData(data) 76 | 77 | return .Left(mutData) 78 | } else { 79 | let str = "b4" + data.base64EncodedStringWithOptions(.Encoding64CharacterLineLength) 80 | 81 | return .Right(str) 82 | } 83 | } 84 | 85 | /// Send an engine message (4) 86 | func send(msg: String, withData datas: [NSData]) { 87 | write(msg, withType: .Message, withData: datas) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SocketChat.xcodeproj/xcuserdata/gabriel.xcuserdatad/xcschemes/SocketChat.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SocketChat.xcodeproj/xcuserdata/simon.xcuserdatad/xcschemes/SocketChat.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SocketChat/UsersViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UsersViewController.swift 3 | // SocketChat 4 | // 5 | // Created by Gabriel Theodoropoulos on 1/31/16. 6 | // Copyright © 2016 AppCoda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UsersViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 12 | 13 | @IBOutlet weak var tblUserList: UITableView! 14 | 15 | 16 | var users = [[String: AnyObject]]() 17 | 18 | var nickname: String! 19 | 20 | var configurationOK = false 21 | 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | // Do any additional setup after loading the view. 27 | } 28 | 29 | 30 | override func viewWillAppear(animated: Bool) { 31 | super.viewWillAppear(animated) 32 | 33 | if !configurationOK { 34 | configureNavigationBar() 35 | configureTableView() 36 | configurationOK = true 37 | } 38 | 39 | } 40 | 41 | 42 | override func viewDidAppear(animated: Bool) { 43 | super.viewDidAppear(animated) 44 | 45 | if nickname == nil { 46 | askForNickname() 47 | } 48 | } 49 | 50 | 51 | override func didReceiveMemoryWarning() { 52 | super.didReceiveMemoryWarning() 53 | // Dispose of any resources that can be recreated. 54 | } 55 | 56 | 57 | 58 | // MARK: - Navigation 59 | 60 | // In a storyboard-based application, you will often want to do a little preparation before navigation 61 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 62 | if let identifier = segue.identifier { 63 | if identifier == "idSegueJoinChat" { 64 | let chatViewController = segue.destinationViewController as! ChatViewController 65 | chatViewController.nickname = nickname 66 | } 67 | } 68 | } 69 | 70 | 71 | // MARK: IBAction Methods 72 | 73 | @IBAction func exitChat(sender: AnyObject) { 74 | SocketIOManager.sharedInstance.exitChatWithNickname(nickname) { () -> Void in 75 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 76 | self.nickname = nil 77 | self.users.removeAll() 78 | self.tblUserList.hidden = true 79 | self.askForNickname() 80 | }) 81 | } 82 | } 83 | 84 | 85 | 86 | // MARK: Custom Methods 87 | 88 | func configureNavigationBar() { 89 | navigationItem.title = "SocketChat" 90 | } 91 | 92 | 93 | func configureTableView() { 94 | tblUserList.delegate = self 95 | tblUserList.dataSource = self 96 | tblUserList.registerNib(UINib(nibName: "UserCell", bundle: nil), forCellReuseIdentifier: "idCellUser") 97 | tblUserList.hidden = true 98 | tblUserList.tableFooterView = UIView(frame: CGRectZero) 99 | } 100 | 101 | 102 | func askForNickname() { 103 | let alertController = UIAlertController(title: "SocketChat", message: "Please enter a nickname:", preferredStyle: UIAlertControllerStyle.Alert) 104 | 105 | alertController.addTextFieldWithConfigurationHandler(nil) 106 | 107 | let OKAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (action) -> Void in 108 | let textfield = alertController.textFields![0] 109 | if textfield.text?.characters.count == 0 { 110 | self.askForNickname() 111 | } 112 | else { 113 | self.nickname = textfield.text 114 | 115 | SocketIOManager.sharedInstance.connectToServerWithNickname(self.nickname, completionHandler: { (userList) -> Void in 116 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 117 | if userList != nil { 118 | self.users = userList 119 | self.tblUserList.reloadData() 120 | self.tblUserList.hidden = false 121 | } 122 | }) 123 | }) 124 | } 125 | } 126 | 127 | alertController.addAction(OKAction) 128 | presentViewController(alertController, animated: true, completion: nil) 129 | } 130 | 131 | 132 | // MARK: UITableView Delegate and Datasource methods 133 | 134 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 135 | return 1 136 | } 137 | 138 | 139 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 140 | return users.count 141 | } 142 | 143 | 144 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 145 | let cell = tableView.dequeueReusableCellWithIdentifier("idCellUser", forIndexPath: indexPath) as! UserCell 146 | 147 | cell.textLabel?.text = users[indexPath.row]["nickname"] as? String 148 | cell.detailTextLabel?.text = (users[indexPath.row]["isConnected"] as! Bool) ? "Online" : "Offline" 149 | cell.detailTextLabel?.textColor = (users[indexPath.row]["isConnected"] as! Bool) ? UIColor.greenColor() : UIColor.redColor() 150 | 151 | return cell 152 | } 153 | 154 | 155 | func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 156 | return 44.0 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /SocketChat/ChatCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Source/SwiftRegex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftRegex.swift 3 | // SwiftRegex 4 | // 5 | // Created by John Holdsworth on 26/06/2014. 6 | // Copyright (c) 2014 John Holdsworth. 7 | // 8 | // $Id: //depot/SwiftRegex/SwiftRegex.swift#37 $ 9 | // 10 | // This code is in the public domain from: 11 | // https://github.com/johnno1962/SwiftRegex 12 | // 13 | 14 | import Foundation 15 | 16 | infix operator <~ { associativity none precedence 130 } 17 | 18 | private let lock = dispatch_semaphore_create(1) 19 | private var swiftRegexCache = [String: NSRegularExpression]() 20 | 21 | internal final class SwiftRegex: NSObject, BooleanType { 22 | var target:String 23 | var regex: NSRegularExpression 24 | 25 | init(target:String, pattern:String, options:NSRegularExpressionOptions?) { 26 | if dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC))) != 0 { 27 | fatalError("This should never happen") 28 | } 29 | 30 | self.target = target 31 | if let regex = swiftRegexCache[pattern] { 32 | self.regex = regex 33 | } else { 34 | do { 35 | let regex = try NSRegularExpression(pattern: pattern, options: 36 | NSRegularExpressionOptions.DotMatchesLineSeparators) 37 | swiftRegexCache[pattern] = regex 38 | self.regex = regex 39 | } catch let error as NSError { 40 | SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") 41 | self.regex = NSRegularExpression() 42 | } 43 | } 44 | dispatch_semaphore_signal(lock) 45 | super.init() 46 | } 47 | 48 | private static func failure(message: String) { 49 | fatalError("SwiftRegex: \(message)") 50 | } 51 | 52 | private var targetRange: NSRange { 53 | return NSRange(location: 0,length: target.utf16.count) 54 | } 55 | 56 | private func substring(range: NSRange) -> String? { 57 | if range.location != NSNotFound { 58 | return (target as NSString).substringWithRange(range) 59 | } else { 60 | return nil 61 | } 62 | } 63 | 64 | func doesMatch(options: NSMatchingOptions!) -> Bool { 65 | return range(options).location != NSNotFound 66 | } 67 | 68 | func range(options: NSMatchingOptions) -> NSRange { 69 | return regex.rangeOfFirstMatchInString(target as String, options: [], range: targetRange) 70 | } 71 | 72 | func match(options: NSMatchingOptions) -> String? { 73 | return substring(range(options)) 74 | } 75 | 76 | func groups() -> [String]? { 77 | return groupsForMatch(regex.firstMatchInString(target as String, options: 78 | NSMatchingOptions.WithoutAnchoringBounds, range: targetRange)) 79 | } 80 | 81 | private func groupsForMatch(match: NSTextCheckingResult?) -> [String]? { 82 | guard let match = match else { 83 | return nil 84 | } 85 | var groups = [String]() 86 | for groupno in 0...regex.numberOfCaptureGroups { 87 | if let group = substring(match.rangeAtIndex(groupno)) { 88 | groups += [group] 89 | } else { 90 | groups += ["_"] // avoids bridging problems 91 | } 92 | } 93 | return groups 94 | } 95 | 96 | subscript(groupno: Int) -> String? { 97 | get { 98 | return groups()?[groupno] 99 | } 100 | 101 | set(newValue) { 102 | if newValue == nil { 103 | return 104 | } 105 | 106 | for match in Array(matchResults().reverse()) { 107 | let replacement = regex.replacementStringForResult(match, 108 | inString: target as String, offset: 0, template: newValue!) 109 | let mut = NSMutableString(string: target) 110 | mut.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) 111 | 112 | target = mut as String 113 | } 114 | } 115 | } 116 | 117 | func matchResults() -> [NSTextCheckingResult] { 118 | let matches = regex.matchesInString(target as String, options: 119 | NSMatchingOptions.WithoutAnchoringBounds, range: targetRange) 120 | as [NSTextCheckingResult] 121 | 122 | return matches 123 | } 124 | 125 | func ranges() -> [NSRange] { 126 | return matchResults().map { $0.range } 127 | } 128 | 129 | func matches() -> [String] { 130 | return matchResults().map( { self.substring($0.range)!}) 131 | } 132 | 133 | func allGroups() -> [[String]?] { 134 | return matchResults().map { self.groupsForMatch($0) } 135 | } 136 | 137 | func dictionary(options: NSMatchingOptions!) -> Dictionary { 138 | var out = Dictionary() 139 | for match in matchResults() { 140 | out[substring(match.rangeAtIndex(1))!] = substring(match.rangeAtIndex(2))! 141 | } 142 | return out 143 | } 144 | 145 | func substituteMatches(substitution: ((NSTextCheckingResult, UnsafeMutablePointer) -> String), 146 | options:NSMatchingOptions) -> String { 147 | let out = NSMutableString() 148 | var pos = 0 149 | 150 | regex.enumerateMatchesInString(target as String, options: options, range: targetRange ) {match, flags, stop in 151 | let matchRange = match!.range 152 | out.appendString( self.substring(NSRange(location:pos, length:matchRange.location-pos))!) 153 | out.appendString( substitution(match!, stop) ) 154 | pos = matchRange.location + matchRange.length 155 | } 156 | 157 | out.appendString(substring(NSRange(location:pos, length:targetRange.length-pos))!) 158 | 159 | return out as String 160 | } 161 | 162 | var boolValue: Bool { 163 | return doesMatch(nil) 164 | } 165 | } 166 | 167 | extension String { 168 | subscript(pattern: String, options: NSRegularExpressionOptions) -> SwiftRegex { 169 | return SwiftRegex(target: self, pattern: pattern, options: options) 170 | } 171 | } 172 | 173 | extension String { 174 | subscript(pattern: String) -> SwiftRegex { 175 | return SwiftRegex(target: self, pattern: pattern, options: nil) 176 | } 177 | } 178 | 179 | func <~ (left: SwiftRegex, right: String) -> String { 180 | return left.substituteMatches({match, stop in 181 | return left.regex.replacementStringForResult( match, 182 | inString: left.target as String, offset: 0, template: right ) 183 | }, options: []) 184 | } 185 | -------------------------------------------------------------------------------- /Source/SocketParsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketParsable.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | import Foundation 24 | 25 | protocol SocketParsable: SocketClientSpec { 26 | func parseBinaryData(data: NSData) 27 | func parseSocketMessage(message: String) 28 | } 29 | 30 | extension SocketParsable { 31 | private func isCorrectNamespace(nsp: String) -> Bool { 32 | return nsp == self.nsp 33 | } 34 | 35 | private func handleConnect(p: SocketPacket) { 36 | if p.nsp == "/" && nsp != "/" { 37 | joinNamespace(nsp) 38 | } else if p.nsp != "/" && nsp == "/" { 39 | didConnect() 40 | } else { 41 | didConnect() 42 | } 43 | } 44 | 45 | private func handlePacket(pack: SocketPacket) { 46 | switch pack.type { 47 | case .Event where isCorrectNamespace(pack.nsp): 48 | handleEvent(pack.event, data: pack.args, 49 | isInternalMessage: false, withAck: pack.id) 50 | case .Ack where isCorrectNamespace(pack.nsp): 51 | handleAck(pack.id, data: pack.data) 52 | case .BinaryEvent where isCorrectNamespace(pack.nsp): 53 | waitingData.append(pack) 54 | case .BinaryAck where isCorrectNamespace(pack.nsp): 55 | waitingData.append(pack) 56 | case .Connect: 57 | handleConnect(pack) 58 | case .Disconnect: 59 | didDisconnect("Got Disconnect") 60 | case .Error: 61 | handleEvent("error", data: pack.data, isInternalMessage: true, withAck: pack.id) 62 | default: 63 | DefaultSocketLogger.Logger.log("Got invalid packet: %@", type: "SocketParser", args: pack.description) 64 | } 65 | } 66 | 67 | /// Parses a messsage from the engine. Returning either a string error or a complete SocketPacket 68 | func parseString(message: String) -> Either { 69 | var parser = SocketStringReader(message: message) 70 | 71 | guard let type = SocketPacket.PacketType(rawValue: Int(parser.read(1)) ?? -1) else { 72 | return .Left("Invalid packet type") 73 | } 74 | 75 | if !parser.hasNext { 76 | return .Right(SocketPacket(type: type, nsp: "/")) 77 | } 78 | 79 | var namespace: String? 80 | var placeholders = -1 81 | 82 | if type == .BinaryEvent || type == .BinaryAck { 83 | if let holders = Int(parser.readUntilStringOccurence("-")) { 84 | placeholders = holders 85 | } else { 86 | return .Left("Invalid packet") 87 | } 88 | } 89 | 90 | if parser.currentCharacter == "/" { 91 | namespace = parser.readUntilStringOccurence(",") ?? parser.readUntilEnd() 92 | } 93 | 94 | if !parser.hasNext { 95 | return .Right(SocketPacket(type: type, id: -1, 96 | nsp: namespace ?? "/", placeholders: placeholders)) 97 | } 98 | 99 | var idString = "" 100 | 101 | if type == .Error { 102 | parser.advanceIndexBy(-1) 103 | } 104 | 105 | while parser.hasNext && type != .Error { 106 | if let int = Int(parser.read(1)) { 107 | idString += String(int) 108 | } else { 109 | parser.advanceIndexBy(-2) 110 | break 111 | } 112 | } 113 | 114 | let d = message[parser.currentIndex.advancedBy(1).. Either { 134 | let stringData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) 135 | do { 136 | if let arr = try NSJSONSerialization.JSONObjectWithData(stringData!, 137 | options: NSJSONReadingOptions.MutableContainers) as? [AnyObject] { 138 | return .Right(arr) 139 | } else { 140 | return .Left("Expected data array") 141 | } 142 | } catch { 143 | return .Left("Error parsing data for packet") 144 | } 145 | } 146 | 147 | // Parses messages recieved 148 | func parseSocketMessage(message: String) { 149 | guard !message.isEmpty else { return } 150 | 151 | DefaultSocketLogger.Logger.log("Parsing %@", type: "SocketParser", args: message) 152 | 153 | switch parseString(message) { 154 | case let .Left(err): 155 | DefaultSocketLogger.Logger.error("\(err): %@", type: "SocketParser", args: message) 156 | case let .Right(pack): 157 | DefaultSocketLogger.Logger.log("Decoded packet as: %@", type: "SocketParser", args: pack.description) 158 | handlePacket(pack) 159 | } 160 | } 161 | 162 | func parseBinaryData(data: NSData) { 163 | guard !waitingData.isEmpty else { 164 | DefaultSocketLogger.Logger.error("Got data when not remaking packet", type: "SocketParser") 165 | return 166 | } 167 | 168 | // Should execute event? 169 | guard waitingData[waitingData.count - 1].addData(data) else { 170 | return 171 | } 172 | 173 | let packet = waitingData.removeLast() 174 | 175 | if packet.type != .BinaryAck { 176 | handleEvent(packet.event, data: packet.args ?? [], 177 | isInternalMessage: false, withAck: packet.id) 178 | } else { 179 | handleAck(packet.id, data: packet.args) 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Source/SocketIOClientOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketIOClientOption .swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 10/17/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | protocol ClientOption: CustomStringConvertible, Hashable { 28 | func getSocketIOOptionValue() -> AnyObject 29 | } 30 | 31 | public enum SocketIOClientOption: ClientOption { 32 | case ConnectParams([String: AnyObject]) 33 | case Cookies([NSHTTPCookie]) 34 | case ExtraHeaders([String: String]) 35 | case ForceNew(Bool) 36 | case ForcePolling(Bool) 37 | case ForceWebsockets(Bool) 38 | case HandleQueue(dispatch_queue_t) 39 | case Log(Bool) 40 | case Logger(SocketLogger) 41 | case Nsp(String) 42 | case Path(String) 43 | case Reconnects(Bool) 44 | case ReconnectAttempts(Int) 45 | case ReconnectWait(Int) 46 | case Secure(Bool) 47 | case SelfSigned(Bool) 48 | case SessionDelegate(NSURLSessionDelegate) 49 | case VoipEnabled(Bool) 50 | 51 | public var description: String { 52 | let description: String 53 | 54 | switch self { 55 | case .ConnectParams: 56 | description = "connectParams" 57 | case .Cookies: 58 | description = "cookies" 59 | case .ExtraHeaders: 60 | description = "extraHeaders" 61 | case .ForceNew: 62 | description = "forceNew" 63 | case .ForcePolling: 64 | description = "forcePolling" 65 | case .ForceWebsockets: 66 | description = "forceWebsockets" 67 | case .HandleQueue: 68 | description = "handleQueue" 69 | case .Log: 70 | description = "log" 71 | case .Logger: 72 | description = "logger" 73 | case .Nsp: 74 | description = "nsp" 75 | case .Path: 76 | description = "path" 77 | case .Reconnects: 78 | description = "reconnects" 79 | case .ReconnectAttempts: 80 | description = "reconnectAttempts" 81 | case .ReconnectWait: 82 | description = "reconnectWait" 83 | case .Secure: 84 | description = "secure" 85 | case .SelfSigned: 86 | description = "selfSigned" 87 | case .SessionDelegate: 88 | description = "sessionDelegate" 89 | case .VoipEnabled: 90 | description = "voipEnabled" 91 | } 92 | 93 | return description 94 | } 95 | 96 | public var hashValue: Int { 97 | return description.hashValue 98 | } 99 | 100 | func getSocketIOOptionValue() -> AnyObject { 101 | let value: AnyObject 102 | 103 | switch self { 104 | case let .ConnectParams(params): 105 | value = params 106 | case let .Cookies(cookies): 107 | value = cookies 108 | case let .ExtraHeaders(headers): 109 | value = headers 110 | case let .ForceNew(force): 111 | value = force 112 | case let .ForcePolling(force): 113 | value = force 114 | case let .ForceWebsockets(force): 115 | value = force 116 | case let .HandleQueue(queue): 117 | value = queue 118 | case let .Log(log): 119 | value = log 120 | case let .Logger(logger): 121 | value = logger 122 | case let .Nsp(nsp): 123 | value = nsp 124 | case let .Path(path): 125 | value = path 126 | case let .Reconnects(reconnects): 127 | value = reconnects 128 | case let .ReconnectAttempts(attempts): 129 | value = attempts 130 | case let .ReconnectWait(wait): 131 | value = wait 132 | case let .Secure(secure): 133 | value = secure 134 | case let .SelfSigned(signed): 135 | value = signed 136 | case let .SessionDelegate(delegate): 137 | value = delegate 138 | case let .VoipEnabled(enabled): 139 | value = enabled 140 | } 141 | 142 | return value 143 | } 144 | } 145 | 146 | public func ==(lhs: SocketIOClientOption, rhs: SocketIOClientOption) -> Bool { 147 | return lhs.description == rhs.description 148 | } 149 | 150 | extension Set where Element: ClientOption { 151 | mutating func insertIgnore(element: Element) { 152 | if !contains(element) { 153 | insert(element) 154 | } 155 | } 156 | } 157 | 158 | extension NSDictionary { 159 | static func keyValueToSocketIOClientOption(key: String, value: AnyObject) -> SocketIOClientOption? { 160 | switch (key, value) { 161 | case let ("connectParams", params as [String: AnyObject]): 162 | return .ConnectParams(params) 163 | case let ("reconnects", reconnects as Bool): 164 | return .Reconnects(reconnects) 165 | case let ("reconnectAttempts", attempts as Int): 166 | return .ReconnectAttempts(attempts) 167 | case let ("reconnectWait", wait as Int): 168 | return .ReconnectWait(wait) 169 | case let ("forceNew", force as Bool): 170 | return .ForceNew(force) 171 | case let ("forcePolling", force as Bool): 172 | return .ForcePolling(force) 173 | case let ("forceWebsockets", force as Bool): 174 | return .ForceWebsockets(force) 175 | case let ("nsp", nsp as String): 176 | return .Nsp(nsp) 177 | case let ("cookies", cookies as [NSHTTPCookie]): 178 | return .Cookies(cookies) 179 | case let ("log", log as Bool): 180 | return .Log(log) 181 | case let ("logger", logger as SocketLogger): 182 | return .Logger(logger) 183 | case let ("sessionDelegate", delegate as NSURLSessionDelegate): 184 | return .SessionDelegate(delegate) 185 | case let ("path", path as String): 186 | return .Path(path) 187 | case let ("extraHeaders", headers as [String: String]): 188 | return .ExtraHeaders(headers) 189 | case let ("handleQueue", queue as dispatch_queue_t): 190 | return .HandleQueue(queue) 191 | case let ("voipEnabled", enable as Bool): 192 | return .VoipEnabled(enable) 193 | case let ("secure", secure as Bool): 194 | return .Secure(secure) 195 | case let ("selfSigned", selfSigned as Bool): 196 | return .SelfSigned(selfSigned) 197 | default: 198 | return nil 199 | } 200 | } 201 | 202 | func toSocketOptionsSet() -> Set { 203 | var options = Set() 204 | 205 | for (rawKey, value) in self { 206 | if let key = rawKey as? String, opt = NSDictionary.keyValueToSocketIOClientOption(key, value: value) { 207 | options.insertIgnore(opt) 208 | } 209 | } 210 | 211 | return options 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Source/SocketEnginePollable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEnginePollable.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/15/16. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | /// Protocol that is used to implement socket.io polling support 28 | public protocol SocketEnginePollable: SocketEngineSpec { 29 | var invalidated: Bool { get } 30 | /// Holds strings waiting to be sent over polling. 31 | /// You shouldn't need to mess with this. 32 | var postWait: [String] { get set } 33 | var session: NSURLSession? { get } 34 | /// Because socket.io doesn't let you send two polling request at the same time 35 | /// we have to keep track if there's an outstanding poll 36 | var waitingForPoll: Bool { get set } 37 | /// Because socket.io doesn't let you send two post request at the same time 38 | /// we have to keep track if there's an outstanding post 39 | var waitingForPost: Bool { get set } 40 | 41 | func doPoll() 42 | func sendPollMessage(message: String, withType type: SocketEnginePacketType, withData datas: [NSData]) 43 | func stopPolling() 44 | } 45 | 46 | // Default polling methods 47 | extension SocketEnginePollable { 48 | private func addHeaders(req: NSMutableURLRequest) { 49 | if cookies != nil { 50 | let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) 51 | req.allHTTPHeaderFields = headers 52 | } 53 | 54 | if extraHeaders != nil { 55 | for (headerName, value) in extraHeaders! { 56 | req.setValue(value, forHTTPHeaderField: headerName) 57 | } 58 | } 59 | } 60 | 61 | func createRequestForPostWithPostWait() -> NSURLRequest { 62 | var postStr = "" 63 | 64 | for packet in postWait { 65 | let len = packet.characters.count 66 | 67 | postStr += "\(len):\(packet)" 68 | } 69 | 70 | DefaultSocketLogger.Logger.log("Created POST string: %@", type: "SocketEnginePolling", args: postStr) 71 | 72 | postWait.removeAll(keepCapacity: false) 73 | 74 | let req = NSMutableURLRequest(URL: urlPollingWithSid) 75 | 76 | addHeaders(req) 77 | 78 | req.HTTPMethod = "POST" 79 | req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") 80 | 81 | let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, 82 | allowLossyConversion: false)! 83 | 84 | req.HTTPBody = postData 85 | req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") 86 | 87 | return req 88 | } 89 | 90 | public func doPoll() { 91 | if websocket || waitingForPoll || !connected || closed { 92 | return 93 | } 94 | 95 | waitingForPoll = true 96 | let req = NSMutableURLRequest(URL: urlPollingWithSid) 97 | 98 | addHeaders(req) 99 | doLongPoll(req) 100 | } 101 | 102 | func doRequest(req: NSURLRequest, withCallback callback: (NSData?, NSURLResponse?, NSError?) -> Void) { 103 | if !polling || closed || invalidated { 104 | DefaultSocketLogger.Logger.error("Tried to do polling request when not supposed to", type: "SocketEnginePolling") 105 | return 106 | } 107 | 108 | DefaultSocketLogger.Logger.log("Doing polling request", type: "SocketEnginePolling") 109 | 110 | session?.dataTaskWithRequest(req, completionHandler: callback).resume() 111 | } 112 | 113 | func doLongPoll(req: NSURLRequest) { 114 | doRequest(req) {[weak self] data, res, err in 115 | guard let this = self else { return } 116 | 117 | if err != nil || data == nil { 118 | DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") 119 | 120 | if this.polling { 121 | this.didError(err?.localizedDescription ?? "Error") 122 | } 123 | 124 | return 125 | } 126 | 127 | DefaultSocketLogger.Logger.log("Got polling response", type: "SocketEnginePolling") 128 | 129 | if let str = String(data: data!, encoding: NSUTF8StringEncoding) { 130 | dispatch_async(this.parseQueue) { 131 | this.parsePollingMessage(str) 132 | } 133 | } 134 | 135 | this.waitingForPoll = false 136 | 137 | if this.fastUpgrade { 138 | this.doFastUpgrade() 139 | } else if !this.closed && this.polling { 140 | this.doPoll() 141 | } 142 | } 143 | } 144 | 145 | private func flushWaitingForPost() { 146 | if postWait.count == 0 || !connected { 147 | return 148 | } else if websocket { 149 | flushWaitingForPostToWebSocket() 150 | return 151 | } 152 | 153 | let req = createRequestForPostWithPostWait() 154 | 155 | waitingForPost = true 156 | 157 | DefaultSocketLogger.Logger.log("POSTing", type: "SocketEnginePolling") 158 | 159 | doRequest(req) {[weak self] data, res, err in 160 | guard let this = self else { return } 161 | 162 | if err != nil { 163 | DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") 164 | 165 | if this.polling { 166 | this.didError(err?.localizedDescription ?? "Error") 167 | } 168 | 169 | return 170 | } 171 | 172 | this.waitingForPost = false 173 | 174 | dispatch_async(this.emitQueue) { 175 | if !this.fastUpgrade { 176 | this.flushWaitingForPost() 177 | this.doPoll() 178 | } 179 | } 180 | } 181 | } 182 | 183 | func parsePollingMessage(str: String) { 184 | guard str.characters.count != 1 else { 185 | return 186 | } 187 | 188 | var reader = SocketStringReader(message: str) 189 | 190 | while reader.hasNext { 191 | if let n = Int(reader.readUntilStringOccurence(":")) { 192 | let str = reader.read(n) 193 | 194 | dispatch_async(handleQueue) { 195 | self.parseEngineMessage(str, fromPolling: true) 196 | } 197 | } else { 198 | dispatch_async(handleQueue) { 199 | self.parseEngineMessage(str, fromPolling: true) 200 | } 201 | break 202 | } 203 | } 204 | } 205 | 206 | /// Send polling message. 207 | /// Only call on emitQueue 208 | public func sendPollMessage(message: String, withType type: SocketEnginePacketType, withData datas: [NSData]) { 209 | DefaultSocketLogger.Logger.log("Sending poll: %@ as type: %@", type: "SocketEnginePolling", args: message, type.rawValue) 210 | let fixedMessage = doubleEncodeUTF8(message) 211 | let strMsg = "\(type.rawValue)\(fixedMessage)" 212 | 213 | postWait.append(strMsg) 214 | 215 | for data in datas { 216 | if case let .Right(bin) = createBinaryDataForSend(data) { 217 | postWait.append(bin) 218 | } 219 | } 220 | 221 | if !waitingForPost { 222 | flushWaitingForPost() 223 | } 224 | } 225 | 226 | public func stopPolling() { 227 | waitingForPoll = false 228 | waitingForPost = false 229 | session?.finishTasksAndInvalidate() 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Source/SocketPacket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketPacket.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/18/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | struct SocketPacket { 29 | private let placeholders: Int 30 | 31 | private static let logType = "SocketPacket" 32 | 33 | let nsp: String 34 | let id: Int 35 | let type: PacketType 36 | 37 | enum PacketType: Int { 38 | case Connect, Disconnect, Event, Ack, Error, BinaryEvent, BinaryAck 39 | } 40 | 41 | var args: [AnyObject] { 42 | if type == .Event || type == .BinaryEvent && data.count != 0 { 43 | return Array(data.dropFirst()) 44 | } else { 45 | return data 46 | } 47 | } 48 | 49 | var binary: [NSData] 50 | var data: [AnyObject] 51 | var description: String { 52 | return "SocketPacket {type: \(String(type.rawValue)); data: " + 53 | "\(String(data)); id: \(id); placeholders: \(placeholders); nsp: \(nsp)}" 54 | } 55 | 56 | var event: String { 57 | return String(data[0]) 58 | } 59 | 60 | var packetString: String { 61 | return createPacketString() 62 | } 63 | 64 | init(type: SocketPacket.PacketType, data: [AnyObject] = [AnyObject](), id: Int = -1, 65 | nsp: String, placeholders: Int = 0, binary: [NSData] = [NSData]()) { 66 | self.data = data 67 | self.id = id 68 | self.nsp = nsp 69 | self.type = type 70 | self.placeholders = placeholders 71 | self.binary = binary 72 | } 73 | 74 | mutating func addData(data: NSData) -> Bool { 75 | if placeholders == binary.count { 76 | return true 77 | } 78 | 79 | binary.append(data) 80 | 81 | if placeholders == binary.count { 82 | fillInPlaceholders() 83 | return true 84 | } else { 85 | return false 86 | } 87 | } 88 | 89 | private func completeMessage(message: String, ack: Bool) -> String { 90 | var restOfMessage = "" 91 | 92 | if data.count == 0 { 93 | return message + "]" 94 | } 95 | 96 | for arg in data { 97 | if arg is NSDictionary || arg is [AnyObject] { 98 | do { 99 | let jsonSend = try NSJSONSerialization.dataWithJSONObject(arg, 100 | options: NSJSONWritingOptions(rawValue: 0)) 101 | let jsonString = String(data: jsonSend, encoding: NSUTF8StringEncoding) 102 | 103 | restOfMessage += jsonString! + "," 104 | } catch { 105 | DefaultSocketLogger.Logger.error("Error creating JSON object in SocketPacket.completeMessage", 106 | type: SocketPacket.logType) 107 | } 108 | } else if let str = arg as? String { 109 | restOfMessage += "\"" + ((str["\n"] <~ "\\\\n")["\r"] <~ "\\\\r") + "\"," 110 | } else if arg is NSNull { 111 | restOfMessage += "null," 112 | } else { 113 | restOfMessage += "\(arg)," 114 | } 115 | } 116 | 117 | if restOfMessage != "" { 118 | restOfMessage.removeAtIndex(restOfMessage.endIndex.predecessor()) 119 | } 120 | 121 | return message + restOfMessage + "]" 122 | } 123 | 124 | private func createAck() -> String { 125 | let message: String 126 | 127 | if type == .Ack { 128 | if nsp == "/" { 129 | message = "3\(id)[" 130 | } else { 131 | message = "3\(nsp),\(id)[" 132 | } 133 | } else { 134 | if nsp == "/" { 135 | message = "6\(binary.count)-\(id)[" 136 | } else { 137 | message = "6\(binary.count)-\(nsp),\(id)[" 138 | } 139 | } 140 | 141 | return completeMessage(message, ack: true) 142 | } 143 | 144 | 145 | private func createMessageForEvent() -> String { 146 | let message: String 147 | 148 | if type == .Event { 149 | if nsp == "/" { 150 | if id == -1 { 151 | message = "2[" 152 | } else { 153 | message = "2\(id)[" 154 | } 155 | } else { 156 | if id == -1 { 157 | message = "2\(nsp),[" 158 | } else { 159 | message = "2\(nsp),\(id)[" 160 | } 161 | } 162 | } else { 163 | if nsp == "/" { 164 | if id == -1 { 165 | message = "5\(binary.count)-[" 166 | } else { 167 | message = "5\(binary.count)-\(id)[" 168 | } 169 | } else { 170 | if id == -1 { 171 | message = "5\(binary.count)-\(nsp),[" 172 | } else { 173 | message = "5\(binary.count)-\(nsp),\(id)[" 174 | } 175 | } 176 | } 177 | 178 | return completeMessage(message, ack: false) 179 | } 180 | 181 | private func createPacketString() -> String { 182 | let str: String 183 | 184 | if type == .Event || type == .BinaryEvent { 185 | str = createMessageForEvent() 186 | } else { 187 | str = createAck() 188 | } 189 | 190 | return str 191 | } 192 | 193 | // Called when we have all the binary data for a packet 194 | // calls _fillInPlaceholders, which replaces placeholders with the 195 | // corresponding binary 196 | private mutating func fillInPlaceholders() { 197 | data = data.map(_fillInPlaceholders) 198 | } 199 | 200 | // Helper method that looks for placeholder strings 201 | // If object is a collection it will recurse 202 | // Returns the object if it is not a placeholder string or the corresponding 203 | // binary data 204 | private func _fillInPlaceholders(object: AnyObject) -> AnyObject { 205 | switch object { 206 | case let string as String where string["~~(\\d)"].groups() != nil: 207 | return binary[Int(string["~~(\\d)"].groups()![1])!] 208 | case let dict as NSDictionary: 209 | return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in 210 | cur[keyValue.0 as! NSCopying] = _fillInPlaceholders(keyValue.1) 211 | return cur 212 | }) 213 | case let arr as [AnyObject]: 214 | return arr.map(_fillInPlaceholders) 215 | default: 216 | return object 217 | } 218 | } 219 | } 220 | 221 | extension SocketPacket { 222 | private static func findType(binCount: Int, ack: Bool) -> PacketType { 223 | switch binCount { 224 | case 0 where !ack: 225 | return .Event 226 | case 0 where ack: 227 | return .Ack 228 | case _ where !ack: 229 | return .BinaryEvent 230 | case _ where ack: 231 | return .BinaryAck 232 | default: 233 | return .Error 234 | } 235 | } 236 | 237 | static func packetFromEmit(items: [AnyObject], id: Int, nsp: String, ack: Bool) -> SocketPacket { 238 | let (parsedData, binary) = deconstructData(items) 239 | let packet = SocketPacket(type: findType(binary.count, ack: ack), data: parsedData, 240 | id: id, nsp: nsp, placeholders: -1, binary: binary) 241 | 242 | return packet 243 | } 244 | } 245 | 246 | private extension SocketPacket { 247 | // Recursive function that looks for NSData in collections 248 | static func shred(data: AnyObject, inout binary: [NSData]) -> AnyObject { 249 | let placeholder = ["_placeholder": true, "num": binary.count] 250 | 251 | switch data { 252 | case let bin as NSData: 253 | binary.append(bin) 254 | return placeholder 255 | case let arr as [AnyObject]: 256 | return arr.map({shred($0, binary: &binary)}) 257 | case let dict as NSDictionary: 258 | return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in 259 | cur[keyValue.0 as! NSCopying] = shred(keyValue.1, binary: &binary) 260 | return cur 261 | }) 262 | default: 263 | return data 264 | } 265 | } 266 | 267 | // Removes binary data from emit data 268 | // Returns a type containing the de-binaryed data and the binary 269 | static func deconstructData(data: [AnyObject]) -> ([AnyObject], [NSData]) { 270 | var binary = [NSData]() 271 | 272 | return (data.map({shred($0, binary: &binary)}), binary) 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /SocketChat/ChatViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatViewController.swift 3 | // SocketChat 4 | // 5 | // Created by Gabriel Theodoropoulos on 1/31/16. 6 | // Copyright © 2016 AppCoda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate, UIGestureRecognizerDelegate { 12 | 13 | @IBOutlet weak var tblChat: UITableView! 14 | 15 | @IBOutlet weak var lblOtherUserActivityStatus: UILabel! 16 | 17 | @IBOutlet weak var tvMessageEditor: UITextView! 18 | 19 | @IBOutlet weak var conBottomEditor: NSLayoutConstraint! 20 | 21 | @IBOutlet weak var lblNewsBanner: UILabel! 22 | 23 | 24 | 25 | var nickname: String! 26 | 27 | var chatMessages = [[String: AnyObject]]() 28 | 29 | var bannerLabelTimer: NSTimer! 30 | 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | // Do any additional setup after loading the view. 36 | 37 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleKeyboardDidShowNotification:", name: UIKeyboardDidShowNotification, object: nil) 38 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleKeyboardDidHideNotification:", name: UIKeyboardDidHideNotification, object: nil) 39 | 40 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleConnectedUserUpdateNotification:", name: "userWasConnectedNotification", object: nil) 41 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleDisconnectedUserUpdateNotification:", name: "userWasDisconnectedNotification", object: nil) 42 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleUserTypingNotification:", name: "userTypingNotification", object: nil) 43 | 44 | 45 | let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "dismissKeyboard") 46 | swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Down 47 | swipeGestureRecognizer.delegate = self 48 | view.addGestureRecognizer(swipeGestureRecognizer) 49 | } 50 | 51 | 52 | override func viewWillAppear(animated: Bool) { 53 | super.viewWillAppear(animated) 54 | 55 | configureTableView() 56 | configureNewsBannerLabel() 57 | configureOtherUserActivityLabel() 58 | 59 | tvMessageEditor.delegate = self 60 | } 61 | 62 | 63 | override func viewDidAppear(animated: Bool) { 64 | super.viewDidAppear(animated) 65 | 66 | SocketIOManager.sharedInstance.getChatMessage { (messageInfo) -> Void in 67 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 68 | self.chatMessages.append(messageInfo) 69 | self.tblChat.reloadData() 70 | self.scrollToBottom() 71 | }) 72 | } 73 | } 74 | 75 | 76 | override func didReceiveMemoryWarning() { 77 | super.didReceiveMemoryWarning() 78 | // Dispose of any resources that can be recreated. 79 | } 80 | 81 | 82 | deinit { 83 | NSNotificationCenter.defaultCenter().removeObserver(self) 84 | } 85 | 86 | 87 | /* 88 | // MARK: - Navigation 89 | 90 | // In a storyboard-based application, you will often want to do a little preparation before navigation 91 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 92 | // Get the new view controller using segue.destinationViewController. 93 | // Pass the selected object to the new view controller. 94 | } 95 | */ 96 | 97 | 98 | // MARK: IBAction Methods 99 | 100 | @IBAction func sendMessage(sender: AnyObject) { 101 | if tvMessageEditor.text.characters.count > 0 { 102 | SocketIOManager.sharedInstance.sendMessage(tvMessageEditor.text!, withNickname: nickname) 103 | tvMessageEditor.text = "" 104 | tvMessageEditor.resignFirstResponder() 105 | } 106 | } 107 | 108 | 109 | // MARK: Custom Methods 110 | 111 | func configureTableView() { 112 | tblChat.delegate = self 113 | tblChat.dataSource = self 114 | tblChat.registerNib(UINib(nibName: "ChatCell", bundle: nil), forCellReuseIdentifier: "idCellChat") 115 | tblChat.estimatedRowHeight = 90.0 116 | tblChat.rowHeight = UITableViewAutomaticDimension 117 | tblChat.tableFooterView = UIView(frame: CGRectZero) 118 | } 119 | 120 | 121 | func configureNewsBannerLabel() { 122 | lblNewsBanner.layer.cornerRadius = 15.0 123 | lblNewsBanner.clipsToBounds = true 124 | lblNewsBanner.alpha = 0.0 125 | } 126 | 127 | 128 | func configureOtherUserActivityLabel() { 129 | lblOtherUserActivityStatus.hidden = true 130 | lblOtherUserActivityStatus.text = "" 131 | } 132 | 133 | 134 | func handleKeyboardDidShowNotification(notification: NSNotification) { 135 | if let userInfo = notification.userInfo { 136 | if let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() { 137 | conBottomEditor.constant = keyboardFrame.size.height 138 | view.layoutIfNeeded() 139 | } 140 | } 141 | } 142 | 143 | 144 | func handleKeyboardDidHideNotification(notification: NSNotification) { 145 | conBottomEditor.constant = 0 146 | view.layoutIfNeeded() 147 | } 148 | 149 | 150 | func scrollToBottom() { 151 | let delay = 0.1 * Double(NSEC_PER_SEC) 152 | 153 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay)), dispatch_get_main_queue()) { () -> Void in 154 | if self.chatMessages.count > 0 { 155 | let lastRowIndexPath = NSIndexPath(forRow: self.chatMessages.count - 1, inSection: 0) 156 | self.tblChat.scrollToRowAtIndexPath(lastRowIndexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true) 157 | } 158 | } 159 | } 160 | 161 | 162 | func showBannerLabelAnimated() { 163 | UIView.animateWithDuration(0.75, animations: { () -> Void in 164 | self.lblNewsBanner.alpha = 1.0 165 | 166 | }) { (finished) -> Void in 167 | self.bannerLabelTimer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "hideBannerLabel", userInfo: nil, repeats: false) 168 | } 169 | } 170 | 171 | 172 | func hideBannerLabel() { 173 | if bannerLabelTimer != nil { 174 | bannerLabelTimer.invalidate() 175 | bannerLabelTimer = nil 176 | } 177 | 178 | UIView.animateWithDuration(0.75, animations: { () -> Void in 179 | self.lblNewsBanner.alpha = 0.0 180 | 181 | }) { (finished) -> Void in 182 | } 183 | } 184 | 185 | 186 | 187 | func dismissKeyboard() { 188 | if tvMessageEditor.isFirstResponder() { 189 | tvMessageEditor.resignFirstResponder() 190 | 191 | SocketIOManager.sharedInstance.sendStopTypingMessage(nickname) 192 | } 193 | } 194 | 195 | 196 | func handleConnectedUserUpdateNotification(notification: NSNotification) { 197 | let connectedUserInfo = notification.object as! [String: AnyObject] 198 | let connectedUserNickname = connectedUserInfo["nickname"] as? String 199 | lblNewsBanner.text = "User \(connectedUserNickname!.uppercaseString) was just connected." 200 | showBannerLabelAnimated() 201 | } 202 | 203 | 204 | func handleDisconnectedUserUpdateNotification(notification: NSNotification) { 205 | let disconnectedUserNickname = notification.object as! String 206 | lblNewsBanner.text = "User \(disconnectedUserNickname.uppercaseString) has left." 207 | showBannerLabelAnimated() 208 | } 209 | 210 | 211 | func handleUserTypingNotification(notification: NSNotification) { 212 | if let typingUsersDictionary = notification.object as? [String: AnyObject] { 213 | var names = "" 214 | var totalTypingUsers = 0 215 | for (typingUser, _) in typingUsersDictionary { 216 | if typingUser != nickname { 217 | names = (names == "") ? typingUser : "\(names), \(typingUser)" 218 | totalTypingUsers += 1 219 | } 220 | } 221 | 222 | if totalTypingUsers > 0 { 223 | let verb = (totalTypingUsers == 1) ? "is" : "are" 224 | 225 | lblOtherUserActivityStatus.text = "\(names) \(verb) now typing a message..." 226 | lblOtherUserActivityStatus.hidden = false 227 | } 228 | else { 229 | lblOtherUserActivityStatus.hidden = true 230 | } 231 | } 232 | 233 | } 234 | 235 | 236 | // MARK: UITableView Delegate and Datasource Methods 237 | 238 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 239 | return 1 240 | } 241 | 242 | 243 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 244 | return chatMessages.count 245 | } 246 | 247 | 248 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 249 | let cell = tableView.dequeueReusableCellWithIdentifier("idCellChat", forIndexPath: indexPath) as! ChatCell 250 | 251 | let currentChatMessage = chatMessages[indexPath.row] 252 | let senderNickname = currentChatMessage["nickname"] as! String 253 | let message = currentChatMessage["message"] as! String 254 | let messageDate = currentChatMessage["date"] as! String 255 | 256 | if senderNickname == nickname { 257 | cell.lblChatMessage.textAlignment = NSTextAlignment.Right 258 | cell.lblMessageDetails.textAlignment = NSTextAlignment.Right 259 | 260 | cell.lblChatMessage.textColor = lblNewsBanner.backgroundColor 261 | } 262 | 263 | cell.lblChatMessage.text = message 264 | cell.lblMessageDetails.text = "by \(senderNickname.uppercaseString) @ \(messageDate)" 265 | 266 | cell.lblChatMessage.textColor = UIColor.darkGrayColor() 267 | 268 | return cell 269 | } 270 | 271 | 272 | // MARK: UITextViewDelegate Methods 273 | 274 | func textViewShouldBeginEditing(textView: UITextView) -> Bool { 275 | SocketIOManager.sharedInstance.sendStartTypingMessage(nickname) 276 | 277 | return true 278 | } 279 | 280 | 281 | // MARK: UIGestureRecognizerDelegate Methods 282 | 283 | func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { 284 | return true 285 | } 286 | 287 | } 288 | -------------------------------------------------------------------------------- /SocketChat/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 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 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /Source/SocketIOClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketIOClient.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 11/23/14. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable { 28 | public let socketURL: NSURL 29 | 30 | public private(set) var engine: SocketEngineSpec? 31 | public private(set) var status = SocketIOClientStatus.NotConnected 32 | 33 | public var forceNew = false 34 | public var nsp = "/" 35 | public var options: Set 36 | public var reconnects = true 37 | public var reconnectWait = 10 38 | public var sid: String? { 39 | return engine?.sid 40 | } 41 | 42 | private let emitQueue = dispatch_queue_create("com.socketio.emitQueue", DISPATCH_QUEUE_SERIAL) 43 | private let logType = "SocketIOClient" 44 | private let parseQueue = dispatch_queue_create("com.socketio.parseQueue", DISPATCH_QUEUE_SERIAL) 45 | 46 | private var anyHandler: ((SocketAnyEvent) -> Void)? 47 | private var currentReconnectAttempt = 0 48 | private var handlers = [SocketEventHandler]() 49 | private var reconnectTimer: NSTimer? 50 | private var ackHandlers = SocketAckManager() 51 | 52 | private(set) var currentAck = -1 53 | private(set) var handleQueue = dispatch_get_main_queue() 54 | private(set) var reconnectAttempts = -1 55 | 56 | var waitingData = [SocketPacket]() 57 | 58 | /** 59 | Type safe way to create a new SocketIOClient. opts can be omitted 60 | */ 61 | public init(socketURL: NSURL, options: Set = []) { 62 | self.options = options 63 | self.socketURL = socketURL 64 | 65 | if socketURL.absoluteString.hasPrefix("https://") { 66 | self.options.insertIgnore(.Secure(true)) 67 | } 68 | 69 | for option in options { 70 | switch option { 71 | case let .Reconnects(reconnects): 72 | self.reconnects = reconnects 73 | case let .ReconnectAttempts(attempts): 74 | reconnectAttempts = attempts 75 | case let .ReconnectWait(wait): 76 | reconnectWait = abs(wait) 77 | case let .Nsp(nsp): 78 | self.nsp = nsp 79 | case let .Log(log): 80 | DefaultSocketLogger.Logger.log = log 81 | case let .Logger(logger): 82 | DefaultSocketLogger.Logger = logger 83 | case let .HandleQueue(queue): 84 | handleQueue = queue 85 | case let .ForceNew(force): 86 | forceNew = force 87 | default: 88 | continue 89 | } 90 | } 91 | 92 | self.options.insertIgnore(.Path("/socket.io/")) 93 | 94 | super.init() 95 | } 96 | 97 | /** 98 | Not so type safe way to create a SocketIOClient, meant for Objective-C compatiblity. 99 | If using Swift it's recommended to use `init(socketURL: NSURL, options: Set)` 100 | */ 101 | public convenience init(socketURL: NSURL, options: NSDictionary?) { 102 | self.init(socketURL: socketURL, options: options?.toSocketOptionsSet() ?? []) 103 | } 104 | 105 | /// Please use the NSURL based init 106 | @available(*, deprecated=5.3) 107 | public convenience init(socketURLString: String, options: Set = []) { 108 | guard let url = NSURL(string: socketURLString) else { fatalError("Incorrect url") } 109 | self.init(socketURL: url, options: options) 110 | } 111 | 112 | /// Please use the NSURL based init 113 | @available(*, deprecated=5.3) 114 | public convenience init(socketURLString: String, options: NSDictionary?) { 115 | guard let url = NSURL(string: socketURLString) else { fatalError("Incorrect url") } 116 | self.init(socketURL: url, options: options?.toSocketOptionsSet() ?? []) 117 | } 118 | 119 | deinit { 120 | DefaultSocketLogger.Logger.log("Client is being released", type: logType) 121 | engine?.close("Client Deinit") 122 | } 123 | 124 | private func addEngine() -> SocketEngineSpec { 125 | DefaultSocketLogger.Logger.log("Adding engine", type: logType) 126 | 127 | engine = SocketEngine(client: self, url: socketURL, options: options) 128 | 129 | return engine! 130 | } 131 | 132 | private func clearReconnectTimer() { 133 | reconnectTimer?.invalidate() 134 | reconnectTimer = nil 135 | } 136 | 137 | @available(*, deprecated=6.0) 138 | public func close() { 139 | disconnect() 140 | } 141 | 142 | /** 143 | Connect to the server. 144 | */ 145 | public func connect() { 146 | connect(timeoutAfter: 0, withTimeoutHandler: nil) 147 | } 148 | 149 | /** 150 | Connect to the server. If we aren't connected after timeoutAfter, call handler 151 | */ 152 | public func connect(timeoutAfter timeoutAfter: Int, withTimeoutHandler handler: (() -> Void)?) { 153 | assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") 154 | 155 | guard status != .Connected else { 156 | DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", 157 | type: logType) 158 | return 159 | } 160 | 161 | status = .Connecting 162 | 163 | if engine == nil || forceNew { 164 | addEngine().open() 165 | } else { 166 | engine?.open() 167 | } 168 | 169 | guard timeoutAfter != 0 else { return } 170 | 171 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) 172 | 173 | dispatch_after(time, handleQueue) {[weak self] in 174 | if let this = self where this.status != .Connected { 175 | this.status = .Closed 176 | this.engine?.close("Connect timeout") 177 | 178 | handler?() 179 | } 180 | } 181 | } 182 | 183 | private func createOnAck(items: [AnyObject]) -> OnAckCallback { 184 | currentAck += 1 185 | 186 | return {[weak self, ack = currentAck] timeout, callback in 187 | if let this = self { 188 | this.ackHandlers.addAck(ack, callback: callback) 189 | 190 | dispatch_async(this.emitQueue) { 191 | this._emit(items, ack: ack) 192 | } 193 | 194 | if timeout != 0 { 195 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC)) 196 | 197 | dispatch_after(time, this.handleQueue) { 198 | this.ackHandlers.timeoutAck(ack) 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | func didConnect() { 206 | DefaultSocketLogger.Logger.log("Socket connected", type: logType) 207 | status = .Connected 208 | currentReconnectAttempt = 0 209 | clearReconnectTimer() 210 | 211 | // Don't handle as internal because something crazy could happen where 212 | // we disconnect before it's handled 213 | handleEvent("connect", data: [], isInternalMessage: false) 214 | } 215 | 216 | func didDisconnect(reason: String) { 217 | guard status != .Closed else { 218 | return 219 | } 220 | 221 | DefaultSocketLogger.Logger.log("Disconnected: %@", type: logType, args: reason) 222 | 223 | status = .Closed 224 | reconnects = false 225 | 226 | // Make sure the engine is actually dead. 227 | engine?.close(reason) 228 | handleEvent("disconnect", data: [reason], isInternalMessage: true) 229 | } 230 | 231 | /** 232 | Disconnects the socket. Only reconnect the same socket if you know what you're doing. 233 | Will turn off automatic reconnects. 234 | */ 235 | public func disconnect() { 236 | DefaultSocketLogger.Logger.log("Closing socket", type: logType) 237 | 238 | reconnects = false 239 | didDisconnect("Disconnect") 240 | } 241 | 242 | /** 243 | Send a message to the server 244 | */ 245 | public func emit(event: String, _ items: AnyObject...) { 246 | emit(event, withItems: items) 247 | } 248 | 249 | /** 250 | Same as emit, but meant for Objective-C 251 | */ 252 | public func emit(event: String, withItems items: [AnyObject]) { 253 | guard status == .Connected else { 254 | handleEvent("error", data: ["Tried emitting \(event) when not connected"], isInternalMessage: true) 255 | return 256 | } 257 | 258 | dispatch_async(emitQueue) { 259 | self._emit([event] + items) 260 | } 261 | } 262 | 263 | /** 264 | Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add 265 | an ack. 266 | */ 267 | public func emitWithAck(event: String, _ items: AnyObject...) -> OnAckCallback { 268 | return emitWithAck(event, withItems: items) 269 | } 270 | 271 | /** 272 | Same as emitWithAck, but for Objective-C 273 | */ 274 | public func emitWithAck(event: String, withItems items: [AnyObject]) -> OnAckCallback { 275 | return createOnAck([event] + items) 276 | } 277 | 278 | private func _emit(data: [AnyObject], ack: Int? = nil) { 279 | guard status == .Connected else { 280 | handleEvent("error", data: ["Tried emitting when not connected"], isInternalMessage: true) 281 | return 282 | } 283 | 284 | let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: nsp, ack: false) 285 | let str = packet.packetString 286 | 287 | DefaultSocketLogger.Logger.log("Emitting: %@", type: logType, args: str) 288 | 289 | engine?.send(str, withData: packet.binary) 290 | } 291 | 292 | // If the server wants to know that the client received data 293 | func emitAck(ack: Int, withItems items: [AnyObject]) { 294 | dispatch_async(emitQueue) { 295 | if self.status == .Connected { 296 | let packet = SocketPacket.packetFromEmit(items, id: ack ?? -1, nsp: self.nsp, ack: true) 297 | let str = packet.packetString 298 | 299 | DefaultSocketLogger.Logger.log("Emitting Ack: %@", type: self.logType, args: str) 300 | 301 | self.engine?.send(str, withData: packet.binary) 302 | } 303 | } 304 | } 305 | 306 | public func engineDidClose(reason: String) { 307 | waitingData.removeAll() 308 | 309 | if status == .Closed || !reconnects { 310 | didDisconnect(reason) 311 | } else if status != .Reconnecting { 312 | status = .Reconnecting 313 | handleEvent("reconnect", data: [reason], isInternalMessage: true) 314 | tryReconnect() 315 | } 316 | } 317 | 318 | /// error 319 | public func engineDidError(reason: String) { 320 | DefaultSocketLogger.Logger.error("%@", type: logType, args: reason) 321 | 322 | handleEvent("error", data: [reason], isInternalMessage: true) 323 | } 324 | 325 | // Called when the socket gets an ack for something it sent 326 | func handleAck(ack: Int, data: [AnyObject]) { 327 | guard status == .Connected else {return} 328 | 329 | DefaultSocketLogger.Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data ?? "") 330 | 331 | ackHandlers.executeAck(ack, items: data) 332 | } 333 | 334 | /** 335 | Causes an event to be handled. Only use if you know what you're doing. 336 | */ 337 | public func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int = -1) { 338 | guard status == .Connected || isInternalMessage else { 339 | return 340 | } 341 | 342 | DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "") 343 | 344 | dispatch_async(handleQueue) { 345 | self.anyHandler?(SocketAnyEvent(event: event, items: data)) 346 | 347 | for handler in self.handlers where handler.event == event { 348 | handler.executeCallback(data, withAck: ack, withSocket: self) 349 | } 350 | } 351 | } 352 | 353 | /** 354 | Leaves nsp and goes back to / 355 | */ 356 | public func leaveNamespace() { 357 | if nsp != "/" { 358 | engine?.send("1\(nsp)", withData: []) 359 | nsp = "/" 360 | } 361 | } 362 | 363 | /** 364 | Joins namespace 365 | */ 366 | public func joinNamespace(namespace: String) { 367 | nsp = namespace 368 | 369 | if nsp != "/" { 370 | DefaultSocketLogger.Logger.log("Joining namespace", type: logType) 371 | engine?.send("0\(nsp)", withData: []) 372 | } 373 | } 374 | 375 | /** 376 | Removes handler(s) 377 | */ 378 | public func off(event: String) { 379 | DefaultSocketLogger.Logger.log("Removing handler for event: %@", type: logType, args: event) 380 | 381 | handlers = handlers.filter { $0.event != event } 382 | } 383 | 384 | /** 385 | Removes a handler with the specified UUID gotten from an `on` or `once` 386 | */ 387 | public func off(id id: NSUUID) { 388 | DefaultSocketLogger.Logger.log("Removing handler with id: %@", type: logType, args: id) 389 | 390 | handlers = handlers.filter { $0.id != id } 391 | } 392 | 393 | /** 394 | Adds a handler for an event. 395 | Returns: A unique id for the handler 396 | */ 397 | public func on(event: String, callback: NormalCallback) -> NSUUID { 398 | DefaultSocketLogger.Logger.log("Adding handler for event: %@", type: logType, args: event) 399 | 400 | let handler = SocketEventHandler(event: event, id: NSUUID(), callback: callback) 401 | handlers.append(handler) 402 | 403 | return handler.id 404 | } 405 | 406 | /** 407 | Adds a single-use handler for an event. 408 | Returns: A unique id for the handler 409 | */ 410 | public func once(event: String, callback: NormalCallback) -> NSUUID { 411 | DefaultSocketLogger.Logger.log("Adding once handler for event: %@", type: logType, args: event) 412 | 413 | let id = NSUUID() 414 | 415 | let handler = SocketEventHandler(event: event, id: id) {[weak self] data, ack in 416 | guard let this = self else {return} 417 | this.off(id: id) 418 | callback(data, ack) 419 | } 420 | 421 | handlers.append(handler) 422 | 423 | return handler.id 424 | } 425 | 426 | /** 427 | Adds a handler that will be called on every event. 428 | */ 429 | public func onAny(handler: (SocketAnyEvent) -> Void) { 430 | anyHandler = handler 431 | } 432 | 433 | /** 434 | Same as connect 435 | */ 436 | @available(*, deprecated=6.0) 437 | public func open() { 438 | connect() 439 | } 440 | 441 | public func parseEngineMessage(msg: String) { 442 | DefaultSocketLogger.Logger.log("Should parse message: %@", type: "SocketIOClient", args: msg) 443 | 444 | dispatch_async(parseQueue) { 445 | self.parseSocketMessage(msg) 446 | } 447 | } 448 | 449 | public func parseEngineBinaryData(data: NSData) { 450 | dispatch_async(parseQueue) { 451 | self.parseBinaryData(data) 452 | } 453 | } 454 | 455 | /** 456 | Tries to reconnect to the server. 457 | */ 458 | public func reconnect() { 459 | tryReconnect() 460 | } 461 | 462 | /** 463 | Removes all handlers. 464 | Can be used after disconnecting to break any potential remaining retain cycles. 465 | */ 466 | public func removeAllHandlers() { 467 | handlers.removeAll(keepCapacity: false) 468 | } 469 | 470 | private func tryReconnect() { 471 | if reconnectTimer == nil { 472 | DefaultSocketLogger.Logger.log("Starting reconnect", type: logType) 473 | 474 | status = .Reconnecting 475 | 476 | dispatch_async(dispatch_get_main_queue()) { 477 | self.reconnectTimer = NSTimer.scheduledTimerWithTimeInterval(Double(self.reconnectWait), 478 | target: self, selector: "_tryReconnect", userInfo: nil, repeats: true) 479 | } 480 | } 481 | } 482 | 483 | @objc private func _tryReconnect() { 484 | if status == .Connected { 485 | clearReconnectTimer() 486 | 487 | return 488 | } 489 | 490 | if reconnectAttempts != -1 && currentReconnectAttempt + 1 > reconnectAttempts || !reconnects { 491 | clearReconnectTimer() 492 | didDisconnect("Reconnect Failed") 493 | 494 | return 495 | } 496 | 497 | DefaultSocketLogger.Logger.log("Trying to reconnect", type: logType) 498 | handleEvent("reconnectAttempt", data: [reconnectAttempts - currentReconnectAttempt], 499 | isInternalMessage: true) 500 | 501 | currentReconnectAttempt += 1 502 | connect() 503 | } 504 | } 505 | 506 | // Test extensions 507 | extension SocketIOClient { 508 | var testHandlers: [SocketEventHandler] { 509 | return handlers 510 | } 511 | 512 | func setTestable() { 513 | status = .Connected 514 | } 515 | 516 | func setTestEngine(engine: SocketEngineSpec?) { 517 | self.engine = engine 518 | } 519 | 520 | func emitTest(event: String, _ data: AnyObject...) { 521 | self._emit([event] + data) 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /Source/SocketEngine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEngine.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/3/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWebsocket { 28 | public let emitQueue = dispatch_queue_create("com.socketio.engineEmitQueue", DISPATCH_QUEUE_SERIAL) 29 | public let handleQueue = dispatch_queue_create("com.socketio.engineHandleQueue", DISPATCH_QUEUE_SERIAL) 30 | public let parseQueue = dispatch_queue_create("com.socketio.engineParseQueue", DISPATCH_QUEUE_SERIAL) 31 | 32 | public var connectParams: [String: AnyObject]? { 33 | didSet { 34 | (urlPolling, urlWebSocket) = createURLs() 35 | } 36 | } 37 | public var postWait = [String]() 38 | public var waitingForPoll = false 39 | public var waitingForPost = false 40 | 41 | public private(set) var closed = false 42 | public private(set) var connected = false 43 | public private(set) var cookies: [NSHTTPCookie]? 44 | public private(set) var extraHeaders: [String: String]? 45 | public private(set) var fastUpgrade = false 46 | public private(set) var forcePolling = false 47 | public private(set) var forceWebsockets = false 48 | public private(set) var invalidated = false 49 | public private(set) var pingTimer: NSTimer? 50 | public private(set) var polling = true 51 | public private(set) var probing = false 52 | public private(set) var session: NSURLSession? 53 | public private(set) var sid = "" 54 | public private(set) var socketPath = "/engine.io/" 55 | public private(set) var urlPolling = NSURL() 56 | public private(set) var urlWebSocket = NSURL() 57 | public private(set) var websocket = false 58 | public private(set) var ws: WebSocket? 59 | 60 | public weak var client: SocketEngineClient? 61 | 62 | private weak var sessionDelegate: NSURLSessionDelegate? 63 | 64 | private typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData]) 65 | private typealias ProbeWaitQueue = [Probe] 66 | 67 | private let logType = "SocketEngine" 68 | private let url: NSURL 69 | 70 | private var pingInterval: Double? 71 | private var pingTimeout = 0.0 { 72 | didSet { 73 | pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25)) 74 | } 75 | } 76 | private var pongsMissed = 0 77 | private var pongsMissedMax = 0 78 | private var probeWait = ProbeWaitQueue() 79 | private var secure = false 80 | private var selfSigned = false 81 | private var voipEnabled = false 82 | 83 | public init(client: SocketEngineClient, url: NSURL, options: Set) { 84 | self.client = client 85 | self.url = url 86 | 87 | for option in options { 88 | switch option { 89 | case let .ConnectParams(params): 90 | connectParams = params 91 | case let .SessionDelegate(delegate): 92 | sessionDelegate = delegate 93 | case let .ForcePolling(force): 94 | forcePolling = force 95 | case let .ForceWebsockets(force): 96 | forceWebsockets = force 97 | case let .Cookies(cookies): 98 | self.cookies = cookies 99 | case let .Path(path): 100 | socketPath = path 101 | case let .ExtraHeaders(headers): 102 | extraHeaders = headers 103 | case let .VoipEnabled(enable): 104 | voipEnabled = enable 105 | case let .Secure(secure): 106 | self.secure = secure 107 | case let .SelfSigned(selfSigned): 108 | self.selfSigned = selfSigned 109 | default: 110 | continue 111 | } 112 | } 113 | 114 | super.init() 115 | 116 | (urlPolling, urlWebSocket) = createURLs() 117 | } 118 | 119 | public convenience init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) { 120 | self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? []) 121 | } 122 | 123 | @available(*, deprecated=5.3) 124 | public convenience init(client: SocketEngineClient, urlString: String, options: Set) { 125 | guard let url = NSURL(string: urlString) else { fatalError("Incorrect url") } 126 | self.init(client: client, url: url, options: options) 127 | } 128 | 129 | @available(*, deprecated=5.3) 130 | public convenience init(client: SocketEngineClient, urlString: String, options: NSDictionary?) { 131 | guard let url = NSURL(string: urlString) else { fatalError("Incorrect url") } 132 | self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? []) 133 | } 134 | 135 | deinit { 136 | DefaultSocketLogger.Logger.log("Engine is being released", type: logType) 137 | closed = true 138 | stopPolling() 139 | } 140 | 141 | private func checkAndHandleEngineError(msg: String) { 142 | guard let stringData = msg.dataUsingEncoding(NSUTF8StringEncoding, 143 | allowLossyConversion: false) else { return } 144 | 145 | do { 146 | if let dict = try NSJSONSerialization.JSONObjectWithData(stringData, 147 | options: NSJSONReadingOptions.MutableContainers) as? NSDictionary { 148 | guard let code = dict["code"] as? Int else { return } 149 | guard let error = dict["message"] as? String else { return } 150 | 151 | switch code { 152 | case 0: // Unknown transport 153 | didError(error) 154 | case 1: // Unknown sid. 155 | didError(error) 156 | case 2: // Bad handshake request 157 | didError(error) 158 | case 3: // Bad request 159 | didError(error) 160 | default: 161 | didError(error) 162 | } 163 | } 164 | } catch { 165 | didError("Got unknown error from server \(msg)") 166 | } 167 | } 168 | 169 | private func checkIfMessageIsBase64Binary(message: String) -> Bool { 170 | if message.hasPrefix("b4") { 171 | // binary in base64 string 172 | let noPrefix = message[message.startIndex.advancedBy(2).. (NSURL, NSURL) { 212 | if client == nil { 213 | return (NSURL(), NSURL()) 214 | } 215 | 216 | let urlPolling = NSURLComponents(string: url.absoluteString)! 217 | let urlWebSocket = NSURLComponents(string: url.absoluteString)! 218 | var queryString = "" 219 | 220 | urlWebSocket.path = socketPath 221 | urlPolling.path = socketPath 222 | urlWebSocket.query = "transport=websocket" 223 | urlPolling.query = "transport=polling&b64=1" 224 | 225 | if secure { 226 | urlPolling.scheme = "https" 227 | urlWebSocket.scheme = "wss" 228 | } else { 229 | urlPolling.scheme = "http" 230 | urlWebSocket.scheme = "ws" 231 | } 232 | 233 | if connectParams != nil { 234 | for (key, value) in connectParams! { 235 | queryString += "&\(key)=\(value)" 236 | } 237 | } 238 | 239 | urlWebSocket.query = urlWebSocket.query! + queryString 240 | urlPolling.query = urlPolling.query! + queryString 241 | 242 | return (urlPolling.URL!, urlWebSocket.URL!) 243 | } 244 | 245 | private func createWebsocketAndConnect() { 246 | let component = NSURLComponents(URL: urlWebSocket, resolvingAgainstBaseURL: false)! 247 | component.query = component.query! + (sid == "" ? "" : "&sid=\(sid)") 248 | 249 | ws = WebSocket(url: component.URL!) 250 | 251 | if cookies != nil { 252 | let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) 253 | for (key, value) in headers { 254 | ws?.headers[key] = value 255 | } 256 | } 257 | 258 | if extraHeaders != nil { 259 | for (headerName, value) in extraHeaders! { 260 | ws?.headers[headerName] = value 261 | } 262 | } 263 | 264 | ws?.queue = handleQueue 265 | ws?.voipEnabled = voipEnabled 266 | ws?.delegate = self 267 | ws?.selfSignedSSL = selfSigned 268 | 269 | ws?.connect() 270 | } 271 | 272 | public func didError(error: String) { 273 | DefaultSocketLogger.Logger.error(error, type: logType) 274 | client?.engineDidError(error) 275 | close(error) 276 | } 277 | 278 | public func doFastUpgrade() { 279 | if waitingForPoll { 280 | DefaultSocketLogger.Logger.error("Outstanding poll when switched to WebSockets," + 281 | "we'll probably disconnect soon. You should report this.", type: logType) 282 | } 283 | 284 | sendWebSocketMessage("", withType: .Upgrade, withData: []) 285 | websocket = true 286 | polling = false 287 | fastUpgrade = false 288 | probing = false 289 | flushProbeWait() 290 | } 291 | 292 | private func flushProbeWait() { 293 | DefaultSocketLogger.Logger.log("Flushing probe wait", type: logType) 294 | 295 | dispatch_async(emitQueue) { 296 | for waiter in self.probeWait { 297 | self.write(waiter.msg, withType: waiter.type, withData: waiter.data) 298 | } 299 | 300 | self.probeWait.removeAll(keepCapacity: false) 301 | 302 | if self.postWait.count != 0 { 303 | self.flushWaitingForPostToWebSocket() 304 | } 305 | } 306 | } 307 | 308 | // We had packets waiting for send when we upgraded 309 | // Send them raw 310 | public func flushWaitingForPostToWebSocket() { 311 | guard let ws = self.ws else { return } 312 | 313 | for msg in postWait { 314 | ws.writeString(fixDoubleUTF8(msg)) 315 | } 316 | 317 | postWait.removeAll(keepCapacity: true) 318 | } 319 | 320 | private func handleClose(reason: String) { 321 | client?.engineDidClose(reason) 322 | } 323 | 324 | private func handleMessage(message: String) { 325 | client?.parseEngineMessage(message) 326 | } 327 | 328 | private func handleNOOP() { 329 | doPoll() 330 | } 331 | 332 | private func handleOpen(openData: String) { 333 | let mesData = openData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! 334 | do { 335 | let json = try NSJSONSerialization.JSONObjectWithData(mesData, 336 | options: NSJSONReadingOptions.AllowFragments) as? NSDictionary 337 | if let sid = json?["sid"] as? String { 338 | let upgradeWs: Bool 339 | 340 | self.sid = sid 341 | connected = true 342 | 343 | if let upgrades = json?["upgrades"] as? [String] { 344 | upgradeWs = upgrades.contains("websocket") 345 | } else { 346 | upgradeWs = false 347 | } 348 | 349 | if let pingInterval = json?["pingInterval"] as? Double, pingTimeout = json?["pingTimeout"] as? Double { 350 | self.pingInterval = pingInterval / 1000.0 351 | self.pingTimeout = pingTimeout / 1000.0 352 | } 353 | 354 | if !forcePolling && !forceWebsockets && upgradeWs { 355 | createWebsocketAndConnect() 356 | } 357 | 358 | 359 | startPingTimer() 360 | 361 | if !forceWebsockets { 362 | doPoll() 363 | } 364 | 365 | client?.engineDidOpen?("Connect") 366 | } 367 | } catch { 368 | didError("Error parsing open packet") 369 | return 370 | } 371 | } 372 | 373 | private func handlePong(pongMessage: String) { 374 | pongsMissed = 0 375 | 376 | // We should upgrade 377 | if pongMessage == "3probe" { 378 | upgradeTransport() 379 | } 380 | } 381 | 382 | public func open() { 383 | if connected { 384 | DefaultSocketLogger.Logger.error("Engine tried opening while connected. This is probably a programming error. " 385 | + "Abandoning open attempt", type: logType) 386 | return 387 | } 388 | 389 | DefaultSocketLogger.Logger.log("Starting engine", type: logType) 390 | DefaultSocketLogger.Logger.log("Handshaking", type: logType) 391 | 392 | resetEngine() 393 | 394 | if forceWebsockets { 395 | polling = false 396 | websocket = true 397 | createWebsocketAndConnect() 398 | return 399 | } 400 | 401 | let reqPolling = NSMutableURLRequest(URL: urlPolling) 402 | 403 | if cookies != nil { 404 | let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) 405 | reqPolling.allHTTPHeaderFields = headers 406 | } 407 | 408 | if let extraHeaders = extraHeaders { 409 | for (headerName, value) in extraHeaders { 410 | reqPolling.setValue(value, forHTTPHeaderField: headerName) 411 | } 412 | } 413 | 414 | doLongPoll(reqPolling) 415 | } 416 | 417 | public func parseEngineData(data: NSData) { 418 | DefaultSocketLogger.Logger.log("Got binary data: %@", type: "SocketEngine", args: data) 419 | client?.parseEngineBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) 420 | } 421 | 422 | public func parseEngineMessage(message: String, fromPolling: Bool) { 423 | DefaultSocketLogger.Logger.log("Got message: %@", type: logType, args: message) 424 | 425 | let reader = SocketStringReader(message: message) 426 | let fixedString: String 427 | 428 | guard let type = SocketEnginePacketType(rawValue: Int(reader.currentCharacter) ?? -1) else { 429 | if !checkIfMessageIsBase64Binary(message) { 430 | checkAndHandleEngineError(message) 431 | } 432 | 433 | return 434 | } 435 | 436 | if fromPolling && type != .Noop { 437 | fixedString = fixDoubleUTF8(message) 438 | } else { 439 | fixedString = message 440 | } 441 | 442 | switch type { 443 | case .Message: 444 | handleMessage(fixedString[fixedString.startIndex.successor().. pongsMissedMax { 477 | pingTimer?.invalidate() 478 | client?.engineDidClose("Ping timeout") 479 | return 480 | } 481 | 482 | pongsMissed += 1 483 | write("", withType: .Ping, withData: []) 484 | } 485 | 486 | // Starts the ping timer 487 | private func startPingTimer() { 488 | if let pingInterval = pingInterval { 489 | pingTimer?.invalidate() 490 | pingTimer = nil 491 | 492 | dispatch_async(dispatch_get_main_queue()) { 493 | self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(pingInterval, target: self, 494 | selector: Selector("sendPing"), userInfo: nil, repeats: true) 495 | } 496 | } 497 | } 498 | 499 | private func upgradeTransport() { 500 | if ws?.isConnected ?? false { 501 | DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: logType) 502 | 503 | fastUpgrade = true 504 | sendPollMessage("", withType: .Noop, withData: []) 505 | // After this point, we should not send anymore polling messages 506 | } 507 | } 508 | 509 | /** 510 | Write a message, independent of transport. 511 | */ 512 | public func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) { 513 | dispatch_async(emitQueue) { 514 | guard self.connected else { return } 515 | 516 | if self.websocket { 517 | DefaultSocketLogger.Logger.log("Writing ws: %@ has data: %@", 518 | type: self.logType, args: msg, data.count != 0) 519 | self.sendWebSocketMessage(msg, withType: type, withData: data) 520 | } else if !self.probing { 521 | DefaultSocketLogger.Logger.log("Writing poll: %@ has data: %@", 522 | type: self.logType, args: msg, data.count != 0) 523 | self.sendPollMessage(msg, withType: type, withData: data) 524 | } else { 525 | self.probeWait.append((msg, type, data)) 526 | } 527 | } 528 | } 529 | 530 | // Delegate methods 531 | public func websocketDidConnect(socket: WebSocket) { 532 | if !forceWebsockets { 533 | probing = true 534 | probeWebSocket() 535 | } else { 536 | connected = true 537 | probing = false 538 | polling = false 539 | } 540 | } 541 | 542 | public func websocketDidDisconnect(socket: WebSocket, error: NSError?) { 543 | probing = false 544 | 545 | if closed { 546 | client?.engineDidClose("Disconnect") 547 | return 548 | } 549 | 550 | if websocket { 551 | pingTimer?.invalidate() 552 | connected = false 553 | websocket = false 554 | 555 | let reason = error?.localizedDescription ?? "Socket Disconnected" 556 | 557 | if error != nil { 558 | didError(reason) 559 | } 560 | 561 | client?.engineDidClose(reason) 562 | } else { 563 | flushProbeWait() 564 | } 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /SocketChat.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C075D71D1C5E0E81009F9044 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D71C1C5E0E81009F9044 /* AppDelegate.swift */; }; 11 | C075D7221C5E0E81009F9044 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C075D7201C5E0E81009F9044 /* Main.storyboard */; }; 12 | C075D7241C5E0E81009F9044 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C075D7231C5E0E81009F9044 /* Assets.xcassets */; }; 13 | C075D7271C5E0E81009F9044 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C075D7251C5E0E81009F9044 /* LaunchScreen.storyboard */; }; 14 | C075D7301C5E0EB1009F9044 /* UsersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D72F1C5E0EB1009F9044 /* UsersViewController.swift */; }; 15 | C075D7321C5E0EBB009F9044 /* ChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7311C5E0EBB009F9044 /* ChatViewController.swift */; }; 16 | C075D7351C5E0EDC009F9044 /* UserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C075D7341C5E0EDC009F9044 /* UserCell.xib */; }; 17 | C075D7371C5E0EED009F9044 /* UserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7361C5E0EED009F9044 /* UserCell.swift */; }; 18 | C075D7391C5E0EFC009F9044 /* ChatCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C075D7381C5E0EFC009F9044 /* ChatCell.xib */; }; 19 | C075D73B1C5E0F10009F9044 /* ChatCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D73A1C5E0F10009F9044 /* ChatCell.swift */; }; 20 | C075D73D1C5E0F4E009F9044 /* BaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D73C1C5E0F4E009F9044 /* BaseCell.swift */; }; 21 | C075D7561C5E17DD009F9044 /* SocketAckEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7401C5E17DD009F9044 /* SocketAckEmitter.swift */; }; 22 | C075D7571C5E17DD009F9044 /* SocketAckManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7411C5E17DD009F9044 /* SocketAckManager.swift */; }; 23 | C075D7581C5E17DD009F9044 /* SocketAnyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7421C5E17DD009F9044 /* SocketAnyEvent.swift */; }; 24 | C075D7591C5E17DD009F9044 /* SocketClientSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7431C5E17DD009F9044 /* SocketClientSpec.swift */; }; 25 | C075D75A1C5E17DD009F9044 /* SocketEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7441C5E17DD009F9044 /* SocketEngine.swift */; }; 26 | C075D75B1C5E17DD009F9044 /* SocketEngineClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7451C5E17DD009F9044 /* SocketEngineClient.swift */; }; 27 | C075D75C1C5E17DD009F9044 /* SocketEnginePacketType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7461C5E17DD009F9044 /* SocketEnginePacketType.swift */; }; 28 | C075D75D1C5E17DD009F9044 /* SocketEnginePollable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7471C5E17DD009F9044 /* SocketEnginePollable.swift */; }; 29 | C075D75E1C5E17DD009F9044 /* SocketEngineSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7481C5E17DD009F9044 /* SocketEngineSpec.swift */; }; 30 | C075D75F1C5E17DD009F9044 /* SocketEngineWebsocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7491C5E17DD009F9044 /* SocketEngineWebsocket.swift */; }; 31 | C075D7601C5E17DD009F9044 /* SocketEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74A1C5E17DD009F9044 /* SocketEventHandler.swift */; }; 32 | C075D7611C5E17DD009F9044 /* SocketFixUTF8.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74B1C5E17DD009F9044 /* SocketFixUTF8.swift */; }; 33 | C075D7621C5E17DD009F9044 /* SocketIOClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74C1C5E17DD009F9044 /* SocketIOClient.swift */; }; 34 | C075D7631C5E17DD009F9044 /* SocketIOClientOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74D1C5E17DD009F9044 /* SocketIOClientOption.swift */; }; 35 | C075D7641C5E17DD009F9044 /* SocketIOClientStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74E1C5E17DD009F9044 /* SocketIOClientStatus.swift */; }; 36 | C075D7651C5E17DD009F9044 /* SocketLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74F1C5E17DD009F9044 /* SocketLogger.swift */; }; 37 | C075D7661C5E17DD009F9044 /* SocketPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7501C5E17DD009F9044 /* SocketPacket.swift */; }; 38 | C075D7671C5E17DD009F9044 /* SocketParsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7511C5E17DD009F9044 /* SocketParsable.swift */; }; 39 | C075D7681C5E17DD009F9044 /* SocketStringReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7521C5E17DD009F9044 /* SocketStringReader.swift */; }; 40 | C075D7691C5E17DD009F9044 /* SocketTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7531C5E17DD009F9044 /* SocketTypes.swift */; }; 41 | C075D76A1C5E17DD009F9044 /* SwiftRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7541C5E17DD009F9044 /* SwiftRegex.swift */; }; 42 | C075D76B1C5E17DD009F9044 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7551C5E17DD009F9044 /* WebSocket.swift */; }; 43 | C075D76E1C5E187A009F9044 /* SocketIOManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D76D1C5E187A009F9044 /* SocketIOManager.swift */; }; 44 | /* End PBXBuildFile section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | C075D7191C5E0E81009F9044 /* SocketChat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SocketChat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | C075D71C1C5E0E81009F9044 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49 | C075D7211C5E0E81009F9044 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 50 | C075D7231C5E0E81009F9044 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | C075D7261C5E0E81009F9044 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | C075D7281C5E0E81009F9044 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | C075D72F1C5E0EB1009F9044 /* UsersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersViewController.swift; sourceTree = ""; }; 54 | C075D7311C5E0EBB009F9044 /* ChatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatViewController.swift; sourceTree = ""; }; 55 | C075D7341C5E0EDC009F9044 /* UserCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UserCell.xib; sourceTree = ""; }; 56 | C075D7361C5E0EED009F9044 /* UserCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserCell.swift; sourceTree = ""; }; 57 | C075D7381C5E0EFC009F9044 /* ChatCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ChatCell.xib; sourceTree = ""; }; 58 | C075D73A1C5E0F10009F9044 /* ChatCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatCell.swift; sourceTree = ""; }; 59 | C075D73C1C5E0F4E009F9044 /* BaseCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCell.swift; sourceTree = ""; }; 60 | C075D7401C5E17DD009F9044 /* SocketAckEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAckEmitter.swift; sourceTree = ""; }; 61 | C075D7411C5E17DD009F9044 /* SocketAckManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAckManager.swift; sourceTree = ""; }; 62 | C075D7421C5E17DD009F9044 /* SocketAnyEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAnyEvent.swift; sourceTree = ""; }; 63 | C075D7431C5E17DD009F9044 /* SocketClientSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketClientSpec.swift; sourceTree = ""; }; 64 | C075D7441C5E17DD009F9044 /* SocketEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngine.swift; sourceTree = ""; }; 65 | C075D7451C5E17DD009F9044 /* SocketEngineClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineClient.swift; sourceTree = ""; }; 66 | C075D7461C5E17DD009F9044 /* SocketEnginePacketType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEnginePacketType.swift; sourceTree = ""; }; 67 | C075D7471C5E17DD009F9044 /* SocketEnginePollable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEnginePollable.swift; sourceTree = ""; }; 68 | C075D7481C5E17DD009F9044 /* SocketEngineSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineSpec.swift; sourceTree = ""; }; 69 | C075D7491C5E17DD009F9044 /* SocketEngineWebsocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineWebsocket.swift; sourceTree = ""; }; 70 | C075D74A1C5E17DD009F9044 /* SocketEventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEventHandler.swift; sourceTree = ""; }; 71 | C075D74B1C5E17DD009F9044 /* SocketFixUTF8.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketFixUTF8.swift; sourceTree = ""; }; 72 | C075D74C1C5E17DD009F9044 /* SocketIOClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClient.swift; sourceTree = ""; }; 73 | C075D74D1C5E17DD009F9044 /* SocketIOClientOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientOption.swift; sourceTree = ""; }; 74 | C075D74E1C5E17DD009F9044 /* SocketIOClientStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientStatus.swift; sourceTree = ""; }; 75 | C075D74F1C5E17DD009F9044 /* SocketLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketLogger.swift; sourceTree = ""; }; 76 | C075D7501C5E17DD009F9044 /* SocketPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketPacket.swift; sourceTree = ""; }; 77 | C075D7511C5E17DD009F9044 /* SocketParsable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketParsable.swift; sourceTree = ""; }; 78 | C075D7521C5E17DD009F9044 /* SocketStringReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketStringReader.swift; sourceTree = ""; }; 79 | C075D7531C5E17DD009F9044 /* SocketTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketTypes.swift; sourceTree = ""; }; 80 | C075D7541C5E17DD009F9044 /* SwiftRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftRegex.swift; sourceTree = ""; }; 81 | C075D7551C5E17DD009F9044 /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; 82 | C075D76D1C5E187A009F9044 /* SocketIOManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOManager.swift; sourceTree = ""; }; 83 | /* End PBXFileReference section */ 84 | 85 | /* Begin PBXFrameworksBuildPhase section */ 86 | C075D7161C5E0E81009F9044 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXFrameworksBuildPhase section */ 94 | 95 | /* Begin PBXGroup section */ 96 | C075D7101C5E0E81009F9044 = { 97 | isa = PBXGroup; 98 | children = ( 99 | C075D73E1C5E17A2009F9044 /* SocketIO */, 100 | C075D71B1C5E0E81009F9044 /* SocketChat */, 101 | C075D71A1C5E0E81009F9044 /* Products */, 102 | ); 103 | sourceTree = ""; 104 | }; 105 | C075D71A1C5E0E81009F9044 /* Products */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | C075D7191C5E0E81009F9044 /* SocketChat.app */, 109 | ); 110 | name = Products; 111 | sourceTree = ""; 112 | }; 113 | C075D71B1C5E0E81009F9044 /* SocketChat */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | C075D76C1C5E186C009F9044 /* SocketIO */, 117 | C075D7331C5E0EC9009F9044 /* Custom Cells */, 118 | C075D72E1C5E0E96009F9044 /* View Controllers */, 119 | C075D71C1C5E0E81009F9044 /* AppDelegate.swift */, 120 | C075D7201C5E0E81009F9044 /* Main.storyboard */, 121 | C075D7231C5E0E81009F9044 /* Assets.xcassets */, 122 | C075D7251C5E0E81009F9044 /* LaunchScreen.storyboard */, 123 | C075D7281C5E0E81009F9044 /* Info.plist */, 124 | ); 125 | path = SocketChat; 126 | sourceTree = ""; 127 | }; 128 | C075D72E1C5E0E96009F9044 /* View Controllers */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | C075D72F1C5E0EB1009F9044 /* UsersViewController.swift */, 132 | C075D7311C5E0EBB009F9044 /* ChatViewController.swift */, 133 | ); 134 | name = "View Controllers"; 135 | sourceTree = ""; 136 | }; 137 | C075D7331C5E0EC9009F9044 /* Custom Cells */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | C075D73C1C5E0F4E009F9044 /* BaseCell.swift */, 141 | C075D7361C5E0EED009F9044 /* UserCell.swift */, 142 | C075D7341C5E0EDC009F9044 /* UserCell.xib */, 143 | C075D73A1C5E0F10009F9044 /* ChatCell.swift */, 144 | C075D7381C5E0EFC009F9044 /* ChatCell.xib */, 145 | ); 146 | name = "Custom Cells"; 147 | sourceTree = ""; 148 | }; 149 | C075D73E1C5E17A2009F9044 /* SocketIO */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | C075D73F1C5E17DD009F9044 /* Source */, 153 | ); 154 | name = SocketIO; 155 | sourceTree = ""; 156 | }; 157 | C075D73F1C5E17DD009F9044 /* Source */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | C075D7401C5E17DD009F9044 /* SocketAckEmitter.swift */, 161 | C075D7411C5E17DD009F9044 /* SocketAckManager.swift */, 162 | C075D7421C5E17DD009F9044 /* SocketAnyEvent.swift */, 163 | C075D7431C5E17DD009F9044 /* SocketClientSpec.swift */, 164 | C075D7441C5E17DD009F9044 /* SocketEngine.swift */, 165 | C075D7451C5E17DD009F9044 /* SocketEngineClient.swift */, 166 | C075D7461C5E17DD009F9044 /* SocketEnginePacketType.swift */, 167 | C075D7471C5E17DD009F9044 /* SocketEnginePollable.swift */, 168 | C075D7481C5E17DD009F9044 /* SocketEngineSpec.swift */, 169 | C075D7491C5E17DD009F9044 /* SocketEngineWebsocket.swift */, 170 | C075D74A1C5E17DD009F9044 /* SocketEventHandler.swift */, 171 | C075D74B1C5E17DD009F9044 /* SocketFixUTF8.swift */, 172 | C075D74C1C5E17DD009F9044 /* SocketIOClient.swift */, 173 | C075D74D1C5E17DD009F9044 /* SocketIOClientOption.swift */, 174 | C075D74E1C5E17DD009F9044 /* SocketIOClientStatus.swift */, 175 | C075D74F1C5E17DD009F9044 /* SocketLogger.swift */, 176 | C075D7501C5E17DD009F9044 /* SocketPacket.swift */, 177 | C075D7511C5E17DD009F9044 /* SocketParsable.swift */, 178 | C075D7521C5E17DD009F9044 /* SocketStringReader.swift */, 179 | C075D7531C5E17DD009F9044 /* SocketTypes.swift */, 180 | C075D7541C5E17DD009F9044 /* SwiftRegex.swift */, 181 | C075D7551C5E17DD009F9044 /* WebSocket.swift */, 182 | ); 183 | path = Source; 184 | sourceTree = ""; 185 | }; 186 | C075D76C1C5E186C009F9044 /* SocketIO */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | C075D76D1C5E187A009F9044 /* SocketIOManager.swift */, 190 | ); 191 | name = SocketIO; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXGroup section */ 195 | 196 | /* Begin PBXNativeTarget section */ 197 | C075D7181C5E0E81009F9044 /* SocketChat */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = C075D72B1C5E0E81009F9044 /* Build configuration list for PBXNativeTarget "SocketChat" */; 200 | buildPhases = ( 201 | C075D7151C5E0E81009F9044 /* Sources */, 202 | C075D7161C5E0E81009F9044 /* Frameworks */, 203 | C075D7171C5E0E81009F9044 /* Resources */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | ); 209 | name = SocketChat; 210 | productName = SocketChat; 211 | productReference = C075D7191C5E0E81009F9044 /* SocketChat.app */; 212 | productType = "com.apple.product-type.application"; 213 | }; 214 | /* End PBXNativeTarget section */ 215 | 216 | /* Begin PBXProject section */ 217 | C075D7111C5E0E81009F9044 /* Project object */ = { 218 | isa = PBXProject; 219 | attributes = { 220 | LastSwiftUpdateCheck = 0720; 221 | LastUpgradeCheck = 0720; 222 | ORGANIZATIONNAME = AppCoda; 223 | TargetAttributes = { 224 | C075D7181C5E0E81009F9044 = { 225 | CreatedOnToolsVersion = 7.2; 226 | }; 227 | }; 228 | }; 229 | buildConfigurationList = C075D7141C5E0E81009F9044 /* Build configuration list for PBXProject "SocketChat" */; 230 | compatibilityVersion = "Xcode 3.2"; 231 | developmentRegion = English; 232 | hasScannedForEncodings = 0; 233 | knownRegions = ( 234 | en, 235 | Base, 236 | ); 237 | mainGroup = C075D7101C5E0E81009F9044; 238 | productRefGroup = C075D71A1C5E0E81009F9044 /* Products */; 239 | projectDirPath = ""; 240 | projectRoot = ""; 241 | targets = ( 242 | C075D7181C5E0E81009F9044 /* SocketChat */, 243 | ); 244 | }; 245 | /* End PBXProject section */ 246 | 247 | /* Begin PBXResourcesBuildPhase section */ 248 | C075D7171C5E0E81009F9044 /* Resources */ = { 249 | isa = PBXResourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | C075D7351C5E0EDC009F9044 /* UserCell.xib in Resources */, 253 | C075D7271C5E0E81009F9044 /* LaunchScreen.storyboard in Resources */, 254 | C075D7391C5E0EFC009F9044 /* ChatCell.xib in Resources */, 255 | C075D7241C5E0E81009F9044 /* Assets.xcassets in Resources */, 256 | C075D7221C5E0E81009F9044 /* Main.storyboard in Resources */, 257 | ); 258 | runOnlyForDeploymentPostprocessing = 0; 259 | }; 260 | /* End PBXResourcesBuildPhase section */ 261 | 262 | /* Begin PBXSourcesBuildPhase section */ 263 | C075D7151C5E0E81009F9044 /* Sources */ = { 264 | isa = PBXSourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | C075D7371C5E0EED009F9044 /* UserCell.swift in Sources */, 268 | C075D75B1C5E17DD009F9044 /* SocketEngineClient.swift in Sources */, 269 | C075D7671C5E17DD009F9044 /* SocketParsable.swift in Sources */, 270 | C075D75A1C5E17DD009F9044 /* SocketEngine.swift in Sources */, 271 | C075D75E1C5E17DD009F9044 /* SocketEngineSpec.swift in Sources */, 272 | C075D7301C5E0EB1009F9044 /* UsersViewController.swift in Sources */, 273 | C075D7581C5E17DD009F9044 /* SocketAnyEvent.swift in Sources */, 274 | C075D7321C5E0EBB009F9044 /* ChatViewController.swift in Sources */, 275 | C075D7591C5E17DD009F9044 /* SocketClientSpec.swift in Sources */, 276 | C075D73B1C5E0F10009F9044 /* ChatCell.swift in Sources */, 277 | C075D7611C5E17DD009F9044 /* SocketFixUTF8.swift in Sources */, 278 | C075D76A1C5E17DD009F9044 /* SwiftRegex.swift in Sources */, 279 | C075D7571C5E17DD009F9044 /* SocketAckManager.swift in Sources */, 280 | C075D76B1C5E17DD009F9044 /* WebSocket.swift in Sources */, 281 | C075D7561C5E17DD009F9044 /* SocketAckEmitter.swift in Sources */, 282 | C075D75C1C5E17DD009F9044 /* SocketEnginePacketType.swift in Sources */, 283 | C075D7661C5E17DD009F9044 /* SocketPacket.swift in Sources */, 284 | C075D7601C5E17DD009F9044 /* SocketEventHandler.swift in Sources */, 285 | C075D7631C5E17DD009F9044 /* SocketIOClientOption.swift in Sources */, 286 | C075D76E1C5E187A009F9044 /* SocketIOManager.swift in Sources */, 287 | C075D75F1C5E17DD009F9044 /* SocketEngineWebsocket.swift in Sources */, 288 | C075D7641C5E17DD009F9044 /* SocketIOClientStatus.swift in Sources */, 289 | C075D75D1C5E17DD009F9044 /* SocketEnginePollable.swift in Sources */, 290 | C075D7621C5E17DD009F9044 /* SocketIOClient.swift in Sources */, 291 | C075D7681C5E17DD009F9044 /* SocketStringReader.swift in Sources */, 292 | C075D7691C5E17DD009F9044 /* SocketTypes.swift in Sources */, 293 | C075D73D1C5E0F4E009F9044 /* BaseCell.swift in Sources */, 294 | C075D7651C5E17DD009F9044 /* SocketLogger.swift in Sources */, 295 | C075D71D1C5E0E81009F9044 /* AppDelegate.swift in Sources */, 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | /* End PBXSourcesBuildPhase section */ 300 | 301 | /* Begin PBXVariantGroup section */ 302 | C075D7201C5E0E81009F9044 /* Main.storyboard */ = { 303 | isa = PBXVariantGroup; 304 | children = ( 305 | C075D7211C5E0E81009F9044 /* Base */, 306 | ); 307 | name = Main.storyboard; 308 | sourceTree = ""; 309 | }; 310 | C075D7251C5E0E81009F9044 /* LaunchScreen.storyboard */ = { 311 | isa = PBXVariantGroup; 312 | children = ( 313 | C075D7261C5E0E81009F9044 /* Base */, 314 | ); 315 | name = LaunchScreen.storyboard; 316 | sourceTree = ""; 317 | }; 318 | /* End PBXVariantGroup section */ 319 | 320 | /* Begin XCBuildConfiguration section */ 321 | C075D7291C5E0E81009F9044 /* Debug */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ALWAYS_SEARCH_USER_PATHS = NO; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_WARN_BOOL_CONVERSION = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 332 | CLANG_WARN_EMPTY_BODY = YES; 333 | CLANG_WARN_ENUM_CONVERSION = YES; 334 | CLANG_WARN_INT_CONVERSION = YES; 335 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 336 | CLANG_WARN_UNREACHABLE_CODE = YES; 337 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 338 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 339 | COPY_PHASE_STRIP = NO; 340 | DEBUG_INFORMATION_FORMAT = dwarf; 341 | ENABLE_STRICT_OBJC_MSGSEND = YES; 342 | ENABLE_TESTABILITY = YES; 343 | GCC_C_LANGUAGE_STANDARD = gnu99; 344 | GCC_DYNAMIC_NO_PIC = NO; 345 | GCC_NO_COMMON_BLOCKS = YES; 346 | GCC_OPTIMIZATION_LEVEL = 0; 347 | GCC_PREPROCESSOR_DEFINITIONS = ( 348 | "DEBUG=1", 349 | "$(inherited)", 350 | ); 351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 352 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 353 | GCC_WARN_UNDECLARED_SELECTOR = YES; 354 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 355 | GCC_WARN_UNUSED_FUNCTION = YES; 356 | GCC_WARN_UNUSED_VARIABLE = YES; 357 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 358 | MTL_ENABLE_DEBUG_INFO = YES; 359 | ONLY_ACTIVE_ARCH = YES; 360 | SDKROOT = iphoneos; 361 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 362 | TARGETED_DEVICE_FAMILY = "1,2"; 363 | }; 364 | name = Debug; 365 | }; 366 | C075D72A1C5E0E81009F9044 /* Release */ = { 367 | isa = XCBuildConfiguration; 368 | buildSettings = { 369 | ALWAYS_SEARCH_USER_PATHS = NO; 370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 371 | CLANG_CXX_LIBRARY = "libc++"; 372 | CLANG_ENABLE_MODULES = YES; 373 | CLANG_ENABLE_OBJC_ARC = YES; 374 | CLANG_WARN_BOOL_CONVERSION = YES; 375 | CLANG_WARN_CONSTANT_CONVERSION = YES; 376 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 377 | CLANG_WARN_EMPTY_BODY = YES; 378 | CLANG_WARN_ENUM_CONVERSION = YES; 379 | CLANG_WARN_INT_CONVERSION = YES; 380 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 381 | CLANG_WARN_UNREACHABLE_CODE = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 384 | COPY_PHASE_STRIP = NO; 385 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 386 | ENABLE_NS_ASSERTIONS = NO; 387 | ENABLE_STRICT_OBJC_MSGSEND = YES; 388 | GCC_C_LANGUAGE_STANDARD = gnu99; 389 | GCC_NO_COMMON_BLOCKS = YES; 390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 392 | GCC_WARN_UNDECLARED_SELECTOR = YES; 393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 394 | GCC_WARN_UNUSED_FUNCTION = YES; 395 | GCC_WARN_UNUSED_VARIABLE = YES; 396 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 397 | MTL_ENABLE_DEBUG_INFO = NO; 398 | SDKROOT = iphoneos; 399 | TARGETED_DEVICE_FAMILY = "1,2"; 400 | VALIDATE_PRODUCT = YES; 401 | }; 402 | name = Release; 403 | }; 404 | C075D72C1C5E0E81009F9044 /* Debug */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 408 | INFOPLIST_FILE = SocketChat/Info.plist; 409 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 410 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 411 | PRODUCT_BUNDLE_IDENTIFIER = com.appcoda.SocketChat; 412 | PRODUCT_NAME = "$(TARGET_NAME)"; 413 | }; 414 | name = Debug; 415 | }; 416 | C075D72D1C5E0E81009F9044 /* Release */ = { 417 | isa = XCBuildConfiguration; 418 | buildSettings = { 419 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 420 | INFOPLIST_FILE = SocketChat/Info.plist; 421 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 422 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 423 | PRODUCT_BUNDLE_IDENTIFIER = com.appcoda.SocketChat; 424 | PRODUCT_NAME = "$(TARGET_NAME)"; 425 | }; 426 | name = Release; 427 | }; 428 | /* End XCBuildConfiguration section */ 429 | 430 | /* Begin XCConfigurationList section */ 431 | C075D7141C5E0E81009F9044 /* Build configuration list for PBXProject "SocketChat" */ = { 432 | isa = XCConfigurationList; 433 | buildConfigurations = ( 434 | C075D7291C5E0E81009F9044 /* Debug */, 435 | C075D72A1C5E0E81009F9044 /* Release */, 436 | ); 437 | defaultConfigurationIsVisible = 0; 438 | defaultConfigurationName = Release; 439 | }; 440 | C075D72B1C5E0E81009F9044 /* Build configuration list for PBXNativeTarget "SocketChat" */ = { 441 | isa = XCConfigurationList; 442 | buildConfigurations = ( 443 | C075D72C1C5E0E81009F9044 /* Debug */, 444 | C075D72D1C5E0E81009F9044 /* Release */, 445 | ); 446 | defaultConfigurationIsVisible = 0; 447 | defaultConfigurationName = Release; 448 | }; 449 | /* End XCConfigurationList section */ 450 | }; 451 | rootObject = C075D7111C5E0E81009F9044 /* Project object */; 452 | } 453 | --------------------------------------------------------------------------------