diff --git a/SocketChat.xcodeproj/project.pbxproj b/SocketChat.xcodeproj/project.pbxproj index a21c3d1..81732a6 100644 --- a/SocketChat.xcodeproj/project.pbxproj +++ b/SocketChat.xcodeproj/project.pbxproj @@ -7,6 +7,30 @@ objects = { /* Begin PBXBuildFile section */ + 6D8346D41DAD915900A535F9 /* SocketAckEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346BC1DAD915900A535F9 /* SocketAckEmitter.swift */; }; + 6D8346D51DAD915900A535F9 /* SocketAckManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346BD1DAD915900A535F9 /* SocketAckManager.swift */; }; + 6D8346D61DAD915900A535F9 /* SocketAnyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346BE1DAD915900A535F9 /* SocketAnyEvent.swift */; }; + 6D8346D71DAD915900A535F9 /* SocketClientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346BF1DAD915900A535F9 /* SocketClientManager.swift */; }; + 6D8346D81DAD915900A535F9 /* SocketEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346C01DAD915900A535F9 /* SocketEngine.swift */; }; + 6D8346D91DAD915900A535F9 /* SocketEngineClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346C11DAD915900A535F9 /* SocketEngineClient.swift */; }; + 6D8346DA1DAD915900A535F9 /* SocketEnginePacketType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346C21DAD915900A535F9 /* SocketEnginePacketType.swift */; }; + 6D8346DB1DAD915900A535F9 /* SocketEnginePollable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346C31DAD915900A535F9 /* SocketEnginePollable.swift */; }; + 6D8346DC1DAD915900A535F9 /* SocketEngineSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346C41DAD915900A535F9 /* SocketEngineSpec.swift */; }; + 6D8346DD1DAD915900A535F9 /* SocketEngineWebsocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346C51DAD915900A535F9 /* SocketEngineWebsocket.swift */; }; + 6D8346DE1DAD915900A535F9 /* SocketEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346C61DAD915900A535F9 /* SocketEventHandler.swift */; }; + 6D8346DF1DAD915900A535F9 /* SocketExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346C71DAD915900A535F9 /* SocketExtensions.swift */; }; + 6D8346E01DAD915900A535F9 /* SocketIOClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346C81DAD915900A535F9 /* SocketIOClient.swift */; }; + 6D8346E11DAD915900A535F9 /* SocketIOClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346C91DAD915900A535F9 /* SocketIOClientConfiguration.swift */; }; + 6D8346E21DAD915900A535F9 /* SocketIOClientOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346CA1DAD915900A535F9 /* SocketIOClientOption.swift */; }; + 6D8346E31DAD915900A535F9 /* SocketIOClientSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346CB1DAD915900A535F9 /* SocketIOClientSpec.swift */; }; + 6D8346E41DAD915900A535F9 /* SocketIOClientStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346CC1DAD915900A535F9 /* SocketIOClientStatus.swift */; }; + 6D8346E51DAD915900A535F9 /* SocketLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346CD1DAD915900A535F9 /* SocketLogger.swift */; }; + 6D8346E61DAD915900A535F9 /* SocketPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346CE1DAD915900A535F9 /* SocketPacket.swift */; }; + 6D8346E71DAD915900A535F9 /* SocketParsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346CF1DAD915900A535F9 /* SocketParsable.swift */; }; + 6D8346E81DAD915900A535F9 /* SocketStringReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346D01DAD915900A535F9 /* SocketStringReader.swift */; }; + 6D8346E91DAD915900A535F9 /* SocketTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346D11DAD915900A535F9 /* SocketTypes.swift */; }; + 6D8346EA1DAD915900A535F9 /* SSLSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346D21DAD915900A535F9 /* SSLSecurity.swift */; }; + 6D8346EB1DAD915900A535F9 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8346D31DAD915900A535F9 /* WebSocket.swift */; }; C075D71D1C5E0E81009F9044 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D71C1C5E0E81009F9044 /* AppDelegate.swift */; }; C075D7221C5E0E81009F9044 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C075D7201C5E0E81009F9044 /* Main.storyboard */; }; C075D7241C5E0E81009F9044 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C075D7231C5E0E81009F9044 /* Assets.xcassets */; }; @@ -18,32 +42,34 @@ C075D7391C5E0EFC009F9044 /* ChatCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C075D7381C5E0EFC009F9044 /* ChatCell.xib */; }; C075D73B1C5E0F10009F9044 /* ChatCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D73A1C5E0F10009F9044 /* ChatCell.swift */; }; C075D73D1C5E0F4E009F9044 /* BaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D73C1C5E0F4E009F9044 /* BaseCell.swift */; }; - C075D7561C5E17DD009F9044 /* SocketAckEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7401C5E17DD009F9044 /* SocketAckEmitter.swift */; }; - C075D7571C5E17DD009F9044 /* SocketAckManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7411C5E17DD009F9044 /* SocketAckManager.swift */; }; - C075D7581C5E17DD009F9044 /* SocketAnyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7421C5E17DD009F9044 /* SocketAnyEvent.swift */; }; - C075D7591C5E17DD009F9044 /* SocketClientSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7431C5E17DD009F9044 /* SocketClientSpec.swift */; }; - C075D75A1C5E17DD009F9044 /* SocketEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7441C5E17DD009F9044 /* SocketEngine.swift */; }; - C075D75B1C5E17DD009F9044 /* SocketEngineClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7451C5E17DD009F9044 /* SocketEngineClient.swift */; }; - C075D75C1C5E17DD009F9044 /* SocketEnginePacketType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7461C5E17DD009F9044 /* SocketEnginePacketType.swift */; }; - C075D75D1C5E17DD009F9044 /* SocketEnginePollable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7471C5E17DD009F9044 /* SocketEnginePollable.swift */; }; - C075D75E1C5E17DD009F9044 /* SocketEngineSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7481C5E17DD009F9044 /* SocketEngineSpec.swift */; }; - C075D75F1C5E17DD009F9044 /* SocketEngineWebsocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7491C5E17DD009F9044 /* SocketEngineWebsocket.swift */; }; - C075D7601C5E17DD009F9044 /* SocketEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74A1C5E17DD009F9044 /* SocketEventHandler.swift */; }; - C075D7611C5E17DD009F9044 /* SocketFixUTF8.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74B1C5E17DD009F9044 /* SocketFixUTF8.swift */; }; - C075D7621C5E17DD009F9044 /* SocketIOClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74C1C5E17DD009F9044 /* SocketIOClient.swift */; }; - C075D7631C5E17DD009F9044 /* SocketIOClientOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74D1C5E17DD009F9044 /* SocketIOClientOption.swift */; }; - C075D7641C5E17DD009F9044 /* SocketIOClientStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74E1C5E17DD009F9044 /* SocketIOClientStatus.swift */; }; - C075D7651C5E17DD009F9044 /* SocketLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D74F1C5E17DD009F9044 /* SocketLogger.swift */; }; - C075D7661C5E17DD009F9044 /* SocketPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7501C5E17DD009F9044 /* SocketPacket.swift */; }; - C075D7671C5E17DD009F9044 /* SocketParsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7511C5E17DD009F9044 /* SocketParsable.swift */; }; - C075D7681C5E17DD009F9044 /* SocketStringReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7521C5E17DD009F9044 /* SocketStringReader.swift */; }; - C075D7691C5E17DD009F9044 /* SocketTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7531C5E17DD009F9044 /* SocketTypes.swift */; }; - C075D76A1C5E17DD009F9044 /* SwiftRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7541C5E17DD009F9044 /* SwiftRegex.swift */; }; - C075D76B1C5E17DD009F9044 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D7551C5E17DD009F9044 /* WebSocket.swift */; }; C075D76E1C5E187A009F9044 /* SocketIOManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C075D76D1C5E187A009F9044 /* SocketIOManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 6D8346BC1DAD915900A535F9 /* SocketAckEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAckEmitter.swift; sourceTree = ""; }; + 6D8346BD1DAD915900A535F9 /* SocketAckManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAckManager.swift; sourceTree = ""; }; + 6D8346BE1DAD915900A535F9 /* SocketAnyEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAnyEvent.swift; sourceTree = ""; }; + 6D8346BF1DAD915900A535F9 /* SocketClientManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketClientManager.swift; sourceTree = ""; }; + 6D8346C01DAD915900A535F9 /* SocketEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngine.swift; sourceTree = ""; }; + 6D8346C11DAD915900A535F9 /* SocketEngineClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineClient.swift; sourceTree = ""; }; + 6D8346C21DAD915900A535F9 /* SocketEnginePacketType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEnginePacketType.swift; sourceTree = ""; }; + 6D8346C31DAD915900A535F9 /* SocketEnginePollable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEnginePollable.swift; sourceTree = ""; }; + 6D8346C41DAD915900A535F9 /* SocketEngineSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineSpec.swift; sourceTree = ""; }; + 6D8346C51DAD915900A535F9 /* SocketEngineWebsocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineWebsocket.swift; sourceTree = ""; }; + 6D8346C61DAD915900A535F9 /* SocketEventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEventHandler.swift; sourceTree = ""; }; + 6D8346C71DAD915900A535F9 /* SocketExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketExtensions.swift; sourceTree = ""; }; + 6D8346C81DAD915900A535F9 /* SocketIOClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClient.swift; sourceTree = ""; }; + 6D8346C91DAD915900A535F9 /* SocketIOClientConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientConfiguration.swift; sourceTree = ""; }; + 6D8346CA1DAD915900A535F9 /* SocketIOClientOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientOption.swift; sourceTree = ""; }; + 6D8346CB1DAD915900A535F9 /* SocketIOClientSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientSpec.swift; sourceTree = ""; }; + 6D8346CC1DAD915900A535F9 /* SocketIOClientStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientStatus.swift; sourceTree = ""; }; + 6D8346CD1DAD915900A535F9 /* SocketLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketLogger.swift; sourceTree = ""; }; + 6D8346CE1DAD915900A535F9 /* SocketPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketPacket.swift; sourceTree = ""; }; + 6D8346CF1DAD915900A535F9 /* SocketParsable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketParsable.swift; sourceTree = ""; }; + 6D8346D01DAD915900A535F9 /* SocketStringReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketStringReader.swift; sourceTree = ""; }; + 6D8346D11DAD915900A535F9 /* SocketTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketTypes.swift; sourceTree = ""; }; + 6D8346D21DAD915900A535F9 /* SSLSecurity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSLSecurity.swift; sourceTree = ""; }; + 6D8346D31DAD915900A535F9 /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; C075D7191C5E0E81009F9044 /* SocketChat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SocketChat.app; sourceTree = BUILT_PRODUCTS_DIR; }; C075D71C1C5E0E81009F9044 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C075D7211C5E0E81009F9044 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -57,28 +83,6 @@ C075D7381C5E0EFC009F9044 /* ChatCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ChatCell.xib; sourceTree = ""; }; C075D73A1C5E0F10009F9044 /* ChatCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatCell.swift; sourceTree = ""; }; C075D73C1C5E0F4E009F9044 /* BaseCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCell.swift; sourceTree = ""; }; - C075D7401C5E17DD009F9044 /* SocketAckEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAckEmitter.swift; sourceTree = ""; }; - C075D7411C5E17DD009F9044 /* SocketAckManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAckManager.swift; sourceTree = ""; }; - C075D7421C5E17DD009F9044 /* SocketAnyEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketAnyEvent.swift; sourceTree = ""; }; - C075D7431C5E17DD009F9044 /* SocketClientSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketClientSpec.swift; sourceTree = ""; }; - C075D7441C5E17DD009F9044 /* SocketEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngine.swift; sourceTree = ""; }; - C075D7451C5E17DD009F9044 /* SocketEngineClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineClient.swift; sourceTree = ""; }; - C075D7461C5E17DD009F9044 /* SocketEnginePacketType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEnginePacketType.swift; sourceTree = ""; }; - C075D7471C5E17DD009F9044 /* SocketEnginePollable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEnginePollable.swift; sourceTree = ""; }; - C075D7481C5E17DD009F9044 /* SocketEngineSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineSpec.swift; sourceTree = ""; }; - C075D7491C5E17DD009F9044 /* SocketEngineWebsocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEngineWebsocket.swift; sourceTree = ""; }; - C075D74A1C5E17DD009F9044 /* SocketEventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketEventHandler.swift; sourceTree = ""; }; - C075D74B1C5E17DD009F9044 /* SocketFixUTF8.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketFixUTF8.swift; sourceTree = ""; }; - C075D74C1C5E17DD009F9044 /* SocketIOClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClient.swift; sourceTree = ""; }; - C075D74D1C5E17DD009F9044 /* SocketIOClientOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientOption.swift; sourceTree = ""; }; - C075D74E1C5E17DD009F9044 /* SocketIOClientStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOClientStatus.swift; sourceTree = ""; }; - C075D74F1C5E17DD009F9044 /* SocketLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketLogger.swift; sourceTree = ""; }; - C075D7501C5E17DD009F9044 /* SocketPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketPacket.swift; sourceTree = ""; }; - C075D7511C5E17DD009F9044 /* SocketParsable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketParsable.swift; sourceTree = ""; }; - C075D7521C5E17DD009F9044 /* SocketStringReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketStringReader.swift; sourceTree = ""; }; - C075D7531C5E17DD009F9044 /* SocketTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketTypes.swift; sourceTree = ""; }; - C075D7541C5E17DD009F9044 /* SwiftRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftRegex.swift; sourceTree = ""; }; - C075D7551C5E17DD009F9044 /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; C075D76D1C5E187A009F9044 /* SocketIOManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketIOManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -157,28 +161,30 @@ C075D73F1C5E17DD009F9044 /* Source */ = { isa = PBXGroup; children = ( - C075D7401C5E17DD009F9044 /* SocketAckEmitter.swift */, - C075D7411C5E17DD009F9044 /* SocketAckManager.swift */, - C075D7421C5E17DD009F9044 /* SocketAnyEvent.swift */, - C075D7431C5E17DD009F9044 /* SocketClientSpec.swift */, - C075D7441C5E17DD009F9044 /* SocketEngine.swift */, - C075D7451C5E17DD009F9044 /* SocketEngineClient.swift */, - C075D7461C5E17DD009F9044 /* SocketEnginePacketType.swift */, - C075D7471C5E17DD009F9044 /* SocketEnginePollable.swift */, - C075D7481C5E17DD009F9044 /* SocketEngineSpec.swift */, - C075D7491C5E17DD009F9044 /* SocketEngineWebsocket.swift */, - C075D74A1C5E17DD009F9044 /* SocketEventHandler.swift */, - C075D74B1C5E17DD009F9044 /* SocketFixUTF8.swift */, - C075D74C1C5E17DD009F9044 /* SocketIOClient.swift */, - C075D74D1C5E17DD009F9044 /* SocketIOClientOption.swift */, - C075D74E1C5E17DD009F9044 /* SocketIOClientStatus.swift */, - C075D74F1C5E17DD009F9044 /* SocketLogger.swift */, - C075D7501C5E17DD009F9044 /* SocketPacket.swift */, - C075D7511C5E17DD009F9044 /* SocketParsable.swift */, - C075D7521C5E17DD009F9044 /* SocketStringReader.swift */, - C075D7531C5E17DD009F9044 /* SocketTypes.swift */, - C075D7541C5E17DD009F9044 /* SwiftRegex.swift */, - C075D7551C5E17DD009F9044 /* WebSocket.swift */, + 6D8346BC1DAD915900A535F9 /* SocketAckEmitter.swift */, + 6D8346BD1DAD915900A535F9 /* SocketAckManager.swift */, + 6D8346BE1DAD915900A535F9 /* SocketAnyEvent.swift */, + 6D8346BF1DAD915900A535F9 /* SocketClientManager.swift */, + 6D8346C01DAD915900A535F9 /* SocketEngine.swift */, + 6D8346C11DAD915900A535F9 /* SocketEngineClient.swift */, + 6D8346C21DAD915900A535F9 /* SocketEnginePacketType.swift */, + 6D8346C31DAD915900A535F9 /* SocketEnginePollable.swift */, + 6D8346C41DAD915900A535F9 /* SocketEngineSpec.swift */, + 6D8346C51DAD915900A535F9 /* SocketEngineWebsocket.swift */, + 6D8346C61DAD915900A535F9 /* SocketEventHandler.swift */, + 6D8346C71DAD915900A535F9 /* SocketExtensions.swift */, + 6D8346C81DAD915900A535F9 /* SocketIOClient.swift */, + 6D8346C91DAD915900A535F9 /* SocketIOClientConfiguration.swift */, + 6D8346CA1DAD915900A535F9 /* SocketIOClientOption.swift */, + 6D8346CB1DAD915900A535F9 /* SocketIOClientSpec.swift */, + 6D8346CC1DAD915900A535F9 /* SocketIOClientStatus.swift */, + 6D8346CD1DAD915900A535F9 /* SocketLogger.swift */, + 6D8346CE1DAD915900A535F9 /* SocketPacket.swift */, + 6D8346CF1DAD915900A535F9 /* SocketParsable.swift */, + 6D8346D01DAD915900A535F9 /* SocketStringReader.swift */, + 6D8346D11DAD915900A535F9 /* SocketTypes.swift */, + 6D8346D21DAD915900A535F9 /* SSLSecurity.swift */, + 6D8346D31DAD915900A535F9 /* WebSocket.swift */, ); path = Source; sourceTree = ""; @@ -218,11 +224,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = AppCoda; TargetAttributes = { C075D7181C5E0E81009F9044 = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0800; }; }; }; @@ -264,34 +271,36 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6D8346D91DAD915900A535F9 /* SocketEngineClient.swift in Sources */, + 6D8346E41DAD915900A535F9 /* SocketIOClientStatus.swift in Sources */, + 6D8346D81DAD915900A535F9 /* SocketEngine.swift in Sources */, + 6D8346E11DAD915900A535F9 /* SocketIOClientConfiguration.swift in Sources */, + 6D8346D61DAD915900A535F9 /* SocketAnyEvent.swift in Sources */, C075D7371C5E0EED009F9044 /* UserCell.swift in Sources */, - C075D75B1C5E17DD009F9044 /* SocketEngineClient.swift in Sources */, - C075D7671C5E17DD009F9044 /* SocketParsable.swift in Sources */, - C075D75A1C5E17DD009F9044 /* SocketEngine.swift in Sources */, - C075D75E1C5E17DD009F9044 /* SocketEngineSpec.swift in Sources */, + 6D8346E61DAD915900A535F9 /* SocketPacket.swift in Sources */, C075D7301C5E0EB1009F9044 /* UsersViewController.swift in Sources */, - C075D7581C5E17DD009F9044 /* SocketAnyEvent.swift in Sources */, C075D7321C5E0EBB009F9044 /* ChatViewController.swift in Sources */, - C075D7591C5E17DD009F9044 /* SocketClientSpec.swift in Sources */, + 6D8346D51DAD915900A535F9 /* SocketAckManager.swift in Sources */, + 6D8346DB1DAD915900A535F9 /* SocketEnginePollable.swift in Sources */, + 6D8346DF1DAD915900A535F9 /* SocketExtensions.swift in Sources */, + 6D8346E51DAD915900A535F9 /* SocketLogger.swift in Sources */, + 6D8346E21DAD915900A535F9 /* SocketIOClientOption.swift in Sources */, + 6D8346E71DAD915900A535F9 /* SocketParsable.swift in Sources */, + 6D8346D71DAD915900A535F9 /* SocketClientManager.swift in Sources */, + 6D8346EA1DAD915900A535F9 /* SSLSecurity.swift in Sources */, + 6D8346DD1DAD915900A535F9 /* SocketEngineWebsocket.swift in Sources */, + 6D8346D41DAD915900A535F9 /* SocketAckEmitter.swift in Sources */, + 6D8346E81DAD915900A535F9 /* SocketStringReader.swift in Sources */, C075D73B1C5E0F10009F9044 /* ChatCell.swift in Sources */, - C075D7611C5E17DD009F9044 /* SocketFixUTF8.swift in Sources */, - C075D76A1C5E17DD009F9044 /* SwiftRegex.swift in Sources */, - C075D7571C5E17DD009F9044 /* SocketAckManager.swift in Sources */, - C075D76B1C5E17DD009F9044 /* WebSocket.swift in Sources */, - C075D7561C5E17DD009F9044 /* SocketAckEmitter.swift in Sources */, - C075D75C1C5E17DD009F9044 /* SocketEnginePacketType.swift in Sources */, - C075D7661C5E17DD009F9044 /* SocketPacket.swift in Sources */, - C075D7601C5E17DD009F9044 /* SocketEventHandler.swift in Sources */, - C075D7631C5E17DD009F9044 /* SocketIOClientOption.swift in Sources */, + 6D8346EB1DAD915900A535F9 /* WebSocket.swift in Sources */, + 6D8346DE1DAD915900A535F9 /* SocketEventHandler.swift in Sources */, + 6D8346DC1DAD915900A535F9 /* SocketEngineSpec.swift in Sources */, + 6D8346E91DAD915900A535F9 /* SocketTypes.swift in Sources */, + 6D8346E31DAD915900A535F9 /* SocketIOClientSpec.swift in Sources */, + 6D8346E01DAD915900A535F9 /* SocketIOClient.swift in Sources */, + 6D8346DA1DAD915900A535F9 /* SocketEnginePacketType.swift in Sources */, C075D76E1C5E187A009F9044 /* SocketIOManager.swift in Sources */, - C075D75F1C5E17DD009F9044 /* SocketEngineWebsocket.swift in Sources */, - C075D7641C5E17DD009F9044 /* SocketIOClientStatus.swift in Sources */, - C075D75D1C5E17DD009F9044 /* SocketEnginePollable.swift in Sources */, - C075D7621C5E17DD009F9044 /* SocketIOClient.swift in Sources */, - C075D7681C5E17DD009F9044 /* SocketStringReader.swift in Sources */, - C075D7691C5E17DD009F9044 /* SocketTypes.swift in Sources */, C075D73D1C5E0F4E009F9044 /* BaseCell.swift in Sources */, - C075D7651C5E17DD009F9044 /* SocketLogger.swift in Sources */, C075D71D1C5E0E81009F9044 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -331,8 +340,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -376,8 +387,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -396,6 +409,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -410,6 +424,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appcoda.SocketChat; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -422,6 +437,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appcoda.SocketChat; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/SocketChat.xcodeproj/project.xcworkspace/xcuserdata/exxon.xcuserdatad/UserInterfaceState.xcuserstate b/SocketChat.xcodeproj/project.xcworkspace/xcuserdata/exxon.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..6fc1f81 Binary files /dev/null and b/SocketChat.xcodeproj/project.xcworkspace/xcuserdata/exxon.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/SocketChat.xcodeproj/xcuserdata/exxon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/SocketChat.xcodeproj/xcuserdata/exxon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..fe2b454 --- /dev/null +++ b/SocketChat.xcodeproj/xcuserdata/exxon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,5 @@ + + + diff --git a/SocketChat.xcodeproj/xcuserdata/exxon.xcuserdatad/xcschemes/SocketChat.xcscheme b/SocketChat.xcodeproj/xcuserdata/exxon.xcuserdatad/xcschemes/SocketChat.xcscheme new file mode 100644 index 0000000..95d87dd --- /dev/null +++ b/SocketChat.xcodeproj/xcuserdata/exxon.xcuserdatad/xcschemes/SocketChat.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SocketChat.xcodeproj/xcuserdata/exxon.xcuserdatad/xcschemes/xcschememanagement.plist b/SocketChat.xcodeproj/xcuserdata/exxon.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..ca61b36 --- /dev/null +++ b/SocketChat.xcodeproj/xcuserdata/exxon.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + SocketChat.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + C075D7181C5E0E81009F9044 + + primary + + + + + diff --git a/SocketChat/AppDelegate.swift b/SocketChat/AppDelegate.swift index 3020aa5..a6a341e 100644 --- a/SocketChat/AppDelegate.swift +++ b/SocketChat/AppDelegate.swift @@ -14,34 +14,34 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + private func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. return true } - func applicationWillResignActive(application: UIApplication) { + func applicationWillResignActive(_ application: UIApplication) { // 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. // 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. } - func applicationDidEnterBackground(application: UIApplication) { + func applicationDidEnterBackground(_ application: UIApplication) { // 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. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. SocketIOManager.sharedInstance.closeConnection() } - func applicationWillEnterForeground(application: UIApplication) { + func applicationWillEnterForeground(_ application: UIApplication) { // 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. } - func applicationDidBecomeActive(application: UIApplication) { + func applicationDidBecomeActive(_ application: UIApplication) { // 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. SocketIOManager.sharedInstance.establishConnection() } - func applicationWillTerminate(application: UIApplication) { + func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } diff --git a/SocketChat/BaseCell.swift b/SocketChat/BaseCell.swift index 22a536f..7e15889 100644 --- a/SocketChat/BaseCell.swift +++ b/SocketChat/BaseCell.swift @@ -7,6 +7,7 @@ // import UIKit +import Foundation class BaseCell: UITableViewCell { @@ -14,16 +15,16 @@ class BaseCell: UITableViewCell { super.awakeFromNib() // Initialization code - separatorInset = UIEdgeInsetsZero + separatorInset = UIEdgeInsetsMake(0, 0, 0, 0) preservesSuperviewLayoutMargins = false - layoutMargins = UIEdgeInsetsZero + layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0) layoutIfNeeded() // Set the selection style to None. - selectionStyle = UITableViewCellSelectionStyle.None + selectionStyle = UITableViewCellSelectionStyle.none } - override func setSelected(selected: Bool, animated: Bool) { + override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state diff --git a/SocketChat/ChatCell.swift b/SocketChat/ChatCell.swift index ad71d4c..ce17293 100644 --- a/SocketChat/ChatCell.swift +++ b/SocketChat/ChatCell.swift @@ -21,7 +21,7 @@ class ChatCell: BaseCell { } - override func setSelected(selected: Bool, animated: Bool) { + override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state diff --git a/SocketChat/ChatViewController.swift b/SocketChat/ChatViewController.swift index 6fd4378..7f408c0 100644 --- a/SocketChat/ChatViewController.swift +++ b/SocketChat/ChatViewController.swift @@ -26,7 +26,7 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData var chatMessages = [[String: AnyObject]]() - var bannerLabelTimer: NSTimer! + var bannerLabelTimer: Timer! override func viewDidLoad() { @@ -34,22 +34,22 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData // Do any additional setup after loading the view. - NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleKeyboardDidShowNotification:", name: UIKeyboardDidShowNotification, object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleKeyboardDidHideNotification:", name: UIKeyboardDidHideNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: Selector(("handleKeyboardDidShowNotification:")), name: NSNotification.Name.UIKeyboardDidShow, object: nil) + NotificationCenter.default.addObserver(self, selector: Selector(("handleKeyboardDidHideNotification:")), name: NSNotification.Name.UIKeyboardDidHide, object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleConnectedUserUpdateNotification:", name: "userWasConnectedNotification", object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleDisconnectedUserUpdateNotification:", name: "userWasDisconnectedNotification", object: nil) - NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleUserTypingNotification:", name: "userTypingNotification", object: nil) + NotificationCenter.default.addObserver(self, selector: Selector(("handleConnectedUserUpdateNotification:")), name: NSNotification.Name(rawValue: "userWasConnectedNotification"), object: nil) + NotificationCenter.default.addObserver(self, selector: Selector(("handleDisconnectedUserUpdateNotification:")), name: NSNotification.Name(rawValue: "userWasDisconnectedNotification"), object: nil) + NotificationCenter.default.addObserver(self, selector: Selector(("handleUserTypingNotification:")), name: NSNotification.Name(rawValue: "userTypingNotification"), object: nil) - let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "dismissKeyboard") - swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Down + let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(ChatViewController.dismissKeyboard)) + swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.down swipeGestureRecognizer.delegate = self view.addGestureRecognizer(swipeGestureRecognizer) } - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) configureTableView() @@ -60,11 +60,11 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData } - override func viewDidAppear(animated: Bool) { + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) SocketIOManager.sharedInstance.getChatMessage { (messageInfo) -> Void in - dispatch_async(dispatch_get_main_queue(), { () -> Void in + DispatchQueue.main.async(execute: { () -> Void in self.chatMessages.append(messageInfo) self.tblChat.reloadData() self.scrollToBottom() @@ -80,7 +80,7 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData deinit { - NSNotificationCenter.defaultCenter().removeObserver(self) + NotificationCenter.default.removeObserver(self) } @@ -99,7 +99,7 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData @IBAction func sendMessage(sender: AnyObject) { if tvMessageEditor.text.characters.count > 0 { - SocketIOManager.sharedInstance.sendMessage(tvMessageEditor.text!, withNickname: nickname) + SocketIOManager.sharedInstance.sendMessage(message: tvMessageEditor.text!, withNickname: nickname) tvMessageEditor.text = "" tvMessageEditor.resignFirstResponder() } @@ -111,10 +111,10 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData func configureTableView() { tblChat.delegate = self tblChat.dataSource = self - tblChat.registerNib(UINib(nibName: "ChatCell", bundle: nil), forCellReuseIdentifier: "idCellChat") + tblChat.register(UINib(nibName: "ChatCell", bundle: nil), forCellReuseIdentifier: "idCellChat") tblChat.estimatedRowHeight = 90.0 tblChat.rowHeight = UITableViewAutomaticDimension - tblChat.tableFooterView = UIView(frame: CGRectZero) + tblChat.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0)) } @@ -126,14 +126,14 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData func configureOtherUserActivityLabel() { - lblOtherUserActivityStatus.hidden = true + lblOtherUserActivityStatus.isHidden = true lblOtherUserActivityStatus.text = "" } func handleKeyboardDidShowNotification(notification: NSNotification) { if let userInfo = notification.userInfo { - if let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() { + if let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { conBottomEditor.constant = keyboardFrame.size.height view.layoutIfNeeded() } @@ -150,21 +150,21 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData func scrollToBottom() { let delay = 0.1 * Double(NSEC_PER_SEC) - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay)), dispatch_get_main_queue()) { () -> Void in + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { if self.chatMessages.count > 0 { - let lastRowIndexPath = NSIndexPath(forRow: self.chatMessages.count - 1, inSection: 0) - self.tblChat.scrollToRowAtIndexPath(lastRowIndexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true) + let lastRowIndexPath = IndexPath(row: self.chatMessages.count - 1, section: 0) + self.tblChat.scrollToRow(at: lastRowIndexPath, at: UITableViewScrollPosition.bottom, animated: true) } } } func showBannerLabelAnimated() { - UIView.animateWithDuration(0.75, animations: { () -> Void in + UIView.animate(withDuration: 0.75, animations: { () -> Void in self.lblNewsBanner.alpha = 1.0 }) { (finished) -> Void in - self.bannerLabelTimer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "hideBannerLabel", userInfo: nil, repeats: false) + self.bannerLabelTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(ChatViewController.hideBannerLabel), userInfo: nil, repeats: false) } } @@ -175,7 +175,7 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData bannerLabelTimer = nil } - UIView.animateWithDuration(0.75, animations: { () -> Void in + UIView.animate(withDuration: 0.75, animations: { () -> Void in self.lblNewsBanner.alpha = 0.0 }) { (finished) -> Void in @@ -185,10 +185,10 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData func dismissKeyboard() { - if tvMessageEditor.isFirstResponder() { + if tvMessageEditor.isFirstResponder { tvMessageEditor.resignFirstResponder() - SocketIOManager.sharedInstance.sendStopTypingMessage(nickname) + SocketIOManager.sharedInstance.sendStopTypingMessage(nickname: nickname) } } @@ -196,14 +196,14 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData func handleConnectedUserUpdateNotification(notification: NSNotification) { let connectedUserInfo = notification.object as! [String: AnyObject] let connectedUserNickname = connectedUserInfo["nickname"] as? String - lblNewsBanner.text = "User \(connectedUserNickname!.uppercaseString) was just connected." + lblNewsBanner.text = "User \(connectedUserNickname?.uppercased()) was just connected." showBannerLabelAnimated() } func handleDisconnectedUserUpdateNotification(notification: NSNotification) { let disconnectedUserNickname = notification.object as! String - lblNewsBanner.text = "User \(disconnectedUserNickname.uppercaseString) has left." + lblNewsBanner.text = "User \(disconnectedUserNickname.uppercased()) has left." showBannerLabelAnimated() } @@ -223,10 +223,10 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData let verb = (totalTypingUsers == 1) ? "is" : "are" lblOtherUserActivityStatus.text = "\(names) \(verb) now typing a message..." - lblOtherUserActivityStatus.hidden = false + lblOtherUserActivityStatus.isHidden = false } else { - lblOtherUserActivityStatus.hidden = true + lblOtherUserActivityStatus.isHidden = true } } @@ -235,18 +235,18 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData // MARK: UITableView Delegate and Datasource Methods - func numberOfSectionsInTableView(tableView: UITableView) -> Int { + private func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } - func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return chatMessages.count } - func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCellWithIdentifier("idCellChat", forIndexPath: indexPath) as! ChatCell + internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "idCellChat", for: indexPath as IndexPath) as! ChatCell let currentChatMessage = chatMessages[indexPath.row] let senderNickname = currentChatMessage["nickname"] as! String @@ -254,16 +254,16 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData let messageDate = currentChatMessage["date"] as! String if senderNickname == nickname { - cell.lblChatMessage.textAlignment = NSTextAlignment.Right - cell.lblMessageDetails.textAlignment = NSTextAlignment.Right + cell.lblChatMessage.textAlignment = NSTextAlignment.right + cell.lblMessageDetails.textAlignment = NSTextAlignment.right cell.lblChatMessage.textColor = lblNewsBanner.backgroundColor } cell.lblChatMessage.text = message - cell.lblMessageDetails.text = "by \(senderNickname.uppercaseString) @ \(messageDate)" + cell.lblMessageDetails.text = "by \(senderNickname.uppercased()) @ \(messageDate)" - cell.lblChatMessage.textColor = UIColor.darkGrayColor() + cell.lblChatMessage.textColor = UIColor.darkGray return cell } @@ -271,8 +271,8 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData // MARK: UITextViewDelegate Methods - func textViewShouldBeginEditing(textView: UITextView) -> Bool { - SocketIOManager.sharedInstance.sendStartTypingMessage(nickname) + func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { + SocketIOManager.sharedInstance.sendStartTypingMessage(nickname: nickname) return true } @@ -280,7 +280,7 @@ class ChatViewController: UIViewController, UITableViewDelegate, UITableViewData // MARK: UIGestureRecognizerDelegate Methods - func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } diff --git a/SocketChat/SocketIOManager.swift b/SocketChat/SocketIOManager.swift index 0e7fa4b..c2af0e8 100644 --- a/SocketChat/SocketIOManager.swift +++ b/SocketChat/SocketIOManager.swift @@ -11,7 +11,7 @@ import UIKit class SocketIOManager: NSObject { static let sharedInstance = SocketIOManager() - var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: "http://192.168.1.XXX:3000")!) + var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: "http://192.168.1.XXX:3000")! as URL) override init() { @@ -29,11 +29,11 @@ class SocketIOManager: NSObject { } - func connectToServerWithNickname(nickname: String, completionHandler: (userList: [[String: AnyObject]]!) -> Void) { + func connectToServerWithNickname(nickname: String, completionHandler: @escaping (_ userList: [[String: AnyObject]]?) -> Void) { socket.emit("connectUser", nickname) socket.on("userList") { ( dataArray, ack) -> Void in - completionHandler(userList: dataArray[0] as! [[String: AnyObject]]) + completionHandler(dataArray[0] as? [[String: AnyObject]]) } listenForOtherMessages() @@ -51,29 +51,29 @@ class SocketIOManager: NSObject { } - func getChatMessage(completionHandler: (messageInfo: [String: AnyObject]) -> Void) { + func getChatMessage(completionHandler: @escaping (_ messageInfo: [String: AnyObject]) -> Void) { socket.on("newChatMessage") { (dataArray, socketAck) -> Void in var messageDictionary = [String: AnyObject]() - messageDictionary["nickname"] = dataArray[0] as! String - messageDictionary["message"] = dataArray[1] as! String - messageDictionary["date"] = dataArray[2] as! String + messageDictionary["nickname"] = dataArray[0] as! String as AnyObject? + messageDictionary["message"] = dataArray[1] as! String as AnyObject? + messageDictionary["date"] = dataArray[2] as! String as AnyObject? - completionHandler(messageInfo: messageDictionary) + completionHandler(messageDictionary) } } private func listenForOtherMessages() { socket.on("userConnectUpdate") { (dataArray, socketAck) -> Void in - NSNotificationCenter.defaultCenter().postNotificationName("userWasConnectedNotification", object: dataArray[0] as! [String: AnyObject]) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "userWasConnectedNotification"), object: dataArray[0] as! [String: AnyObject]) } socket.on("userExitUpdate") { (dataArray, socketAck) -> Void in - NSNotificationCenter.defaultCenter().postNotificationName("userWasDisconnectedNotification", object: dataArray[0] as! String) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "userWasDisconnectedNotification"), object: dataArray[0] as! String) } socket.on("userTypingUpdate") { (dataArray, socketAck) -> Void in - NSNotificationCenter.defaultCenter().postNotificationName("userTypingNotification", object: dataArray[0] as? [String: AnyObject]) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "userTypingNotification"), object: dataArray[0] as? [String: AnyObject]) } } diff --git a/SocketChat/UserCell.swift b/SocketChat/UserCell.swift index f48ddec..9d0ad8a 100644 --- a/SocketChat/UserCell.swift +++ b/SocketChat/UserCell.swift @@ -16,7 +16,7 @@ class UserCell: BaseCell { // Initialization code } - override func setSelected(selected: Bool, animated: Bool) { + override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state diff --git a/SocketChat/UsersViewController.swift b/SocketChat/UsersViewController.swift index ec7598d..aea60d9 100644 --- a/SocketChat/UsersViewController.swift +++ b/SocketChat/UsersViewController.swift @@ -27,7 +27,7 @@ class UsersViewController: UIViewController, UITableViewDelegate, UITableViewDat } - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if !configurationOK { @@ -39,7 +39,7 @@ class UsersViewController: UIViewController, UITableViewDelegate, UITableViewDat } - override func viewDidAppear(animated: Bool) { + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if nickname == nil { @@ -58,10 +58,10 @@ class UsersViewController: UIViewController, UITableViewDelegate, UITableViewDat // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if let identifier = segue.identifier { if identifier == "idSegueJoinChat" { - let chatViewController = segue.destinationViewController as! ChatViewController + let chatViewController = segue.destination as! ChatViewController chatViewController.nickname = nickname } } @@ -71,11 +71,11 @@ class UsersViewController: UIViewController, UITableViewDelegate, UITableViewDat // MARK: IBAction Methods @IBAction func exitChat(sender: AnyObject) { - SocketIOManager.sharedInstance.exitChatWithNickname(nickname) { () -> Void in - dispatch_async(dispatch_get_main_queue(), { () -> Void in + SocketIOManager.sharedInstance.exitChatWithNickname(nickname: nickname) { () -> Void in + DispatchQueue.main.async(execute: { () -> Void in self.nickname = nil self.users.removeAll() - self.tblUserList.hidden = true + self.tblUserList.isHidden = true self.askForNickname() }) } @@ -93,18 +93,18 @@ class UsersViewController: UIViewController, UITableViewDelegate, UITableViewDat func configureTableView() { tblUserList.delegate = self tblUserList.dataSource = self - tblUserList.registerNib(UINib(nibName: "UserCell", bundle: nil), forCellReuseIdentifier: "idCellUser") - tblUserList.hidden = true - tblUserList.tableFooterView = UIView(frame: CGRectZero) + tblUserList.register(UINib(nibName: "UserCell", bundle: nil), forCellReuseIdentifier: "idCellUser") + tblUserList.isHidden = true + tblUserList.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0)) } func askForNickname() { - let alertController = UIAlertController(title: "SocketChat", message: "Please enter a nickname:", preferredStyle: UIAlertControllerStyle.Alert) + let alertController = UIAlertController(title: "SocketChat", message: "Please enter a nickname:", preferredStyle: UIAlertControllerStyle.alert) - alertController.addTextFieldWithConfigurationHandler(nil) + alertController.addTextField(configurationHandler: nil) - let OKAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (action) -> Void in + let OKAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) { (action) -> Void in let textfield = alertController.textFields![0] if textfield.text?.characters.count == 0 { self.askForNickname() @@ -112,12 +112,12 @@ class UsersViewController: UIViewController, UITableViewDelegate, UITableViewDat else { self.nickname = textfield.text - SocketIOManager.sharedInstance.connectToServerWithNickname(self.nickname, completionHandler: { (userList) -> Void in - dispatch_async(dispatch_get_main_queue(), { () -> Void in + SocketIOManager.sharedInstance.connectToServerWithNickname(nickname: self.nickname, completionHandler: { (userList) -> Void in + DispatchQueue.main.async(execute: { () -> Void in if userList != nil { - self.users = userList + self.users = userList! self.tblUserList.reloadData() - self.tblUserList.hidden = false + self.tblUserList.isHidden = false } }) }) @@ -125,34 +125,34 @@ class UsersViewController: UIViewController, UITableViewDelegate, UITableViewDat } alertController.addAction(OKAction) - presentViewController(alertController, animated: true, completion: nil) + present(alertController, animated: true, completion: nil) } // MARK: UITableView Delegate and Datasource methods - func numberOfSectionsInTableView(tableView: UITableView) -> Int { + private func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } - func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return users.count } - func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCellWithIdentifier("idCellUser", forIndexPath: indexPath) as! UserCell + internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "idCellUser", for: indexPath as IndexPath) as! UserCell cell.textLabel?.text = users[indexPath.row]["nickname"] as? String cell.detailTextLabel?.text = (users[indexPath.row]["isConnected"] as! Bool) ? "Online" : "Offline" - cell.detailTextLabel?.textColor = (users[indexPath.row]["isConnected"] as! Bool) ? UIColor.greenColor() : UIColor.redColor() + cell.detailTextLabel?.textColor = (users[indexPath.row]["isConnected"] as! Bool) ? UIColor.green : UIColor.red return cell } - func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + private func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return 44.0 } diff --git a/Source/SSLSecurity.swift b/Source/SSLSecurity.swift new file mode 100755 index 0000000..00343fb --- /dev/null +++ b/Source/SSLSecurity.swift @@ -0,0 +1,258 @@ +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// SSLSecurity.swift +// Starscream +// +// Created by Dalton Cherry on 5/16/15. +// Copyright (c) 2014-2015 Dalton Cherry. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +////////////////////////////////////////////////////////////////////////////////////////////////// + +import Foundation +import Security + +public class SSLCert : NSObject { + var certData: Data? + var key: SecKey? + + /** + Designated init for certificates + + - parameter data: is the binary data of the certificate + + - returns: a representation security object to be used with + */ + public init(data: Data) { + self.certData = data + } + + /** + Designated init for public keys + + - parameter key: is the public key to be used + + - returns: a representation security object to be used with + */ + public init(key: SecKey) { + self.key = key + } +} + +public class SSLSecurity : NSObject { + public var validatedDN = true //should the domain name be validated? + + var isReady = false //is the key processing done? + var certificates: [Data]? //the certificates + @nonobjc var pubKeys: [SecKey]? //the public keys + var usePublicKeys = false //use public keys or certificate validation? + + /** + Use certs from main app bundle + + - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation + + - returns: a representation security object to be used with + */ + public convenience init(usePublicKeys: Bool = false) { + let paths = Bundle.main.paths(forResourcesOfType: "cer", inDirectory: ".") + + let certs = paths.reduce([SSLCert]()) { (certs: [SSLCert], path: String) -> [SSLCert] in + var certs = certs + if let data = NSData(contentsOfFile: path) { + certs.append(SSLCert(data: data as Data)) + } + return certs + } + + self.init(certs: certs, usePublicKeys: usePublicKeys) + } + + /** + Designated init + + - parameter keys: is the certificates or public keys to use + - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation + + - returns: a representation security object to be used with + */ + public init(certs: [SSLCert], usePublicKeys: Bool) { + self.usePublicKeys = usePublicKeys + + super.init() + + if self.usePublicKeys { + DispatchQueue.global(qos: .default).async { + let pubKeys = certs.reduce([SecKey]()) { (pubKeys: [SecKey], cert: SSLCert) -> [SecKey] in + var pubKeys = pubKeys + if let data = cert.certData, cert.key == nil { + cert.key = self.extractPublicKey(data) + } + if let key = cert.key { + pubKeys.append(key) + } + return pubKeys + } + + self.pubKeys = pubKeys + self.isReady = true + } + } else { + let certificates = certs.reduce([Data]()) { (certificates: [Data], cert: SSLCert) -> [Data] in + var certificates = certificates + if let data = cert.certData { + certificates.append(data) + } + return certificates + } + self.certificates = certificates + self.isReady = true + } + } + + /** + Valid the trust and domain name. + + - parameter trust: is the serverTrust to validate + - parameter domain: is the CN domain to validate + + - returns: if the key was successfully validated + */ + public func isValid(_ trust: SecTrust, domain: String?) -> Bool { + + var tries = 0 + while !self.isReady { + usleep(1000) + tries += 1 + if tries > 5 { + return false //doesn't appear it is going to ever be ready... + } + } + var policy: SecPolicy + if self.validatedDN { + policy = SecPolicyCreateSSL(true, domain as NSString?) + } else { + policy = SecPolicyCreateBasicX509() + } + SecTrustSetPolicies(trust,policy) + if self.usePublicKeys { + if let keys = self.pubKeys { + let serverPubKeys = publicKeyChain(trust) + for serverKey in serverPubKeys as [AnyObject] { + for key in keys as [AnyObject] { + if serverKey.isEqual(key) { + return true + } + } + } + } + } else if let certs = self.certificates { + let serverCerts = certificateChain(trust) + var collect = [SecCertificate]() + for cert in certs { + collect.append(SecCertificateCreateWithData(nil,cert as CFData)!) + } + SecTrustSetAnchorCertificates(trust,collect as NSArray) + var result: SecTrustResultType = .unspecified + SecTrustEvaluate(trust,&result) + if result == .unspecified || result == .proceed { + var trustedCount = 0 + for serverCert in serverCerts { + for cert in certs { + if cert == serverCert { + trustedCount += 1 + break + } + } + } + if trustedCount == serverCerts.count { + return true + } + } + } + return false + } + + /** + Get the public key from a certificate data + + - parameter data: is the certificate to pull the public key from + + - returns: a public key + */ + func extractPublicKey(_ data: Data) -> SecKey? { + guard let cert = SecCertificateCreateWithData(nil, data as CFData) else { return nil } + + return extractPublicKey(cert, policy: SecPolicyCreateBasicX509()) + } + + /** + Get the public key from a certificate + + - parameter data: is the certificate to pull the public key from + + - returns: a public key + */ + func extractPublicKey(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? { + var possibleTrust: SecTrust? + SecTrustCreateWithCertificates(cert, policy, &possibleTrust) + + guard let trust = possibleTrust else { return nil } + var result: SecTrustResultType = .unspecified + SecTrustEvaluate(trust, &result) + return SecTrustCopyPublicKey(trust) + } + + /** + Get the certificate chain for the trust + + - parameter trust: is the trust to lookup the certificate chain for + + - returns: the certificate chain for the trust + */ + func certificateChain(_ trust: SecTrust) -> [Data] { + let certificates = (0.. [Data] in + var certificates = certificates + let cert = SecTrustGetCertificateAtIndex(trust, index) + certificates.append(SecCertificateCopyData(cert!) as Data) + return certificates + } + + return certificates + } + + /** + Get the public key chain for the trust + + - parameter trust: is the trust to lookup the certificate chain and extract the public keys + + - returns: the public keys from the certifcate chain for the trust + */ + @nonobjc func publicKeyChain(_ trust: SecTrust) -> [SecKey] { + let policy = SecPolicyCreateBasicX509() + let keys = (0.. [SecKey] in + var keys = keys + let cert = SecTrustGetCertificateAtIndex(trust, index) + if let key = extractPublicKey(cert!, policy: policy) { + keys.append(key) + } + + return keys + } + + return keys + } + + +} diff --git a/Source/SocketAckEmitter.swift b/Source/SocketAckEmitter.swift index 678f084..511d497 100755 --- a/Source/SocketAckEmitter.swift +++ b/Source/SocketAckEmitter.swift @@ -24,7 +24,7 @@ import Foundation -public final class SocketAckEmitter: NSObject { +public final class SocketAckEmitter : NSObject { let socket: SocketIOClient let ackNum: Int @@ -33,15 +33,15 @@ public final class SocketAckEmitter: NSObject { self.ackNum = ackNum } - public func with(items: AnyObject...) { + public func with(_ items: SocketData...) { guard ackNum != -1 else { return } - socket.emitAck(ackNum, withItems: items) + socket.emitAck(ackNum, with: items) } - public func with(items: [AnyObject]) { + public func with(_ items: [Any]) { guard ackNum != -1 else { return } - socket.emitAck(ackNum, withItems: items) + socket.emitAck(ackNum, with: items) } } diff --git a/Source/SocketAckManager.swift b/Source/SocketAckManager.swift index e48d48c..ae6a6fa 100755 --- a/Source/SocketAckManager.swift +++ b/Source/SocketAckManager.swift @@ -24,7 +24,7 @@ import Foundation -private struct SocketAck: Hashable, Equatable { +private struct SocketAck : Hashable { let ack: Int var callback: AckCallback! var hashValue: Int { @@ -35,7 +35,7 @@ private struct SocketAck: Hashable, Equatable { self.ack = ack } - init(ack: Int, callback: AckCallback) { + init(ack: Int, callback: @escaping AckCallback) { self.ack = ack self.callback = callback } @@ -52,23 +52,21 @@ private func ==(lhs: SocketAck, rhs: SocketAck) -> Bool { struct SocketAckManager { private var acks = Set(minimumCapacity: 1) - mutating func addAck(ack: Int, callback: AckCallback) { + mutating func addAck(_ ack: Int, callback: @escaping AckCallback) { acks.insert(SocketAck(ack: ack, callback: callback)) } - mutating func executeAck(ack: Int, items: [AnyObject]) { - let callback = acks.remove(SocketAck(ack: ack)) - - dispatch_async(dispatch_get_main_queue()) { - callback?.callback(items) - } + /// Should be called on handle queue + mutating func executeAck(_ ack: Int, with items: [Any], onQueue: DispatchQueue) { + let ack = acks.remove(SocketAck(ack: ack)) + + onQueue.async() { ack?.callback(items) } } - mutating func timeoutAck(ack: Int) { - let callback = acks.remove(SocketAck(ack: ack)) + /// Should be called on handle queue + mutating func timeoutAck(_ ack: Int, onQueue: DispatchQueue) { + let ack = acks.remove(SocketAck(ack: ack)) - dispatch_async(dispatch_get_main_queue()) { - callback?.callback(["NO ACK"]) - } + onQueue.async() { ack?.callback?(["NO ACK"]) } } } diff --git a/Source/SocketAnyEvent.swift b/Source/SocketAnyEvent.swift index fdaa315..b3f2833 100755 --- a/Source/SocketAnyEvent.swift +++ b/Source/SocketAnyEvent.swift @@ -24,14 +24,14 @@ import Foundation -@objc public final class SocketAnyEvent: NSObject { - public let event: String! - public let items: NSArray? +public final class SocketAnyEvent : NSObject { + public let event: String + public let items: [Any]? override public var description: String { return "SocketAnyEvent: Event: \(event) items: \(items ?? nil)" } - init(event: String, items: NSArray?) { + init(event: String, items: [Any]?) { self.event = event self.items = items } diff --git a/Source/SocketClientManager.swift b/Source/SocketClientManager.swift new file mode 100755 index 0000000..e230272 --- /dev/null +++ b/Source/SocketClientManager.swift @@ -0,0 +1,82 @@ +// +// SocketClientManager.swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 6/11/16. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/** + Experimental socket manager. + + API subject to change. + + Can be used to persist sockets across ViewControllers. + + Sockets are strongly stored, so be sure to remove them once they are no + longer needed. + + Example usage: + ``` + let manager = SocketClientManager.sharedManager + manager["room1"] = socket1 + manager["room2"] = socket2 + manager.removeSocket(socket: socket2) + manager["room1"]?.emit("hello") + ``` + */ +open class SocketClientManager : NSObject { + open static let sharedManager = SocketClientManager() + + private var sockets = [String: SocketIOClient]() + + open subscript(string: String) -> SocketIOClient? { + get { + return sockets[string] + } + + set(socket) { + sockets[string] = socket + } + } + + open func addSocket(_ socket: SocketIOClient, labeledAs label: String) { + sockets[label] = socket + } + + open func removeSocket(withLabel label: String) -> SocketIOClient? { + return sockets.removeValue(forKey: label) + } + + open func removeSocket(_ socket: SocketIOClient) -> SocketIOClient? { + var returnSocket: SocketIOClient? + + for (label, dictSocket) in sockets where dictSocket === socket { + returnSocket = sockets.removeValue(forKey: label) + } + + return returnSocket + } + + open func removeSockets() { + sockets.removeAll() + } +} diff --git a/Source/SocketEngine.swift b/Source/SocketEngine.swift index 99f5b43..6caf87e 100755 --- a/Source/SocketEngine.swift +++ b/Source/SocketEngine.swift @@ -24,112 +24,109 @@ import Foundation -public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWebsocket { - public let emitQueue = dispatch_queue_create("com.socketio.engineEmitQueue", DISPATCH_QUEUE_SERIAL) - public let handleQueue = dispatch_queue_create("com.socketio.engineHandleQueue", DISPATCH_QUEUE_SERIAL) - public let parseQueue = dispatch_queue_create("com.socketio.engineParseQueue", DISPATCH_QUEUE_SERIAL) +public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, SocketEngineWebsocket { + public let emitQueue = DispatchQueue(label: "com.socketio.engineEmitQueue", attributes: []) + public let handleQueue = DispatchQueue(label: "com.socketio.engineHandleQueue", attributes: []) + public let parseQueue = DispatchQueue(label: "com.socketio.engineParseQueue", attributes: []) - public var connectParams: [String: AnyObject]? { + public var connectParams: [String: Any]? { didSet { (urlPolling, urlWebSocket) = createURLs() } } + public var postWait = [String]() public var waitingForPoll = false public var waitingForPost = false - + public private(set) var closed = false public private(set) var connected = false - public private(set) var cookies: [NSHTTPCookie]? + public private(set) var cookies: [HTTPCookie]? + public private(set) var doubleEncodeUTF8 = true public private(set) var extraHeaders: [String: String]? public private(set) var fastUpgrade = false public private(set) var forcePolling = false public private(set) var forceWebsockets = false public private(set) var invalidated = false - public private(set) var pingTimer: NSTimer? public private(set) var polling = true public private(set) var probing = false - public private(set) var session: NSURLSession? + public private(set) var session: URLSession? public private(set) var sid = "" public private(set) var socketPath = "/engine.io/" - public private(set) var urlPolling = NSURL() - public private(set) var urlWebSocket = NSURL() + public private(set) var urlPolling = URL(string: "http://localhost/")! + public private(set) var urlWebSocket = URL(string: "http://localhost/")! public private(set) var websocket = false public private(set) var ws: WebSocket? public weak var client: SocketEngineClient? - - private weak var sessionDelegate: NSURLSessionDelegate? - private typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData]) - private typealias ProbeWaitQueue = [Probe] + private weak var sessionDelegate: URLSessionDelegate? private let logType = "SocketEngine" - private let url: NSURL - + private let url: URL + private var pingInterval: Double? private var pingTimeout = 0.0 { didSet { pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25)) } } + private var pongsMissed = 0 private var pongsMissedMax = 0 private var probeWait = ProbeWaitQueue() private var secure = false + private var security: SSLSecurity? private var selfSigned = false private var voipEnabled = false - public init(client: SocketEngineClient, url: NSURL, options: Set) { + public init(client: SocketEngineClient, url: URL, config: SocketIOClientConfiguration) { self.client = client self.url = url - - for option in options { + for option in config { switch option { - case let .ConnectParams(params): + case let .connectParams(params): connectParams = params - case let .SessionDelegate(delegate): + case let .cookies(cookies): + self.cookies = cookies + case let .doubleEncodeUTF8(encode): + doubleEncodeUTF8 = encode + case let .extraHeaders(headers): + extraHeaders = headers + case let .sessionDelegate(delegate): sessionDelegate = delegate - case let .ForcePolling(force): + case let .forcePolling(force): forcePolling = force - case let .ForceWebsockets(force): + case let .forceWebsockets(force): forceWebsockets = force - case let .Cookies(cookies): - self.cookies = cookies - case let .Path(path): + case let .path(path): socketPath = path - case let .ExtraHeaders(headers): - extraHeaders = headers - case let .VoipEnabled(enable): + + if !socketPath.hasSuffix("/") { + socketPath += "/" + } + case let .voipEnabled(enable): voipEnabled = enable - case let .Secure(secure): + case let .secure(secure): self.secure = secure - case let .SelfSigned(selfSigned): + case let .selfSigned(selfSigned): self.selfSigned = selfSigned + case let .security(security): + self.security = security default: continue } } - + super.init() + sessionDelegate = sessionDelegate ?? self + (urlPolling, urlWebSocket) = createURLs() } - public convenience init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) { - self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? []) - } - - @available(*, deprecated=5.3) - public convenience init(client: SocketEngineClient, urlString: String, options: Set) { - guard let url = NSURL(string: urlString) else { fatalError("Incorrect url") } - self.init(client: client, url: url, options: options) - } - - @available(*, deprecated=5.3) - public convenience init(client: SocketEngineClient, urlString: String, options: NSDictionary?) { - guard let url = NSURL(string: urlString) else { fatalError("Incorrect url") } - self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? []) + public convenience init(client: SocketEngineClient, url: URL, options: NSDictionary?) { + self.init(client: client, url: url, config: options?.toSocketConfiguration() ?? []) } deinit { @@ -137,90 +134,90 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb closed = true stopPolling() } - - private func checkAndHandleEngineError(msg: String) { - guard let stringData = msg.dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false) else { return } - + + private func checkAndHandleEngineError(_ msg: String) { do { - if let dict = try NSJSONSerialization.JSONObjectWithData(stringData, - options: NSJSONReadingOptions.MutableContainers) as? NSDictionary { - guard let code = dict["code"] as? Int else { return } - guard let error = dict["message"] as? String else { return } - - switch code { - case 0: // Unknown transport - didError(error) - case 1: // Unknown sid. - didError(error) - case 2: // Bad handshake request - didError(error) - case 3: // Bad request - didError(error) - default: - didError(error) - } - } + let dict = try msg.toNSDictionary() + guard let error = dict["message"] as? String else { return } + + /* + 0: Unknown transport + 1: Unknown sid + 2: Bad handshake request + 3: Bad request + */ + didError(reason: error) } catch { - didError("Got unknown error from server \(msg)") + client?.engineDidError(reason: "Got unknown error from server \(msg)") } } - private func checkIfMessageIsBase64Binary(message: String) -> Bool { - if message.hasPrefix("b4") { - // binary in base64 string - let noPrefix = message[message.startIndex.advancedBy(2).. (NSURL, NSURL) { + private func createURLs() -> (URL, URL) { if client == nil { - return (NSURL(), NSURL()) + return (URL(string: "http://localhost/")!, URL(string: "http://localhost/")!) } - let urlPolling = NSURLComponents(string: url.absoluteString)! - let urlWebSocket = NSURLComponents(string: url.absoluteString)! + var urlPolling = URLComponents(string: url.absoluteString)! + var urlWebSocket = URLComponents(string: url.absoluteString)! var queryString = "" urlWebSocket.path = socketPath urlPolling.path = socketPath - urlWebSocket.query = "transport=websocket" - urlPolling.query = "transport=polling&b64=1" if secure { urlPolling.scheme = "https" @@ -232,24 +229,24 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb if connectParams != nil { for (key, value) in connectParams! { - queryString += "&\(key)=\(value)" + let keyEsc = key.urlEncode()! + let valueEsc = "\(value)".urlEncode()! + + queryString += "&\(keyEsc)=\(valueEsc)" } } - urlWebSocket.query = urlWebSocket.query! + queryString - urlPolling.query = urlPolling.query! + queryString - - return (urlPolling.URL!, urlWebSocket.URL!) + urlWebSocket.percentEncodedQuery = "transport=websocket" + queryString + urlPolling.percentEncodedQuery = "transport=polling&b64=1" + queryString + + return (urlPolling.url!, urlWebSocket.url!) } private func createWebsocketAndConnect() { - let component = NSURLComponents(URL: urlWebSocket, resolvingAgainstBaseURL: false)! - component.query = component.query! + (sid == "" ? "" : "&sid=\(sid)") - - ws = WebSocket(url: component.URL!) - + ws = WebSocket(url: urlWebSocketWithSid as URL) + if cookies != nil { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) + let headers = HTTPCookie.requestHeaderFields(with: cookies!) for (key, value) in headers { ws?.headers[key] = value } @@ -261,18 +258,47 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb } } - ws?.queue = handleQueue + ws?.callbackQueue = handleQueue ws?.voipEnabled = voipEnabled ws?.delegate = self - ws?.selfSignedSSL = selfSigned + ws?.disableSSLCertValidation = selfSigned + ws?.security = security ws?.connect() } - - public func didError(error: String) { - DefaultSocketLogger.Logger.error(error, type: logType) - client?.engineDidError(error) - close(error) + + public func didError(reason: String) { + DefaultSocketLogger.Logger.error("%@", type: logType, args: reason) + client?.engineDidError(reason: reason) + disconnect(reason: reason) + } + + public func disconnect(reason: String) { + guard connected else { return closeOutEngine(reason: reason) } + + DefaultSocketLogger.Logger.log("Engine is being closed.", type: logType) + + if closed { + return closeOutEngine(reason: reason) + } + + if websocket { + sendWebSocketMessage("", withType: .close, withData: []) + closeOutEngine(reason: reason) + } else { + disconnectPolling(reason: reason) + } + } + + // We need to take special care when we're polling that we send it ASAP + // Also make sure we're on the emitQueue since we're touching postWait + private func disconnectPolling(reason: String) { + emitQueue.sync { + self.postWait.append(String(SocketEnginePacketType.close.rawValue)) + let req = self.createRequestForPostWithPostWait() + self.doRequest(for: req) {_, _, _ in } + self.closeOutEngine(reason: reason) + } } public func doFastUpgrade() { @@ -281,7 +307,7 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb "we'll probably disconnect soon. You should report this.", type: logType) } - sendWebSocketMessage("", withType: .Upgrade, withData: []) + sendWebSocketMessage("", withType: .upgrade, withData: []) websocket = true polling = false fastUpgrade = false @@ -292,169 +318,139 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb private func flushProbeWait() { DefaultSocketLogger.Logger.log("Flushing probe wait", type: logType) - dispatch_async(emitQueue) { + emitQueue.async { for waiter in self.probeWait { self.write(waiter.msg, withType: waiter.type, withData: waiter.data) } - - self.probeWait.removeAll(keepCapacity: false) - + + self.probeWait.removeAll(keepingCapacity: false) + if self.postWait.count != 0 { self.flushWaitingForPostToWebSocket() } } } - + // We had packets waiting for send when we upgraded // Send them raw public func flushWaitingForPostToWebSocket() { guard let ws = self.ws else { return } - + for msg in postWait { - ws.writeString(fixDoubleUTF8(msg)) + ws.write(string: msg) } - - postWait.removeAll(keepCapacity: true) + + postWait.removeAll(keepingCapacity: false) } - private func handleClose(reason: String) { - client?.engineDidClose(reason) + private func handleClose(_ reason: String) { + client?.engineDidClose(reason: reason) } - private func handleMessage(message: String) { + private func handleMessage(_ message: String) { client?.parseEngineMessage(message) } private func handleNOOP() { doPoll() } - + private func handleOpen(openData: String) { - let mesData = openData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! - do { - let json = try NSJSONSerialization.JSONObjectWithData(mesData, - options: NSJSONReadingOptions.AllowFragments) as? NSDictionary - if let sid = json?["sid"] as? String { - let upgradeWs: Bool - - self.sid = sid - connected = true - - if let upgrades = json?["upgrades"] as? [String] { - upgradeWs = upgrades.contains("websocket") - } else { - upgradeWs = false - } - - if let pingInterval = json?["pingInterval"] as? Double, pingTimeout = json?["pingTimeout"] as? Double { - self.pingInterval = pingInterval / 1000.0 - self.pingTimeout = pingTimeout / 1000.0 - } - - if !forcePolling && !forceWebsockets && upgradeWs { - createWebsocketAndConnect() - } - - - startPingTimer() - - if !forceWebsockets { - doPoll() - } - - client?.engineDidOpen?("Connect") - } - } catch { - didError("Error parsing open packet") + guard let json = try? openData.toNSDictionary() else { + didError(reason: "Error parsing open packet") + return } - } - - private func handlePong(pongMessage: String) { - pongsMissed = 0 - - // We should upgrade - if pongMessage == "3probe" { - upgradeTransport() - } - } - - public func open() { - if connected { - DefaultSocketLogger.Logger.error("Engine tried opening while connected. This is probably a programming error. " - + "Abandoning open attempt", type: logType) + + guard let sid = json["sid"] as? String else { + didError(reason: "Open packet contained no sid") + return } - DefaultSocketLogger.Logger.log("Starting engine", type: logType) - DefaultSocketLogger.Logger.log("Handshaking", type: logType) - - resetEngine() - - if forceWebsockets { - polling = false - websocket = true + let upgradeWs: Bool + + self.sid = sid + connected = true + + if let upgrades = json["upgrades"] as? [String] { + upgradeWs = upgrades.contains("websocket") + } else { + upgradeWs = false + } + + if let pingInterval = json["pingInterval"] as? Double, let pingTimeout = json["pingTimeout"] as? Double { + self.pingInterval = pingInterval / 1000.0 + self.pingTimeout = pingTimeout / 1000.0 + } + + if !forcePolling && !forceWebsockets && upgradeWs { createWebsocketAndConnect() - return } - - let reqPolling = NSMutableURLRequest(URL: urlPolling) - - if cookies != nil { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) - reqPolling.allHTTPHeaderFields = headers + + sendPing() + + if !forceWebsockets { + doPoll() } + + client?.engineDidOpen(reason: "Connect") + } - if let extraHeaders = extraHeaders { - for (headerName, value) in extraHeaders { - reqPolling.setValue(value, forHTTPHeaderField: headerName) - } - } + private func handlePong(with message: String) { + pongsMissed = 0 - doLongPoll(reqPolling) + // We should upgrade + if message == "3probe" { + upgradeTransport() + } } - public func parseEngineData(data: NSData) { + public func parseEngineData(_ data: Data) { DefaultSocketLogger.Logger.log("Got binary data: %@", type: "SocketEngine", args: data) - client?.parseEngineBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) + + client?.parseEngineBinaryData(data.subdata(in: 1.. pongsMissedMax { - pingTimer?.invalidate() - client?.engineDidClose("Ping timeout") + client?.engineDidClose(reason: "Ping timeout") + return } + guard let pingInterval = pingInterval else { return } + pongsMissed += 1 - write("", withType: .Ping, withData: []) - } - - // Starts the ping timer - private func startPingTimer() { - if let pingInterval = pingInterval { - pingTimer?.invalidate() - pingTimer = nil - - dispatch_async(dispatch_get_main_queue()) { - self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(pingInterval, target: self, - selector: Selector("sendPing"), userInfo: nil, repeats: true) - } - } + write("", withType: .ping, withData: []) + + let time = DispatchTime.now() + Double(Int64(pingInterval * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + DispatchQueue.main.asyncAfter(deadline: time) {[weak self] in self?.sendPing() } } + // Moves from long-polling to websockets private func upgradeTransport() { if ws?.isConnected ?? false { DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: logType) fastUpgrade = true - sendPollMessage("", withType: .Noop, withData: []) + sendPollMessage("", withType: .noop, withData: []) // After this point, we should not send anymore polling messages } } - /** - Write a message, independent of transport. - */ - public func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) { - dispatch_async(emitQueue) { + /// Write a message, independent of transport. + public func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) { + emitQueue.async { guard self.connected else { return } - + if self.websocket { DefaultSocketLogger.Logger.log("Writing ws: %@ has data: %@", type: self.logType, args: msg, data.count != 0) @@ -526,7 +513,7 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb } } } - + // Delegate methods public func websocketDidConnect(socket: WebSocket) { if !forceWebsockets { @@ -538,29 +525,35 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb polling = false } } - + public func websocketDidDisconnect(socket: WebSocket, error: NSError?) { probing = false - + if closed { - client?.engineDidClose("Disconnect") + client?.engineDidClose(reason: "Disconnect") + return } - + if websocket { - pingTimer?.invalidate() connected = false websocket = false - - let reason = error?.localizedDescription ?? "Socket Disconnected" - - if error != nil { - didError(reason) + + if let reason = error?.localizedDescription { + didError(reason: reason) + } else { + client?.engineDidClose(reason: "Socket Disconnected") } - - client?.engineDidClose(reason) } else { flushProbeWait() } } } + +extension SocketEngine { + public func URLSession(session: URLSession, didBecomeInvalidWithError error: NSError?) { + DefaultSocketLogger.Logger.error("Engine URLSession became invalid", type: "SocketEngine") + + didError(reason: "Engine URLSession became invalid") + } +} diff --git a/Source/SocketEngineClient.swift b/Source/SocketEngineClient.swift index a1db7f6..49cac7c 100755 --- a/Source/SocketEngineClient.swift +++ b/Source/SocketEngineClient.swift @@ -28,7 +28,7 @@ import Foundation @objc public protocol SocketEngineClient { func engineDidError(reason: String) func engineDidClose(reason: String) - optional func engineDidOpen(reason: String) - func parseEngineMessage(msg: String) - func parseEngineBinaryData(data: NSData) + func engineDidOpen(reason: String) + func parseEngineMessage(_ msg: String) + func parseEngineBinaryData(_ data: Data) } diff --git a/Source/SocketEnginePacketType.swift b/Source/SocketEnginePacketType.swift index f899721..763335e 100755 --- a/Source/SocketEnginePacketType.swift +++ b/Source/SocketEnginePacketType.swift @@ -25,6 +25,6 @@ import Foundation -@objc public enum SocketEnginePacketType: Int { - case Open, Close, Ping, Pong, Message, Upgrade, Noop +@objc public enum SocketEnginePacketType : Int { + case open, close, ping, pong, message, upgrade, noop } \ No newline at end of file diff --git a/Source/SocketEnginePollable.swift b/Source/SocketEnginePollable.swift index b871e2d..b050975 100755 --- a/Source/SocketEnginePollable.swift +++ b/Source/SocketEnginePollable.swift @@ -25,12 +25,12 @@ import Foundation /// Protocol that is used to implement socket.io polling support -public protocol SocketEnginePollable: SocketEngineSpec { +public protocol SocketEnginePollable : SocketEngineSpec { var invalidated: Bool { get } /// Holds strings waiting to be sent over polling. /// You shouldn't need to mess with this. var postWait: [String] { get set } - var session: NSURLSession? { get } + var session: URLSession? { get } /// Because socket.io doesn't let you send two polling request at the same time /// we have to keep track if there's an outstanding poll var waitingForPoll: Bool { get set } @@ -39,15 +39,17 @@ public protocol SocketEnginePollable: SocketEngineSpec { var waitingForPost: Bool { get set } func doPoll() - func sendPollMessage(message: String, withType type: SocketEnginePacketType, withData datas: [NSData]) + func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) func stopPolling() } // Default polling methods extension SocketEnginePollable { - private func addHeaders(req: NSMutableURLRequest) { + private func addHeaders(for req: URLRequest) -> URLRequest { + var req = req + if cookies != nil { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) + let headers = HTTPCookie.requestHeaderFields(with: cookies!) req.allHTTPHeaderFields = headers } @@ -56,9 +58,13 @@ extension SocketEnginePollable { req.setValue(value, forHTTPHeaderField: headerName) } } + + return req } - func createRequestForPostWithPostWait() -> NSURLRequest { + func createRequestForPostWithPostWait() -> URLRequest { + defer { postWait.removeAll(keepingCapacity: true) } + var postStr = "" for packet in postWait { @@ -69,22 +75,18 @@ extension SocketEnginePollable { DefaultSocketLogger.Logger.log("Created POST string: %@", type: "SocketEnginePolling", args: postStr) - postWait.removeAll(keepCapacity: false) - - let req = NSMutableURLRequest(URL: urlPollingWithSid) + var req = URLRequest(url: urlPollingWithSid) + let postData = postStr.data(using: .utf8, allowLossyConversion: false)! - addHeaders(req) + req = addHeaders(for: req) - req.HTTPMethod = "POST" + req.httpMethod = "POST" req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") + + req.httpBody = postData + req.setValue(String(postData.count), forHTTPHeaderField: "Content-Length") - let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)! - - req.HTTPBody = postData - req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") - - return req + return req as URLRequest } public func doPoll() { @@ -93,32 +95,32 @@ extension SocketEnginePollable { } waitingForPoll = true - let req = NSMutableURLRequest(URL: urlPollingWithSid) - addHeaders(req) - doLongPoll(req) + var req = URLRequest(url: urlPollingWithSid) + + req = addHeaders(for: req) + doLongPoll(for: req ) } - func doRequest(req: NSURLRequest, withCallback callback: (NSData?, NSURLResponse?, NSError?) -> Void) { - if !polling || closed || invalidated { - DefaultSocketLogger.Logger.error("Tried to do polling request when not supposed to", type: "SocketEnginePolling") - return - } - - DefaultSocketLogger.Logger.log("Doing polling request", type: "SocketEnginePolling") - - session?.dataTaskWithRequest(req, completionHandler: callback).resume() + func doRequest(for req: URLRequest, callbackWith callback: @escaping (Data?, URLResponse?, Error?) -> Void) { + if !polling || closed || invalidated || fastUpgrade { + return + } + + DefaultSocketLogger.Logger.log("Doing polling request", type: "SocketEnginePolling") + + session?.dataTask(with: req, completionHandler: callback).resume() } - func doLongPoll(req: NSURLRequest) { - doRequest(req) {[weak self] data, res, err in - guard let this = self else { return } + func doLongPoll(for req: URLRequest) { + doRequest(for: req) {[weak self] data, res, err in + guard let this = self, this.polling else { return } if err != nil || data == nil { DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") if this.polling { - this.didError(err?.localizedDescription ?? "Error") + this.didError(reason: err?.localizedDescription ?? "Error") } return @@ -126,8 +128,8 @@ extension SocketEnginePollable { DefaultSocketLogger.Logger.log("Got polling response", type: "SocketEnginePolling") - if let str = String(data: data!, encoding: NSUTF8StringEncoding) { - dispatch_async(this.parseQueue) { + if let str = String(data: data!, encoding: String.Encoding.utf8) { + this.parseQueue.async { this.parsePollingMessage(str) } } @@ -156,14 +158,14 @@ extension SocketEnginePollable { DefaultSocketLogger.Logger.log("POSTing", type: "SocketEnginePolling") - doRequest(req) {[weak self] data, res, err in + doRequest(for: req) {[weak self] data, res, err in guard let this = self else { return } if err != nil { DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") if this.polling { - this.didError(err?.localizedDescription ?? "Error") + this.didError(reason: err?.localizedDescription ?? "Error") } return @@ -171,7 +173,7 @@ extension SocketEnginePollable { this.waitingForPost = false - dispatch_async(this.emitQueue) { + this.emitQueue.async { if !this.fastUpgrade { this.flushWaitingForPost() this.doPoll() @@ -180,24 +182,18 @@ extension SocketEnginePollable { } } - func parsePollingMessage(str: String) { - guard str.characters.count != 1 else { - return - } + func parsePollingMessage(_ str: String) { + guard str.characters.count != 1 else { return } var reader = SocketStringReader(message: str) while reader.hasNext { - if let n = Int(reader.readUntilStringOccurence(":")) { - let str = reader.read(n) + if let n = Int(reader.readUntilOccurence(of: ":")) { + let str = reader.read(count: n) - dispatch_async(handleQueue) { - self.parseEngineMessage(str, fromPolling: true) - } + handleQueue.async { self.parseEngineMessage(str, fromPolling: true) } } else { - dispatch_async(handleQueue) { - self.parseEngineMessage(str, fromPolling: true) - } + handleQueue.async { self.parseEngineMessage(str, fromPolling: true) } break } } @@ -205,22 +201,27 @@ extension SocketEnginePollable { /// Send polling message. /// Only call on emitQueue - public func sendPollMessage(message: String, withType type: SocketEnginePacketType, withData datas: [NSData]) { - DefaultSocketLogger.Logger.log("Sending poll: %@ as type: %@", type: "SocketEnginePolling", args: message, type.rawValue) - let fixedMessage = doubleEncodeUTF8(message) - let strMsg = "\(type.rawValue)\(fixedMessage)" - - postWait.append(strMsg) - - for data in datas { - if case let .Right(bin) = createBinaryDataForSend(data) { - postWait.append(bin) - } - } - - if !waitingForPost { - flushWaitingForPost() + public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) { + DefaultSocketLogger.Logger.log("Sending poll: %@ as type: %@", type: "SocketEnginePolling", args: message, type.rawValue) + let fixedMessage: String + + if doubleEncodeUTF8 { + fixedMessage = doubleEncodeUTF8(message) + } else { + fixedMessage = message + } + + postWait.append(String(type.rawValue) + fixedMessage) + + for data in datas { + if case let .right(bin) = createBinaryDataForSend(using: data) { + postWait.append(bin) } + } + + if !waitingForPost { + flushWaitingForPost() + } } public func stopPolling() { diff --git a/Source/SocketEngineSpec.swift b/Source/SocketEngineSpec.swift index 1703d98..f862889 100755 --- a/Source/SocketEngineSpec.swift +++ b/Source/SocketEngineSpec.swift @@ -29,61 +29,87 @@ import Foundation weak var client: SocketEngineClient? { get set } var closed: Bool { get } var connected: Bool { get } - var connectParams: [String: AnyObject]? { get set } - var cookies: [NSHTTPCookie]? { get } + var connectParams: [String: Any]? { get set } + var doubleEncodeUTF8: Bool { get } + var cookies: [HTTPCookie]? { get } var extraHeaders: [String: String]? { get } var fastUpgrade: Bool { get } var forcePolling: Bool { get } var forceWebsockets: Bool { get } - var parseQueue: dispatch_queue_t! { get } - var pingTimer: NSTimer? { get } + var parseQueue: DispatchQueue { get } var polling: Bool { get } var probing: Bool { get } - var emitQueue: dispatch_queue_t! { get } - var handleQueue: dispatch_queue_t! { get } + var emitQueue: DispatchQueue { get } + var handleQueue: DispatchQueue { get } var sid: String { get } var socketPath: String { get } - var urlPolling: NSURL { get } - var urlWebSocket: NSURL { get } + var urlPolling: URL { get } + var urlWebSocket: URL { get } var websocket: Bool { get } + var ws: WebSocket? { get } - init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) + init(client: SocketEngineClient, url: URL, options: NSDictionary?) - func close(reason: String) - func didError(error: String) + func connect() + func didError(reason: String) + func disconnect(reason: String) func doFastUpgrade() func flushWaitingForPostToWebSocket() - func open() - func parseEngineData(data: NSData) - func parseEngineMessage(message: String, fromPolling: Bool) - func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) + func parseEngineData(_ data: Data) + func parseEngineMessage(_ message: String, fromPolling: Bool) + func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) } extension SocketEngineSpec { - var urlPollingWithSid: NSURL { - let com = NSURLComponents(URL: urlPolling, resolvingAgainstBaseURL: false)! - com.query = com.query! + "&sid=\(sid)" + var urlPollingWithSid: URL { + var com = URLComponents(url: urlPolling, resolvingAgainstBaseURL: false)! + com.percentEncodedQuery = com.percentEncodedQuery! + "&sid=\(sid.urlEncode()!)" - return com.URL! + return com.url! } - func createBinaryDataForSend(data: NSData) -> Either { + var urlWebSocketWithSid: URL { + var com = URLComponents(url: urlWebSocket, resolvingAgainstBaseURL: false)! + com.percentEncodedQuery = com.percentEncodedQuery! + (sid == "" ? "" : "&sid=\(sid.urlEncode()!)") + + return com.url! + } + + func createBinaryDataForSend(using data: Data) -> Either { if websocket { - var byteArray = [UInt8](count: 1, repeatedValue: 0x4) + var byteArray = [UInt8](repeating: 0x4, count: 1) let mutData = NSMutableData(bytes: &byteArray, length: 1) - mutData.appendData(data) + mutData.append(data) - return .Left(mutData) + return .left(mutData as Data) } else { - let str = "b4" + data.base64EncodedStringWithOptions(.Encoding64CharacterLineLength) + let str = "b4" + data.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)) - return .Right(str) + return .right(str) + } + } + + func doubleEncodeUTF8(_ string: String) -> String { + if let latin1 = string.data(using: String.Encoding.utf8), + let utf8 = NSString(data: latin1, encoding: String.Encoding.isoLatin1.rawValue) { + return utf8 as String + } else { + return string + } + } + + func fixDoubleUTF8(_ string: String) -> String { + if let utf8 = string.data(using: String.Encoding.isoLatin1), + let latin1 = NSString(data: utf8, encoding: String.Encoding.utf8.rawValue) { + return latin1 as String + } else { + return string } } /// Send an engine message (4) - func send(msg: String, withData datas: [NSData]) { - write(msg, withType: .Message, withData: datas) + func send(_ msg: String, withData datas: [Data]) { + write(msg, withType: .message, withData: datas) } } diff --git a/Source/SocketEngineWebsocket.swift b/Source/SocketEngineWebsocket.swift index 4b1be7f..3c37b2b 100755 --- a/Source/SocketEngineWebsocket.swift +++ b/Source/SocketEngineWebsocket.swift @@ -26,39 +26,37 @@ import Foundation /// Protocol that is used to implement socket.io WebSocket support -public protocol SocketEngineWebsocket: SocketEngineSpec, WebSocketDelegate { - var ws: WebSocket? { get } - - func sendWebSocketMessage(str: String, withType type: SocketEnginePacketType, withData datas: [NSData]) +public protocol SocketEngineWebsocket : SocketEngineSpec, WebSocketDelegate { + func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) } // WebSocket methods extension SocketEngineWebsocket { func probeWebSocket() { if ws?.isConnected ?? false { - sendWebSocketMessage("probe", withType: .Ping, withData: []) + sendWebSocketMessage("probe", withType: .ping, withData: []) } } /// Send message on WebSockets /// Only call on emitQueue - public func sendWebSocketMessage(str: String, withType type: SocketEnginePacketType, withData datas: [NSData]) { - DefaultSocketLogger.Logger.log("Sending ws: %@ as type: %@", type: "SocketEngine", args: str, type.rawValue) - - ws?.writeString("\(type.rawValue)\(str)") - - for data in datas { - if case let .Left(bin) = createBinaryDataForSend(data) { - ws?.writeData(bin) - } + public func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) { + DefaultSocketLogger.Logger.log("Sending ws: %@ as type: %@", type: "SocketEngine", args: str, type.rawValue) + + ws?.write(string: "\(type.rawValue)\(str)") + + for data in datas { + if case let .left(bin) = createBinaryDataForSend(using: data) { + ws?.write(data: bin) } + } } public func websocketDidReceiveMessage(socket: WebSocket, text: String) { parseEngineMessage(text, fromPolling: false) } - public func websocketDidReceiveData(socket: WebSocket, data: NSData) { + public func websocketDidReceiveData(socket: WebSocket, data: Data) { parseEngineData(data) } } diff --git a/Source/SocketEventHandler.swift b/Source/SocketEventHandler.swift index 41774a9..5497f7f 100755 --- a/Source/SocketEventHandler.swift +++ b/Source/SocketEventHandler.swift @@ -26,10 +26,10 @@ import Foundation struct SocketEventHandler { let event: String - let id: NSUUID + let id: UUID let callback: NormalCallback - func executeCallback(items: [AnyObject], withAck ack: Int, withSocket socket: SocketIOClient) { + func executeCallback(with items: [Any], withAck ack: Int, withSocket socket: SocketIOClient) { callback(items, SocketAckEmitter(socket: socket, ackNum: ack)) } } diff --git a/Source/SocketExtensions.swift b/Source/SocketExtensions.swift new file mode 100755 index 0000000..bf5280a --- /dev/null +++ b/Source/SocketExtensions.swift @@ -0,0 +1,127 @@ +// +// SocketExtensions.swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 7/1/2016. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +enum JSONError : Error { + case notArray + case notNSDictionary +} + +extension Array { + func toJSON() throws -> Data { + return try JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions(rawValue: 0)) + } +} + +extension CharacterSet { + static var allowedURLCharacterSet: CharacterSet { + return CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[]\" {}").inverted + } +} + +extension NSDictionary { + private static func keyValueToSocketIOClientOption(key: String, value: Any) -> SocketIOClientOption? { + switch (key, value) { + case let ("connectParams", params as [String: Any]): + return .connectParams(params) + case let ("cookies", cookies as [HTTPCookie]): + return .cookies(cookies) + case let ("doubleEncodeUTF8", encode as Bool): + return .doubleEncodeUTF8(encode) + case let ("extraHeaders", headers as [String: String]): + return .extraHeaders(headers) + case let ("forceNew", force as Bool): + return .forceNew(force) + case let ("forcePolling", force as Bool): + return .forcePolling(force) + case let ("forceWebsockets", force as Bool): + return .forceWebsockets(force) + case let ("handleQueue", queue as DispatchQueue): + return .handleQueue(queue) + case let ("log", log as Bool): + return .log(log) + case let ("logger", logger as SocketLogger): + return .logger(logger) + case let ("nsp", nsp as String): + return .nsp(nsp) + case let ("path", path as String): + return .path(path) + case let ("reconnects", reconnects as Bool): + return .reconnects(reconnects) + case let ("reconnectAttempts", attempts as Int): + return .reconnectAttempts(attempts) + case let ("reconnectWait", wait as Int): + return .reconnectWait(wait) + case let ("secure", secure as Bool): + return .secure(secure) + case let ("security", security as SSLSecurity): + return .security(security) + case let ("selfSigned", selfSigned as Bool): + return .selfSigned(selfSigned) + case let ("sessionDelegate", delegate as URLSessionDelegate): + return .sessionDelegate(delegate) + case let ("voipEnabled", enable as Bool): + return .voipEnabled(enable) + default: + return nil + } + } + + func toSocketConfiguration() -> SocketIOClientConfiguration { + var options = [] as SocketIOClientConfiguration + + for (rawKey, value) in self { + if let key = rawKey as? String, let opt = NSDictionary.keyValueToSocketIOClientOption(key: key, value: value) { + options.insert(opt) + } + } + + return options + } +} + +extension String { + func toArray() throws -> [Any] { + guard let stringData = data(using: .utf8, allowLossyConversion: false) else { return [] } + guard let array = try JSONSerialization.jsonObject(with: stringData, options: .mutableContainers) as? [Any] else { + throw JSONError.notArray + } + + return array + } + + func toNSDictionary() throws -> NSDictionary { + guard let binData = data(using: .utf8, allowLossyConversion: false) else { return [:] } + guard let json = try JSONSerialization.jsonObject(with: binData, options: .allowFragments) as? NSDictionary else { + throw JSONError.notNSDictionary + } + + return json + } + + func urlEncode() -> String? { + return addingPercentEncoding(withAllowedCharacters: .allowedURLCharacterSet) + } +} diff --git a/Source/SocketFixUTF8.swift b/Source/SocketFixUTF8.swift deleted file mode 100755 index 5986003..0000000 --- a/Source/SocketFixUTF8.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// SocketFixUTF8.swift -// Socket.IO-Client-Swift -// -// Created by Erik Little on 3/16/15. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import Foundation - -func fixDoubleUTF8(string: String) -> String { - if let utf8 = string.dataUsingEncoding(NSISOLatin1StringEncoding), - latin1 = NSString(data: utf8, encoding: NSUTF8StringEncoding) { - return latin1 as String - } else { - return string - } -} - -func doubleEncodeUTF8(string: String) -> String { - if let latin1 = string.dataUsingEncoding(NSUTF8StringEncoding), - utf8 = NSString(data: latin1, encoding: NSISOLatin1StringEncoding) { - return utf8 as String - } else { - return string - } -} diff --git a/Source/SocketIOClient.swift b/Source/SocketIOClient.swift index c349202..0a51bd0 100755 --- a/Source/SocketIOClient.swift +++ b/Source/SocketIOClient.swift @@ -24,179 +24,160 @@ import Foundation -public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable { - public let socketURL: NSURL +public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable { + public let socketURL: URL public private(set) var engine: SocketEngineSpec? - public private(set) var status = SocketIOClientStatus.NotConnected + public private(set) var status = SocketIOClientStatus.notConnected { + didSet { + switch status { + case .connected: + reconnecting = false + currentReconnectAttempt = 0 + default: + break + } + } + } public var forceNew = false public var nsp = "/" - public var options: Set + public var config: SocketIOClientConfiguration public var reconnects = true public var reconnectWait = 10 - public var sid: String? { - return engine?.sid - } - private let emitQueue = dispatch_queue_create("com.socketio.emitQueue", DISPATCH_QUEUE_SERIAL) + private let ackQueue = DispatchQueue(label: "com.socketio.ackQueue", attributes: []) + private let emitQueue = DispatchQueue(label: "com.socketio.emitQueue", attributes: []) private let logType = "SocketIOClient" - private let parseQueue = dispatch_queue_create("com.socketio.parseQueue", DISPATCH_QUEUE_SERIAL) + private let parseQueue = DispatchQueue(label: "com.socketio.parseQueue", attributes: []) private var anyHandler: ((SocketAnyEvent) -> Void)? private var currentReconnectAttempt = 0 private var handlers = [SocketEventHandler]() - private var reconnectTimer: NSTimer? private var ackHandlers = SocketAckManager() + private var reconnecting = false private(set) var currentAck = -1 - private(set) var handleQueue = dispatch_get_main_queue() + private(set) var handleQueue = DispatchQueue.main private(set) var reconnectAttempts = -1 - var waitingData = [SocketPacket]() + var waitingPackets = [SocketPacket]() - /** - Type safe way to create a new SocketIOClient. opts can be omitted - */ - public init(socketURL: NSURL, options: Set = []) { - self.options = options + public var sid: String? { + return nsp + "#" + (engine?.sid ?? "") + } + + /// Type safe way to create a new SocketIOClient. opts can be omitted + public init(socketURL: URL, config: SocketIOClientConfiguration = []) { + self.config = config self.socketURL = socketURL if socketURL.absoluteString.hasPrefix("https://") { - self.options.insertIgnore(.Secure(true)) + self.config.insert(.secure(true)) } - for option in options { + for option in config { switch option { - case let .Reconnects(reconnects): + case let .reconnects(reconnects): self.reconnects = reconnects - case let .ReconnectAttempts(attempts): + case let .reconnectAttempts(attempts): reconnectAttempts = attempts - case let .ReconnectWait(wait): + case let .reconnectWait(wait): reconnectWait = abs(wait) - case let .Nsp(nsp): + case let .nsp(nsp): self.nsp = nsp - case let .Log(log): + case let .log(log): DefaultSocketLogger.Logger.log = log - case let .Logger(logger): + case let .logger(logger): DefaultSocketLogger.Logger = logger - case let .HandleQueue(queue): + case let .handleQueue(queue): handleQueue = queue - case let .ForceNew(force): + case let .forceNew(force): forceNew = force default: continue } } - - self.options.insertIgnore(.Path("/socket.io/")) + + self.config.insert(.path("/socket.io/"), replacing: false) super.init() } - /** - Not so type safe way to create a SocketIOClient, meant for Objective-C compatiblity. - If using Swift it's recommended to use `init(socketURL: NSURL, options: Set)` - */ - public convenience init(socketURL: NSURL, options: NSDictionary?) { - self.init(socketURL: socketURL, options: options?.toSocketOptionsSet() ?? []) - } - - /// Please use the NSURL based init - @available(*, deprecated=5.3) - public convenience init(socketURLString: String, options: Set = []) { - guard let url = NSURL(string: socketURLString) else { fatalError("Incorrect url") } - self.init(socketURL: url, options: options) - } - - /// Please use the NSURL based init - @available(*, deprecated=5.3) - public convenience init(socketURLString: String, options: NSDictionary?) { - guard let url = NSURL(string: socketURLString) else { fatalError("Incorrect url") } - self.init(socketURL: url, options: options?.toSocketOptionsSet() ?? []) + /// Not so type safe way to create a SocketIOClient, meant for Objective-C compatiblity. + /// If using Swift it's recommended to use `init(socketURL: NSURL, options: Set)` + public convenience init(socketURL: NSURL, config: NSDictionary?) { + self.init(socketURL: socketURL as URL, config: config?.toSocketConfiguration() ?? []) } deinit { DefaultSocketLogger.Logger.log("Client is being released", type: logType) - engine?.close("Client Deinit") + engine?.disconnect(reason: "Client Deinit") } private func addEngine() -> SocketEngineSpec { - DefaultSocketLogger.Logger.log("Adding engine", type: logType) + DefaultSocketLogger.Logger.log("Adding engine", type: logType, args: "") - engine = SocketEngine(client: self, url: socketURL, options: options) + engine = SocketEngine(client: self, url: socketURL, config: config) return engine! } - private func clearReconnectTimer() { - reconnectTimer?.invalidate() - reconnectTimer = nil - } - - @available(*, deprecated=6.0) - public func close() { - disconnect() - } - - /** - Connect to the server. - */ + /// Connect to the server. public func connect() { - connect(timeoutAfter: 0, withTimeoutHandler: nil) + connect(timeoutAfter: 0, withHandler: nil) } - /** - Connect to the server. If we aren't connected after timeoutAfter, call handler - */ - public func connect(timeoutAfter timeoutAfter: Int, withTimeoutHandler handler: (() -> Void)?) { + /// Connect to the server. If we aren't connected after timeoutAfter, call withHandler + /// 0 Never times out + public func connect(timeoutAfter: Int, withHandler handler: (() -> Void)?) { assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") - guard status != .Connected else { - DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", - type: logType) + guard status != .connected else { + DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", type: logType) return } - status = .Connecting + status = .connecting if engine == nil || forceNew { - addEngine().open() + addEngine().connect() } else { - engine?.open() + engine?.connect() } - + guard timeoutAfter != 0 else { return } - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) - - dispatch_after(time, handleQueue) {[weak self] in - if let this = self where this.status != .Connected { - this.status = .Closed - this.engine?.close("Connect timeout") + let time = DispatchTime.now() + Double(Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC) - handler?() - } + handleQueue.asyncAfter(deadline: time) {[weak self] in + guard let this = self, this.status != .connected && this.status != .disconnected else { return } + + this.status = .disconnected + this.engine?.disconnect(reason: "Connect timeout") + + handler?() } } - private func createOnAck(items: [AnyObject]) -> OnAckCallback { + private func createOnAck(_ items: [Any]) -> OnAckCallback { currentAck += 1 - + return {[weak self, ack = currentAck] timeout, callback in - if let this = self { - this.ackHandlers.addAck(ack, callback: callback) - - dispatch_async(this.emitQueue) { - this._emit(items, ack: ack) - } + guard let this = self else { return } - if timeout != 0 { - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC)) - - dispatch_after(time, this.handleQueue) { - this.ackHandlers.timeoutAck(ack) - } + this.ackQueue.sync() { + this.ackHandlers.addAck(ack, callback: callback) + } + + + this._emit(items, ack: ack) + + if timeout != 0 { + let time = DispatchTime.now() + Double(Int64(timeout * NSEC_PER_SEC)) / Double(NSEC_PER_SEC) + + this.handleQueue.asyncAfter(deadline: time) { + this.ackHandlers.timeoutAck(ack, onQueue: this.handleQueue) } } } @@ -204,9 +185,7 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable func didConnect() { DefaultSocketLogger.Logger.log("Socket connected", type: logType) - status = .Connected - currentReconnectAttempt = 0 - clearReconnectTimer() + status = .connected // Don't handle as internal because something crazy could happen where // we disconnect before it's handled @@ -214,104 +193,93 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable } func didDisconnect(reason: String) { - guard status != .Closed else { - return - } + guard status != .disconnected else { return } DefaultSocketLogger.Logger.log("Disconnected: %@", type: logType, args: reason) - status = .Closed - reconnects = false + reconnecting = false + status = .disconnected // Make sure the engine is actually dead. - engine?.close(reason) + engine?.disconnect(reason: reason) handleEvent("disconnect", data: [reason], isInternalMessage: true) } - /** - Disconnects the socket. Only reconnect the same socket if you know what you're doing. - Will turn off automatic reconnects. - */ + /// Disconnects the socket. public func disconnect() { DefaultSocketLogger.Logger.log("Closing socket", type: logType) - reconnects = false - didDisconnect("Disconnect") + didDisconnect(reason: "Disconnect") } - /** - Send a message to the server - */ - public func emit(event: String, _ items: AnyObject...) { - emit(event, withItems: items) + /// Send a message to the server + public func emit(_ event: String, _ items: SocketData...) { + emit(event, with: items) } - /** - Same as emit, but meant for Objective-C - */ - public func emit(event: String, withItems items: [AnyObject]) { - guard status == .Connected else { + /// Same as emit, but meant for Objective-C + public func emit(_ event: String, with items: [Any]) { + guard status == .connected else { handleEvent("error", data: ["Tried emitting \(event) when not connected"], isInternalMessage: true) return } - - dispatch_async(emitQueue) { - self._emit([event] + items) - } + + _emit([event] + items) } - /** - Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add - an ack. - */ - public func emitWithAck(event: String, _ items: AnyObject...) -> OnAckCallback { - return emitWithAck(event, withItems: items) + /// Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add + /// an ack. + public func emitWithAck(_ event: String, _ items: SocketData...) -> OnAckCallback { + return emitWithAck(event, with: items) } - /** - Same as emitWithAck, but for Objective-C - */ - public func emitWithAck(event: String, withItems items: [AnyObject]) -> OnAckCallback { + /// Same as emitWithAck, but for Objective-C + public func emitWithAck(_ event: String, with items: [Any]) -> OnAckCallback { return createOnAck([event] + items) } - private func _emit(data: [AnyObject], ack: Int? = nil) { - guard status == .Connected else { - handleEvent("error", data: ["Tried emitting when not connected"], isInternalMessage: true) - return + private func _emit(_ data: [Any], ack: Int? = nil) { + emitQueue.async { + guard self.status == .connected else { + self.handleEvent("error", data: ["Tried emitting when not connected"], isInternalMessage: true) + return + } + + let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: self.nsp, ack: false) + let str = packet.packetString + + DefaultSocketLogger.Logger.log("Emitting: %@", type: self.logType, args: str) + + self.engine?.send(str, withData: packet.binary) } - - let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: nsp, ack: false) - let str = packet.packetString - - DefaultSocketLogger.Logger.log("Emitting: %@", type: logType, args: str) - - engine?.send(str, withData: packet.binary) } // If the server wants to know that the client received data - func emitAck(ack: Int, withItems items: [AnyObject]) { - dispatch_async(emitQueue) { - if self.status == .Connected { - let packet = SocketPacket.packetFromEmit(items, id: ack ?? -1, nsp: self.nsp, ack: true) - let str = packet.packetString - - DefaultSocketLogger.Logger.log("Emitting Ack: %@", type: self.logType, args: str) - - self.engine?.send(str, withData: packet.binary) - } + func emitAck(_ ack: Int, with items: [Any]) { + emitQueue.async { + guard self.status == .connected else { return } + + let packet = SocketPacket.packetFromEmit(items, id: ack, nsp: self.nsp, ack: true) + let str = packet.packetString + + DefaultSocketLogger.Logger.log("Emitting Ack: %@", type: self.logType, args: str) + + self.engine?.send(str, withData: packet.binary) } } public func engineDidClose(reason: String) { - waitingData.removeAll() - - if status == .Closed || !reconnects { - didDisconnect(reason) - } else if status != .Reconnecting { - status = .Reconnecting - handleEvent("reconnect", data: [reason], isInternalMessage: true) - tryReconnect() + waitingPackets.removeAll() + + if status != .disconnected { + status = .notConnected + } + + if status == .disconnected || !reconnects { + didDisconnect(reason: reason) + } else if !reconnecting { + reconnecting = true + tryReconnect(reason: reason) } } @@ -321,38 +289,38 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable handleEvent("error", data: [reason], isInternalMessage: true) } + + public func engineDidOpen(reason: String) { + DefaultSocketLogger.Logger.log(reason, type: "SocketEngineClient") + } // Called when the socket gets an ack for something it sent - func handleAck(ack: Int, data: [AnyObject]) { - guard status == .Connected else {return} + func handleAck(_ ack: Int, data: [Any]) { + guard status == .connected else { return } - DefaultSocketLogger.Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data ?? "") + DefaultSocketLogger.Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data) - ackHandlers.executeAck(ack, items: data) + handleQueue.async() { + self.ackHandlers.executeAck(ack, with: data, onQueue: self.handleQueue) + } } - /** - Causes an event to be handled. Only use if you know what you're doing. - */ - public func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int = -1) { - guard status == .Connected || isInternalMessage else { - return - } + /// Causes an event to be handled. Only use if you know what you're doing. + public func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) { + guard status == .connected || isInternalMessage else { return } - DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "") + DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data) - dispatch_async(handleQueue) { + handleQueue.async { self.anyHandler?(SocketAnyEvent(event: event, items: data)) for handler in self.handlers where handler.event == event { - handler.executeCallback(data, withAck: ack, withSocket: self) + handler.executeCallback(with: data, withAck: ack, withSocket: self) } } } - /** - Leaves nsp and goes back to / - */ + /// Leaves nsp and goes back to / public func leaveNamespace() { if nsp != "/" { engine?.send("1\(nsp)", withData: []) @@ -360,10 +328,8 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable } } - /** - Joins namespace - */ - public func joinNamespace(namespace: String) { + /// Joins namespace + public func joinNamespace(_ namespace: String) { nsp = namespace if nsp != "/" { @@ -372,48 +338,42 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable } } - /** - Removes handler(s) - */ - public func off(event: String) { + /// Removes handler(s) based on name + public func off(_ event: String) { DefaultSocketLogger.Logger.log("Removing handler for event: %@", type: logType, args: event) - handlers = handlers.filter { $0.event != event } + handlers = handlers.filter({ $0.event != event }) } - /** - Removes a handler with the specified UUID gotten from an `on` or `once` - */ - public func off(id id: NSUUID) { + /// Removes a handler with the specified UUID gotten from an `on` or `once` + public func off(id: UUID) { DefaultSocketLogger.Logger.log("Removing handler with id: %@", type: logType, args: id) - handlers = handlers.filter { $0.id != id } + handlers = handlers.filter({ $0.id != id }) } - /** - Adds a handler for an event. - Returns: A unique id for the handler - */ - public func on(event: String, callback: NormalCallback) -> NSUUID { + /// Adds a handler for an event. + /// Returns: A unique id for the handler + @discardableResult + public func on(_ event: String, callback: @escaping NormalCallback) -> UUID { DefaultSocketLogger.Logger.log("Adding handler for event: %@", type: logType, args: event) - let handler = SocketEventHandler(event: event, id: NSUUID(), callback: callback) + let handler = SocketEventHandler(event: event, id: UUID(), callback: callback) handlers.append(handler) return handler.id } - /** - Adds a single-use handler for an event. - Returns: A unique id for the handler - */ - public func once(event: String, callback: NormalCallback) -> NSUUID { + /// Adds a single-use handler for an event. + /// Returns: A unique id for the handler + @discardableResult + public func once(_ event: String, callback: @escaping NormalCallback) -> UUID { DefaultSocketLogger.Logger.log("Adding once handler for event: %@", type: logType, args: event) - let id = NSUUID() + let id = UUID() let handler = SocketEventHandler(event: event, id: id) {[weak self] data, ack in - guard let this = self else {return} + guard let this = self else { return } this.off(id: id) callback(data, ack) } @@ -423,101 +383,76 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable return handler.id } - /** - Adds a handler that will be called on every event. - */ - public func onAny(handler: (SocketAnyEvent) -> Void) { + /// Adds a handler that will be called on every event. + public func onAny(_ handler: @escaping (SocketAnyEvent) -> Void) { anyHandler = handler } - /** - Same as connect - */ - @available(*, deprecated=6.0) - public func open() { - connect() - } - - public func parseEngineMessage(msg: String) { + public func parseEngineMessage(_ msg: String) { DefaultSocketLogger.Logger.log("Should parse message: %@", type: "SocketIOClient", args: msg) - dispatch_async(parseQueue) { - self.parseSocketMessage(msg) - } + parseQueue.async { self.parseSocketMessage(msg) } } - public func parseEngineBinaryData(data: NSData) { - dispatch_async(parseQueue) { - self.parseBinaryData(data) - } + public func parseEngineBinaryData(_ data: Data) { + parseQueue.async { self.parseBinaryData(data) } } - /** - Tries to reconnect to the server. - */ + /// Tries to reconnect to the server. public func reconnect() { - tryReconnect() + guard !reconnecting else { return } + + engine?.disconnect(reason: "manual reconnect") } - /** - Removes all handlers. - Can be used after disconnecting to break any potential remaining retain cycles. - */ + /// Removes all handlers. + /// Can be used after disconnecting to break any potential remaining retain cycles. public func removeAllHandlers() { - handlers.removeAll(keepCapacity: false) + handlers.removeAll(keepingCapacity: false) } - private func tryReconnect() { - if reconnectTimer == nil { - DefaultSocketLogger.Logger.log("Starting reconnect", type: logType) + private func tryReconnect(reason: String) { + guard reconnecting else { return } - status = .Reconnecting - - dispatch_async(dispatch_get_main_queue()) { - self.reconnectTimer = NSTimer.scheduledTimerWithTimeInterval(Double(self.reconnectWait), - target: self, selector: "_tryReconnect", userInfo: nil, repeats: true) - } - } + DefaultSocketLogger.Logger.log("Starting reconnect", type: logType) + handleEvent("reconnect", data: [reason], isInternalMessage: true) + + _tryReconnect() } - @objc private func _tryReconnect() { - if status == .Connected { - clearReconnectTimer() - - return - } + private func _tryReconnect() { + guard reconnecting else { return } if reconnectAttempts != -1 && currentReconnectAttempt + 1 > reconnectAttempts || !reconnects { - clearReconnectTimer() - didDisconnect("Reconnect Failed") - - return + return didDisconnect(reason: "Reconnect Failed") } DefaultSocketLogger.Logger.log("Trying to reconnect", type: logType) - handleEvent("reconnectAttempt", data: [reconnectAttempts - currentReconnectAttempt], - isInternalMessage: true) + handleEvent("reconnectAttempt", data: [(reconnectAttempts - currentReconnectAttempt)], isInternalMessage: true) currentReconnectAttempt += 1 connect() + + let deadline = DispatchTime.now() + Double(Int64(UInt64(reconnectWait) * NSEC_PER_SEC)) / Double(NSEC_PER_SEC) + + DispatchQueue.main.asyncAfter(deadline: deadline, execute: _tryReconnect) } -} - -// Test extensions -extension SocketIOClient { + + // Test properties + var testHandlers: [SocketEventHandler] { return handlers } - + func setTestable() { - status = .Connected + status = .connected } - - func setTestEngine(engine: SocketEngineSpec?) { + + func setTestEngine(_ engine: SocketEngineSpec?) { self.engine = engine } - - func emitTest(event: String, _ data: AnyObject...) { - self._emit([event] + data) + + func emitTest(event: String, _ data: Any...) { + _emit([event] + data) } } diff --git a/Source/SocketIOClientConfiguration.swift b/Source/SocketIOClientConfiguration.swift new file mode 100755 index 0000000..07aba15 --- /dev/null +++ b/Source/SocketIOClientConfiguration.swift @@ -0,0 +1,108 @@ +// +// SocketIOClientConfiguration.swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 8/13/16. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +public struct SocketIOClientConfiguration : ExpressibleByArrayLiteral, Collection, MutableCollection { + public typealias Element = SocketIOClientOption + public typealias Index = Array.Index + public typealias Generator = Array.Generator + public typealias SubSequence = Array.SubSequence + + private var backingArray = [SocketIOClientOption]() + + public var startIndex: Index { + return backingArray.startIndex + } + + public var endIndex: Index { + return backingArray.endIndex + } + + public var isEmpty: Bool { + return backingArray.isEmpty + } + + public var count: Index.Stride { + return backingArray.count + } + + public var first: Generator.Element? { + return backingArray.first + } + + public subscript(position: Index) -> Generator.Element { + get { + return backingArray[position] + } + + set { + backingArray[position] = newValue + } + } + + public subscript(bounds: Range) -> SubSequence { + get { + return backingArray[bounds] + } + + set { + backingArray[bounds] = newValue + } + } + + public init(arrayLiteral elements: Element...) { + backingArray = elements + } + + public func generate() -> Generator { + return backingArray.makeIterator() + } + + public func index(after i: Index) -> Index { + return backingArray.index(after: i) + } + + public mutating func insert(_ element: Element, replacing replace: Bool = true) { + for i in 0.. SubSequence { + return backingArray.prefix(upTo: end) + } + + public func prefix(through position: Index) -> SubSequence { + return backingArray.prefix(through: position) + } + + public func suffix(from start: Index) -> SubSequence { + return backingArray.suffix(from: start) + } +} diff --git a/Source/SocketIOClientOption.swift b/Source/SocketIOClientOption.swift index c43db7f..dd723f4 100755 --- a/Source/SocketIOClientOption.swift +++ b/Source/SocketIOClientOption.swift @@ -24,118 +24,124 @@ import Foundation -protocol ClientOption: CustomStringConvertible, Hashable { - func getSocketIOOptionValue() -> AnyObject +protocol ClientOption : CustomStringConvertible, Equatable { + func getSocketIOOptionValue() -> Any } -public enum SocketIOClientOption: ClientOption { - case ConnectParams([String: AnyObject]) - case Cookies([NSHTTPCookie]) - case ExtraHeaders([String: String]) - case ForceNew(Bool) - case ForcePolling(Bool) - case ForceWebsockets(Bool) - case HandleQueue(dispatch_queue_t) - case Log(Bool) - case Logger(SocketLogger) - case Nsp(String) - case Path(String) - case Reconnects(Bool) - case ReconnectAttempts(Int) - case ReconnectWait(Int) - case Secure(Bool) - case SelfSigned(Bool) - case SessionDelegate(NSURLSessionDelegate) - case VoipEnabled(Bool) +public enum SocketIOClientOption : ClientOption { + case connectParams([String: Any]) + case cookies([HTTPCookie]) + case doubleEncodeUTF8(Bool) + case extraHeaders([String: String]) + case forceNew(Bool) + case forcePolling(Bool) + case forceWebsockets(Bool) + case handleQueue(DispatchQueue) + case log(Bool) + case logger(SocketLogger) + case nsp(String) + case path(String) + case reconnects(Bool) + case reconnectAttempts(Int) + case reconnectWait(Int) + case secure(Bool) + case security(SSLSecurity) + case selfSigned(Bool) + case sessionDelegate(URLSessionDelegate) + case voipEnabled(Bool) public var description: String { let description: String switch self { - case .ConnectParams: + case .connectParams: description = "connectParams" - case .Cookies: + case .cookies: description = "cookies" - case .ExtraHeaders: + case .doubleEncodeUTF8: + description = "doubleEncodeUTF8" + case .extraHeaders: description = "extraHeaders" - case .ForceNew: + case .forceNew: description = "forceNew" - case .ForcePolling: + case .forcePolling: description = "forcePolling" - case .ForceWebsockets: + case .forceWebsockets: description = "forceWebsockets" - case .HandleQueue: + case .handleQueue: description = "handleQueue" - case .Log: + case .log: description = "log" - case .Logger: + case .logger: description = "logger" - case .Nsp: + case .nsp: description = "nsp" - case .Path: + case .path: description = "path" - case .Reconnects: + case .reconnects: description = "reconnects" - case .ReconnectAttempts: + case .reconnectAttempts: description = "reconnectAttempts" - case .ReconnectWait: + case .reconnectWait: description = "reconnectWait" - case .Secure: + case .secure: description = "secure" - case .SelfSigned: + case .selfSigned: description = "selfSigned" - case .SessionDelegate: + case .security: + description = "security" + case .sessionDelegate: description = "sessionDelegate" - case .VoipEnabled: + case .voipEnabled: description = "voipEnabled" } return description } - public var hashValue: Int { - return description.hashValue - } - - func getSocketIOOptionValue() -> AnyObject { - let value: AnyObject + func getSocketIOOptionValue() -> Any { + let value: Any switch self { - case let .ConnectParams(params): + case let .connectParams(params): value = params - case let .Cookies(cookies): + case let .cookies(cookies): value = cookies - case let .ExtraHeaders(headers): + case let .doubleEncodeUTF8(encode): + value = encode + case let .extraHeaders(headers): value = headers - case let .ForceNew(force): + case let .forceNew(force): value = force - case let .ForcePolling(force): + case let .forcePolling(force): value = force - case let .ForceWebsockets(force): + case let .forceWebsockets(force): value = force - case let .HandleQueue(queue): + case let .handleQueue(queue): value = queue - case let .Log(log): + case let .log(log): value = log - case let .Logger(logger): + case let .logger(logger): value = logger - case let .Nsp(nsp): + case let .nsp(nsp): value = nsp - case let .Path(path): + case let .path(path): value = path - case let .Reconnects(reconnects): + case let .reconnects(reconnects): value = reconnects - case let .ReconnectAttempts(attempts): + case let .reconnectAttempts(attempts): value = attempts - case let .ReconnectWait(wait): + case let .reconnectWait(wait): value = wait - case let .Secure(secure): + case let .secure(secure): value = secure - case let .SelfSigned(signed): + case let .security(security): + value = security + case let .selfSigned(signed): value = signed - case let .SessionDelegate(delegate): + case let .sessionDelegate(delegate): value = delegate - case let .VoipEnabled(enabled): + case let .voipEnabled(enabled): value = enabled } @@ -146,68 +152,3 @@ public enum SocketIOClientOption: ClientOption { public func ==(lhs: SocketIOClientOption, rhs: SocketIOClientOption) -> Bool { return lhs.description == rhs.description } - -extension Set where Element: ClientOption { - mutating func insertIgnore(element: Element) { - if !contains(element) { - insert(element) - } - } -} - -extension NSDictionary { - static func keyValueToSocketIOClientOption(key: String, value: AnyObject) -> SocketIOClientOption? { - switch (key, value) { - case let ("connectParams", params as [String: AnyObject]): - return .ConnectParams(params) - case let ("reconnects", reconnects as Bool): - return .Reconnects(reconnects) - case let ("reconnectAttempts", attempts as Int): - return .ReconnectAttempts(attempts) - case let ("reconnectWait", wait as Int): - return .ReconnectWait(wait) - case let ("forceNew", force as Bool): - return .ForceNew(force) - case let ("forcePolling", force as Bool): - return .ForcePolling(force) - case let ("forceWebsockets", force as Bool): - return .ForceWebsockets(force) - case let ("nsp", nsp as String): - return .Nsp(nsp) - case let ("cookies", cookies as [NSHTTPCookie]): - return .Cookies(cookies) - case let ("log", log as Bool): - return .Log(log) - case let ("logger", logger as SocketLogger): - return .Logger(logger) - case let ("sessionDelegate", delegate as NSURLSessionDelegate): - return .SessionDelegate(delegate) - case let ("path", path as String): - return .Path(path) - case let ("extraHeaders", headers as [String: String]): - return .ExtraHeaders(headers) - case let ("handleQueue", queue as dispatch_queue_t): - return .HandleQueue(queue) - case let ("voipEnabled", enable as Bool): - return .VoipEnabled(enable) - case let ("secure", secure as Bool): - return .Secure(secure) - case let ("selfSigned", selfSigned as Bool): - return .SelfSigned(selfSigned) - default: - return nil - } - } - - func toSocketOptionsSet() -> Set { - var options = Set() - - for (rawKey, value) in self { - if let key = rawKey as? String, opt = NSDictionary.keyValueToSocketIOClientOption(key, value: value) { - options.insertIgnore(opt) - } - } - - return options - } -} diff --git a/Source/SocketClientSpec.swift b/Source/SocketIOClientSpec.swift similarity index 82% rename from Source/SocketClientSpec.swift rename to Source/SocketIOClientSpec.swift index 6408f39..e91c840 100755 --- a/Source/SocketClientSpec.swift +++ b/Source/SocketIOClientSpec.swift @@ -1,5 +1,5 @@ // -// SocketClientSpec.swift +// SocketIOClientSpec.swift // Socket.IO-Client-Swift // // Created by Erik Little on 1/3/16. @@ -22,19 +22,19 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -protocol SocketClientSpec: class { +protocol SocketIOClientSpec : class { var nsp: String { get set } - var waitingData: [SocketPacket] { get set } + var waitingPackets: [SocketPacket] { get set } func didConnect() func didDisconnect(reason: String) func didError(reason: String) - func handleAck(ack: Int, data: [AnyObject]) - func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int) - func joinNamespace(namespace: String) + func handleAck(_ ack: Int, data: [Any]) + func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int) + func joinNamespace(_ namespace: String) } -extension SocketClientSpec { +extension SocketIOClientSpec { func didError(reason: String) { DefaultSocketLogger.Logger.error("%@", type: "SocketIOClient", args: reason) diff --git a/Source/SocketIOClientStatus.swift b/Source/SocketIOClientStatus.swift index e8720c6..27574e6 100755 --- a/Source/SocketIOClientStatus.swift +++ b/Source/SocketIOClientStatus.swift @@ -24,21 +24,9 @@ import Foundation -@objc public enum SocketIOClientStatus: Int, CustomStringConvertible { - case NotConnected, Closed, Connecting, Connected, Reconnecting - - public var description: String { - switch self { - case NotConnected: - return "Not Connected" - case Closed: - return "Closed" - case Connecting: - return "Connecting" - case Connected: - return "Connected" - case Reconnecting: - return "Reconnecting" - } - } -} \ No newline at end of file +/// **NotConnected**: initial state +/// +/// **Disconnected**: connected before +@objc public enum SocketIOClientStatus : Int { + case notConnected, disconnected, connecting, connected +} diff --git a/Source/SocketLogger.swift b/Source/SocketLogger.swift index bbb8578..640d344 100755 --- a/Source/SocketLogger.swift +++ b/Source/SocketLogger.swift @@ -24,37 +24,37 @@ import Foundation -public protocol SocketLogger: class { +public protocol SocketLogger : class { /// Whether to log or not - var log: Bool {get set} + var log: Bool { get set } /// Normal log messages - func log(message: String, type: String, args: AnyObject...) + func log(_ message: String, type: String, args: Any...) /// Error Messages - func error(message: String, type: String, args: AnyObject...) + func error(_ message: String, type: String, args: Any...) } public extension SocketLogger { - func log(message: String, type: String, args: AnyObject...) { + func log(_ message: String, type: String, args: Any...) { abstractLog("LOG", message: message, type: type, args: args) } - func error(message: String, type: String, args: AnyObject...) { + func error(_ message: String, type: String, args: Any...) { abstractLog("ERROR", message: message, type: type, args: args) } - private func abstractLog(logType: String, message: String, type: String, args: [AnyObject]) { + private func abstractLog(_ logType: String, message: String, type: String, args: [Any]) { guard log else { return } - let newArgs = args.map({arg -> CVarArgType in String(arg)}) - let replaced = String(format: message, arguments: newArgs) + let newArgs = args.map({arg -> CVarArg in String(describing: arg)}) + let messageFormat = String(format: message, arguments: newArgs) - NSLog("%@ %@: %@", logType, type, replaced) + NSLog("\(logType) \(type): %@", messageFormat) } } -class DefaultSocketLogger: SocketLogger { +class DefaultSocketLogger : SocketLogger { static var Logger: SocketLogger = DefaultSocketLogger() var log = false diff --git a/Source/SocketPacket.swift b/Source/SocketPacket.swift index 19eaf74..95b3218 100755 --- a/Source/SocketPacket.swift +++ b/Source/SocketPacket.swift @@ -26,6 +26,10 @@ import Foundation struct SocketPacket { + enum PacketType: Int { + case connect, disconnect, event, ack, error, binaryEvent, binaryAck + } + private let placeholders: Int private static let logType = "SocketPacket" @@ -34,35 +38,31 @@ struct SocketPacket { let id: Int let type: PacketType - enum PacketType: Int { - case Connect, Disconnect, Event, Ack, Error, BinaryEvent, BinaryAck - } - - var args: [AnyObject] { - if type == .Event || type == .BinaryEvent && data.count != 0 { + var binary: [Data] + var data: [Any] + var args: [Any] { + if type == .event || type == .binaryEvent && data.count != 0 { return Array(data.dropFirst()) } else { return data } } - var binary: [NSData] - var data: [AnyObject] var description: String { return "SocketPacket {type: \(String(type.rawValue)); data: " + - "\(String(data)); id: \(id); placeholders: \(placeholders); nsp: \(nsp)}" + "\(String(describing: data)); id: \(id); placeholders: \(placeholders); nsp: \(nsp)}" } var event: String { - return String(data[0]) + return String(describing: data[0]) } var packetString: String { return createPacketString() } - init(type: SocketPacket.PacketType, data: [AnyObject] = [AnyObject](), id: Int = -1, - nsp: String, placeholders: Int = 0, binary: [NSData] = [NSData]()) { + init(type: PacketType, data: [Any] = [Any](), id: Int = -1, nsp: String, placeholders: Int = 0, + binary: [Data] = [Data]()) { self.data = data self.id = id self.nsp = nsp @@ -71,7 +71,7 @@ struct SocketPacket { self.binary = binary } - mutating func addData(data: NSData) -> Bool { + mutating func addData(_ data: Data) -> Bool { if placeholders == binary.count { return true } @@ -86,108 +86,38 @@ struct SocketPacket { } } - private func completeMessage(message: String, ack: Bool) -> String { - var restOfMessage = "" + private func completeMessage(_ message: String) -> String { + let restOfMessage: String if data.count == 0 { - return message + "]" - } - - for arg in data { - if arg is NSDictionary || arg is [AnyObject] { - do { - let jsonSend = try NSJSONSerialization.dataWithJSONObject(arg, - options: NSJSONWritingOptions(rawValue: 0)) - let jsonString = String(data: jsonSend, encoding: NSUTF8StringEncoding) - - restOfMessage += jsonString! + "," - } catch { - DefaultSocketLogger.Logger.error("Error creating JSON object in SocketPacket.completeMessage", - type: SocketPacket.logType) - } - } else if let str = arg as? String { - restOfMessage += "\"" + ((str["\n"] <~ "\\\\n")["\r"] <~ "\\\\r") + "\"," - } else if arg is NSNull { - restOfMessage += "null," - } else { - restOfMessage += "\(arg)," - } - } - - if restOfMessage != "" { - restOfMessage.removeAtIndex(restOfMessage.endIndex.predecessor()) - } - - return message + restOfMessage + "]" - } - - private func createAck() -> String { - let message: String - - if type == .Ack { - if nsp == "/" { - message = "3\(id)[" - } else { - message = "3\(nsp),\(id)[" - } - } else { - if nsp == "/" { - message = "6\(binary.count)-\(id)[" - } else { - message = "6\(binary.count)-\(nsp),\(id)[" - } + return message + "[]" } - return completeMessage(message, ack: true) - } - - - private func createMessageForEvent() -> String { - let message: String - - if type == .Event { - if nsp == "/" { - if id == -1 { - message = "2[" - } else { - message = "2\(id)[" - } - } else { - if id == -1 { - message = "2\(nsp),[" - } else { - message = "2\(nsp),\(id)[" - } - } - } else { - if nsp == "/" { - if id == -1 { - message = "5\(binary.count)-[" - } else { - message = "5\(binary.count)-\(id)[" - } - } else { - if id == -1 { - message = "5\(binary.count)-\(nsp),[" - } else { - message = "5\(binary.count)-\(nsp),\(id)[" - } - } + do { + let jsonSend = try data.toJSON() + guard let jsonString = String(data: jsonSend, encoding: .utf8) else { return message + "[]" } + + restOfMessage = jsonString + } catch { + DefaultSocketLogger.Logger.error("Error creating JSON object in SocketPacket.completeMessage", + type: SocketPacket.logType) + + restOfMessage = "[]" } - return completeMessage(message, ack: false) + return message + restOfMessage } private func createPacketString() -> String { - let str: String - - if type == .Event || type == .BinaryEvent { - str = createMessageForEvent() - } else { - str = createAck() - } + let typeString = String(type.rawValue) + // Binary count? + let binaryCountString = typeString + (type == .binaryEvent || type == .binaryAck ? "\(String(binary.count))-" : "") + // Namespace? + let nspString = binaryCountString + (nsp != "/" ? "\(nsp)," : "") + // Ack number? + let idString = nspString + (id != -1 ? String(id) : "") - return str + return completeMessage(idString) } // Called when we have all the binary data for a packet @@ -197,20 +127,25 @@ struct SocketPacket { data = data.map(_fillInPlaceholders) } - // Helper method that looks for placeholder strings + // Helper method that looks for placeholders // If object is a collection it will recurse - // Returns the object if it is not a placeholder string or the corresponding + // Returns the object if it is not a placeholder or the corresponding // binary data - private func _fillInPlaceholders(object: AnyObject) -> AnyObject { + private func _fillInPlaceholders(_ object: Any) -> Any { switch object { - case let string as String where string["~~(\\d)"].groups() != nil: - return binary[Int(string["~~(\\d)"].groups()![1])!] - case let dict as NSDictionary: - return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in - cur[keyValue.0 as! NSCopying] = _fillInPlaceholders(keyValue.1) - return cur - }) - case let arr as [AnyObject]: + case let dict as [String: Any]: + if dict["_placeholder"] as? Bool ?? false { + return binary[dict["num"] as! Int] + } else { + return dict.reduce([String: Any](), {cur, keyValue in + var cur = cur + + cur[keyValue.0] = _fillInPlaceholders(keyValue.1) + + return cur + }) + } + case let arr as [Any]: return arr.map(_fillInPlaceholders) default: return object @@ -219,25 +154,25 @@ struct SocketPacket { } extension SocketPacket { - private static func findType(binCount: Int, ack: Bool) -> PacketType { + private static func findType(_ binCount: Int, ack: Bool) -> PacketType { switch binCount { case 0 where !ack: - return .Event + return .event case 0 where ack: - return .Ack + return .ack case _ where !ack: - return .BinaryEvent + return .binaryEvent case _ where ack: - return .BinaryAck + return .binaryAck default: - return .Error + return .error } } - static func packetFromEmit(items: [AnyObject], id: Int, nsp: String, ack: Bool) -> SocketPacket { + static func packetFromEmit(_ items: [Any], id: Int, nsp: String, ack: Bool) -> SocketPacket { let (parsedData, binary) = deconstructData(items) let packet = SocketPacket(type: findType(binary.count, ack: ack), data: parsedData, - id: id, nsp: nsp, placeholders: -1, binary: binary) + id: id, nsp: nsp, binary: binary) return packet } @@ -245,19 +180,23 @@ extension SocketPacket { private extension SocketPacket { // Recursive function that looks for NSData in collections - static func shred(data: AnyObject, inout binary: [NSData]) -> AnyObject { - let placeholder = ["_placeholder": true, "num": binary.count] + static func shred(_ data: Any, binary: inout [Data]) -> Any { + let placeholder = ["_placeholder": true, "num": binary.count] as [String : Any] switch data { - case let bin as NSData: + case let bin as Data: binary.append(bin) + return placeholder - case let arr as [AnyObject]: + case let arr as [Any]: return arr.map({shred($0, binary: &binary)}) - case let dict as NSDictionary: - return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in - cur[keyValue.0 as! NSCopying] = shred(keyValue.1, binary: &binary) - return cur + case let dict as [String: Any]: + return dict.reduce([String: Any](), {cur, keyValue in + var mutCur = cur + + mutCur[keyValue.0] = shred(keyValue.1, binary: &binary) + + return mutCur }) default: return data @@ -266,8 +205,8 @@ private extension SocketPacket { // Removes binary data from emit data // Returns a type containing the de-binaryed data and the binary - static func deconstructData(data: [AnyObject]) -> ([AnyObject], [NSData]) { - var binary = [NSData]() + static func deconstructData(_ data: [Any]) -> ([Any], [Data]) { + var binary = [Data]() return (data.map({shred($0, binary: &binary)}), binary) } diff --git a/Source/SocketParsable.swift b/Source/SocketParsable.swift index 8423ca3..7c9ce21 100755 --- a/Source/SocketParsable.swift +++ b/Source/SocketParsable.swift @@ -22,42 +22,39 @@ import Foundation -protocol SocketParsable: SocketClientSpec { - func parseBinaryData(data: NSData) - func parseSocketMessage(message: String) +protocol SocketParsable : SocketIOClientSpec { + func parseBinaryData(_ data: Data) + func parseSocketMessage(_ message: String) } extension SocketParsable { - private func isCorrectNamespace(nsp: String) -> Bool { + private func isCorrectNamespace(_ nsp: String) -> Bool { return nsp == self.nsp } - private func handleConnect(p: SocketPacket) { - if p.nsp == "/" && nsp != "/" { + private func handleConnect(_ packetNamespace: String) { + if packetNamespace == "/" && nsp != "/" { joinNamespace(nsp) - } else if p.nsp != "/" && nsp == "/" { - didConnect() } else { didConnect() } } - private func handlePacket(pack: SocketPacket) { + private func handlePacket(_ pack: SocketPacket) { switch pack.type { - case .Event where isCorrectNamespace(pack.nsp): - handleEvent(pack.event, data: pack.args, - isInternalMessage: false, withAck: pack.id) - case .Ack where isCorrectNamespace(pack.nsp): + case .event where isCorrectNamespace(pack.nsp): + handleEvent(pack.event, data: pack.args, isInternalMessage: false, withAck: pack.id) + case .ack where isCorrectNamespace(pack.nsp): handleAck(pack.id, data: pack.data) - case .BinaryEvent where isCorrectNamespace(pack.nsp): - waitingData.append(pack) - case .BinaryAck where isCorrectNamespace(pack.nsp): - waitingData.append(pack) - case .Connect: - handleConnect(pack) - case .Disconnect: - didDisconnect("Got Disconnect") - case .Error: + case .binaryEvent where isCorrectNamespace(pack.nsp): + waitingPackets.append(pack) + case .binaryAck where isCorrectNamespace(pack.nsp): + waitingPackets.append(pack) + case .connect: + handleConnect(pack.nsp) + case .disconnect: + didDisconnect(reason: "Got Disconnect") + case .error: handleEvent("error", data: pack.data, isInternalMessage: true, withAck: pack.id) default: DefaultSocketLogger.Logger.log("Got invalid packet: %@", type: "SocketParser", args: pack.description) @@ -65,116 +62,105 @@ extension SocketParsable { } /// Parses a messsage from the engine. Returning either a string error or a complete SocketPacket - func parseString(message: String) -> Either { - var parser = SocketStringReader(message: message) + func parseString(_ message: String) -> Either { + var reader = SocketStringReader(message: message) - guard let type = SocketPacket.PacketType(rawValue: Int(parser.read(1)) ?? -1) else { - return .Left("Invalid packet type") + guard let type = Int(reader.read(count: 1)).flatMap({ SocketPacket.PacketType(rawValue: $0) }) else { + return .left("Invalid packet type") } - if !parser.hasNext { - return .Right(SocketPacket(type: type, nsp: "/")) + if !reader.hasNext { + return .right(SocketPacket(type: type, nsp: "/")) } - var namespace: String? + var namespace = "/" var placeholders = -1 - if type == .BinaryEvent || type == .BinaryAck { - if let holders = Int(parser.readUntilStringOccurence("-")) { + if type == .binaryEvent || type == .binaryAck { + if let holders = Int(reader.readUntilOccurence(of: "-")) { placeholders = holders } else { - return .Left("Invalid packet") + return .left("Invalid packet") } } - if parser.currentCharacter == "/" { - namespace = parser.readUntilStringOccurence(",") ?? parser.readUntilEnd() + if reader.currentCharacter == "/" { + namespace = reader.readUntilOccurence(of: ",") } - if !parser.hasNext { - return .Right(SocketPacket(type: type, id: -1, - nsp: namespace ?? "/", placeholders: placeholders)) + if !reader.hasNext { + return .right(SocketPacket(type: type, nsp: namespace, placeholders: placeholders)) } var idString = "" - if type == .Error { - parser.advanceIndexBy(-1) - } - - while parser.hasNext && type != .Error { - if let int = Int(parser.read(1)) { - idString += String(int) - } else { - parser.advanceIndexBy(-2) - break + if type == .error { + reader.advance(by: -1) + } else { + while reader.hasNext { + if let int = Int(reader.read(count: 1)) { + idString += String(int) + } else { + reader.advance(by: -2) + break + } } } - let d = message[parser.currentIndex.advancedBy(1).. Either { - let stringData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) + private func parseData(_ data: String) -> Either { do { - if let arr = try NSJSONSerialization.JSONObjectWithData(stringData!, - options: NSJSONReadingOptions.MutableContainers) as? [AnyObject] { - return .Right(arr) - } else { - return .Left("Expected data array") - } + return .right(try data.toArray()) } catch { - return .Left("Error parsing data for packet") + return .left("Error parsing data for packet") } } // Parses messages recieved - func parseSocketMessage(message: String) { + func parseSocketMessage(_ message: String) { guard !message.isEmpty else { return } DefaultSocketLogger.Logger.log("Parsing %@", type: "SocketParser", args: message) switch parseString(message) { - case let .Left(err): + case let .left(err): DefaultSocketLogger.Logger.error("\(err): %@", type: "SocketParser", args: message) - case let .Right(pack): + case let .right(pack): DefaultSocketLogger.Logger.log("Decoded packet as: %@", type: "SocketParser", args: pack.description) handlePacket(pack) } } - func parseBinaryData(data: NSData) { - guard !waitingData.isEmpty else { + func parseBinaryData(_ data: Data) { + guard !waitingPackets.isEmpty else { DefaultSocketLogger.Logger.error("Got data when not remaking packet", type: "SocketParser") return } // Should execute event? - guard waitingData[waitingData.count - 1].addData(data) else { - return - } + guard waitingPackets[waitingPackets.count - 1].addData(data) else { return } - let packet = waitingData.removeLast() + let packet = waitingPackets.removeLast() - if packet.type != .BinaryAck { - handleEvent(packet.event, data: packet.args ?? [], - isInternalMessage: false, withAck: packet.id) + if packet.type != .binaryAck { + handleEvent(packet.event, data: packet.args, isInternalMessage: false, withAck: packet.id) } else { handleAck(packet.id, data: packet.args) } diff --git a/Source/SocketStringReader.swift b/Source/SocketStringReader.swift index e3b2d69..8bdb4d4 100755 --- a/Source/SocketStringReader.swift +++ b/Source/SocketStringReader.swift @@ -38,31 +38,36 @@ struct SocketStringReader { currentIndex = message.startIndex } - mutating func advanceIndexBy(n: Int) { - currentIndex = currentIndex.advancedBy(n) + @discardableResult + mutating func advance(by: Int) -> String.Index { + currentIndex = message.characters.index(currentIndex, offsetBy: by) + + return currentIndex } - mutating func read(readLength: Int) -> String { - let readString = message[currentIndex.. String { + let readString = message[currentIndex.. String { + mutating func readUntilOccurence(of string: String) -> String { let substring = message[currentIndex.. String { - return read(currentIndex.distanceTo(message.endIndex)) + return read(count: message.characters.distance(from: currentIndex, to: message.endIndex)) } -} \ No newline at end of file +} diff --git a/Source/SocketTypes.swift b/Source/SocketTypes.swift index b8840be..42bf329 100755 --- a/Source/SocketTypes.swift +++ b/Source/SocketTypes.swift @@ -24,11 +24,29 @@ import Foundation -public typealias AckCallback = ([AnyObject]) -> Void -public typealias NormalCallback = ([AnyObject], SocketAckEmitter) -> Void -public typealias OnAckCallback = (timeoutAfter: UInt64, callback: AckCallback) -> Void +public protocol SocketData {} + +extension Array : SocketData {} +extension Bool : SocketData {} +extension Dictionary : SocketData {} +extension Double : SocketData {} +extension Int : SocketData {} +extension NSArray : SocketData {} +extension Data : SocketData {} +extension NSData : SocketData {} +extension NSDictionary : SocketData {} +extension NSString : SocketData {} +extension NSNull : SocketData {} +extension String : SocketData {} + +public typealias AckCallback = ([Any]) -> Void +public typealias NormalCallback = ([Any], SocketAckEmitter) -> Void +public typealias OnAckCallback = (_ timeoutAfter: UInt64, _ callback: @escaping AckCallback) -> Void + +typealias Probe = (msg: String, type: SocketEnginePacketType, data: [Data]) +typealias ProbeWaitQueue = [Probe] enum Either { - case Left(E) - case Right(V) + case left(E) + case right(V) } diff --git a/Source/SwiftRegex.swift b/Source/SwiftRegex.swift deleted file mode 100755 index b7c134b..0000000 --- a/Source/SwiftRegex.swift +++ /dev/null @@ -1,184 +0,0 @@ -// -// SwiftRegex.swift -// SwiftRegex -// -// Created by John Holdsworth on 26/06/2014. -// Copyright (c) 2014 John Holdsworth. -// -// $Id: //depot/SwiftRegex/SwiftRegex.swift#37 $ -// -// This code is in the public domain from: -// https://github.com/johnno1962/SwiftRegex -// - -import Foundation - -infix operator <~ { associativity none precedence 130 } - -private let lock = dispatch_semaphore_create(1) -private var swiftRegexCache = [String: NSRegularExpression]() - -internal final class SwiftRegex: NSObject, BooleanType { - var target:String - var regex: NSRegularExpression - - init(target:String, pattern:String, options:NSRegularExpressionOptions?) { - if dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC))) != 0 { - fatalError("This should never happen") - } - - self.target = target - if let regex = swiftRegexCache[pattern] { - self.regex = regex - } else { - do { - let regex = try NSRegularExpression(pattern: pattern, options: - NSRegularExpressionOptions.DotMatchesLineSeparators) - swiftRegexCache[pattern] = regex - self.regex = regex - } catch let error as NSError { - SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") - self.regex = NSRegularExpression() - } - } - dispatch_semaphore_signal(lock) - super.init() - } - - private static func failure(message: String) { - fatalError("SwiftRegex: \(message)") - } - - private var targetRange: NSRange { - return NSRange(location: 0,length: target.utf16.count) - } - - private func substring(range: NSRange) -> String? { - if range.location != NSNotFound { - return (target as NSString).substringWithRange(range) - } else { - return nil - } - } - - func doesMatch(options: NSMatchingOptions!) -> Bool { - return range(options).location != NSNotFound - } - - func range(options: NSMatchingOptions) -> NSRange { - return regex.rangeOfFirstMatchInString(target as String, options: [], range: targetRange) - } - - func match(options: NSMatchingOptions) -> String? { - return substring(range(options)) - } - - func groups() -> [String]? { - return groupsForMatch(regex.firstMatchInString(target as String, options: - NSMatchingOptions.WithoutAnchoringBounds, range: targetRange)) - } - - private func groupsForMatch(match: NSTextCheckingResult?) -> [String]? { - guard let match = match else { - return nil - } - var groups = [String]() - for groupno in 0...regex.numberOfCaptureGroups { - if let group = substring(match.rangeAtIndex(groupno)) { - groups += [group] - } else { - groups += ["_"] // avoids bridging problems - } - } - return groups - } - - subscript(groupno: Int) -> String? { - get { - return groups()?[groupno] - } - - set(newValue) { - if newValue == nil { - return - } - - for match in Array(matchResults().reverse()) { - let replacement = regex.replacementStringForResult(match, - inString: target as String, offset: 0, template: newValue!) - let mut = NSMutableString(string: target) - mut.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) - - target = mut as String - } - } - } - - func matchResults() -> [NSTextCheckingResult] { - let matches = regex.matchesInString(target as String, options: - NSMatchingOptions.WithoutAnchoringBounds, range: targetRange) - as [NSTextCheckingResult] - - return matches - } - - func ranges() -> [NSRange] { - return matchResults().map { $0.range } - } - - func matches() -> [String] { - return matchResults().map( { self.substring($0.range)!}) - } - - func allGroups() -> [[String]?] { - return matchResults().map { self.groupsForMatch($0) } - } - - func dictionary(options: NSMatchingOptions!) -> Dictionary { - var out = Dictionary() - for match in matchResults() { - out[substring(match.rangeAtIndex(1))!] = substring(match.rangeAtIndex(2))! - } - return out - } - - func substituteMatches(substitution: ((NSTextCheckingResult, UnsafeMutablePointer) -> String), - options:NSMatchingOptions) -> String { - let out = NSMutableString() - var pos = 0 - - regex.enumerateMatchesInString(target as String, options: options, range: targetRange ) {match, flags, stop in - let matchRange = match!.range - out.appendString( self.substring(NSRange(location:pos, length:matchRange.location-pos))!) - out.appendString( substitution(match!, stop) ) - pos = matchRange.location + matchRange.length - } - - out.appendString(substring(NSRange(location:pos, length:targetRange.length-pos))!) - - return out as String - } - - var boolValue: Bool { - return doesMatch(nil) - } -} - -extension String { - subscript(pattern: String, options: NSRegularExpressionOptions) -> SwiftRegex { - return SwiftRegex(target: self, pattern: pattern, options: options) - } -} - -extension String { - subscript(pattern: String) -> SwiftRegex { - return SwiftRegex(target: self, pattern: pattern, options: nil) - } -} - -func <~ (left: SwiftRegex, right: String) -> String { - return left.substituteMatches({match, stop in - return left.regex.replacementStringForResult( match, - inString: left.target as String, offset: 0, template: right ) - }, options: []) -} diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index 49e585b..95982ae 100755 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -23,55 +23,61 @@ import Foundation import CoreFoundation import Security +public let WebsocketDidConnectNotification = "WebsocketDidConnectNotification" +public let WebsocketDidDisconnectNotification = "WebsocketDidDisconnectNotification" +public let WebsocketDisconnectionErrorKeyName = "WebsocketDisconnectionErrorKeyName" + public protocol WebSocketDelegate: class { func websocketDidConnect(socket: WebSocket) func websocketDidDisconnect(socket: WebSocket, error: NSError?) func websocketDidReceiveMessage(socket: WebSocket, text: String) - func websocketDidReceiveData(socket: WebSocket, data: NSData) + func websocketDidReceiveData(socket: WebSocket, data: Data) } public protocol WebSocketPongDelegate: class { - func websocketDidReceivePong(socket: WebSocket) + func websocketDidReceivePong(socket: WebSocket, data: Data?) } -public class WebSocket : NSObject, NSStreamDelegate { +public class WebSocket : NSObject, StreamDelegate { enum OpCode : UInt8 { - case ContinueFrame = 0x0 - case TextFrame = 0x1 - case BinaryFrame = 0x2 - //3-7 are reserved. - case ConnectionClose = 0x8 - case Ping = 0x9 - case Pong = 0xA - //B-F reserved. + case continueFrame = 0x0 + case textFrame = 0x1 + case binaryFrame = 0x2 + // 3-7 are reserved. + case connectionClose = 0x8 + case ping = 0x9 + case pong = 0xA + // B-F reserved. } public enum CloseCode : UInt16 { - case Normal = 1000 - case GoingAway = 1001 - case ProtocolError = 1002 - case ProtocolUnhandledType = 1003 + case normal = 1000 + case goingAway = 1001 + case protocolError = 1002 + case protocolUnhandledType = 1003 // 1004 reserved. - case NoStatusReceived = 1005 + case noStatusReceived = 1005 //1006 reserved. - case Encoding = 1007 - case PolicyViolated = 1008 - case MessageTooBig = 1009 + case encoding = 1007 + case policyViolated = 1008 + case messageTooBig = 1009 } public static let ErrorDomain = "WebSocket" - enum InternalErrorCode : UInt16 { + enum InternalErrorCode: UInt16 { // 0-999 WebSocket status codes not used - case OutputStreamWriteError = 1 + case outputStreamWriteError = 1 } - //Where the callback is executed. It defaults to the main UI thread queue. - public var queue = dispatch_get_main_queue() + // Where the callback is executed. It defaults to the main UI thread queue. + public var callbackQueue = DispatchQueue.main + + var optionalProtocols: [String]? + + // MARK: - Constants - var optionalProtocols : [String]? - //Constant Values. let headerWSUpgradeName = "Upgrade" let headerWSUpgradeValue = "websocket" let headerWSHostName = "Host" @@ -90,115 +96,162 @@ public class WebSocket : NSObject, NSStreamDelegate { let MaskMask: UInt8 = 0x80 let PayloadLenMask: UInt8 = 0x7F let MaxFrameSize: Int = 32 + let httpSwitchProtocolCode = 101 + let supportedSSLSchemes = ["wss", "https"] class WSResponse { var isFin = false - var code: OpCode = .ContinueFrame + var code: OpCode = .continueFrame var bytesLeft = 0 var frameCount = 0 var buffer: NSMutableData? } + // MARK: - Delegates + + /// Responds to callback about new messages coming in over the WebSocket + /// and also connection/disconnect messages. public weak var delegate: WebSocketDelegate? + + /// Receives a callback for each pong message recived. public weak var pongDelegate: WebSocketPongDelegate? + + + // MARK: - Block based API. + public var onConnect: ((Void) -> Void)? public var onDisconnect: ((NSError?) -> Void)? public var onText: ((String) -> Void)? - public var onData: ((NSData) -> Void)? - public var onPong: ((Void) -> Void)? + public var onData: ((Data) -> Void)? + public var onPong: ((Data?) -> Void)? + public var headers = [String: String]() public var voipEnabled = false - public var selfSignedSSL = false - private var security: SSLSecurity? + public var disableSSLCertValidation = false + public var security: SSLSecurity? public var enabledSSLCipherSuites: [SSLCipherSuite]? - public var isConnected :Bool { + public var origin: String? + public var timeout = 5 + public var isConnected: Bool { return connected } - private var url: NSURL - private var inputStream: NSInputStream? - private var outputStream: NSOutputStream? - private var isRunLoop = false + + public var currentURL: URL { return url } + + // MARK: - Private + + private var url: URL + private var inputStream: InputStream? + private var outputStream: OutputStream? private var connected = false - private var isCreated = false - private var writeQueue = NSOperationQueue() + private var isConnecting = false + private var writeQueue = OperationQueue() private var readStack = [WSResponse]() - private var inputQueue = [NSData]() - private var fragBuffer: NSData? + private var inputQueue = [Data]() + private var fragBuffer: Data? private var certValidated = false private var didDisconnect = false - - //used for setting protocols. - public init(url: NSURL, protocols: [String]? = nil) { + private var readyToWrite = false + private let mutex = NSLock() + private let notificationCenter = NotificationCenter.default + private var canDispatch: Bool { + mutex.lock() + let canWork = readyToWrite + mutex.unlock() + return canWork + } + /// The shared processing queue used for all WebSocket. + private static let sharedWorkQueue = DispatchQueue(label: "com.vluxe.starscream.websocket", attributes: []) + + /// Used for setting protocols. + public init(url: URL, protocols: [String]? = nil) { self.url = url + self.origin = url.absoluteString writeQueue.maxConcurrentOperationCount = 1 optionalProtocols = protocols } - ///Connect to the websocket server on a background thread + // Used for specifically setting the QOS for the write queue. + public convenience init(url: URL, writeQueueQOS: QualityOfService, protocols: [String]? = nil) { + self.init(url: url, protocols: protocols) + writeQueue.qualityOfService = writeQueueQOS + } + + /** + Connect to the WebSocket server on a background thread. + */ public func connect() { - guard !isCreated else { return } - - dispatch_async(queue) { [weak self] in - self?.didDisconnect = false - } - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)) { [weak self] in - self?.isCreated = true - self?.createHTTPRequest() - self?.isCreated = false - } + guard !isConnecting else { return } + didDisconnect = false + isConnecting = true + createHTTPRequest() + isConnecting = false } /** Disconnect from the server. I send a Close control frame to the server, then expect the server to respond with a Close control frame and close the socket from its end. I notify my delegate once the socket has been closed. - If you supply a non-nil `forceTimeout`, I wait at most that long (in seconds) for the server to close the socket. After the timeout expires, I close the socket and notify my delegate. - If you supply a zero (or negative) `forceTimeout`, I immediately close the socket (without sending a Close control frame) and notify my delegate. - - Parameter forceTimeout: Maximum time to wait for the server to close the socket. + - Parameter closeCode: The code to send on disconnect. The default is the normal close code for cleanly disconnecting a webSocket. */ - public func disconnect(forceTimeout forceTimeout: NSTimeInterval? = nil) { + public func disconnect(forceTimeout: TimeInterval? = nil, closeCode: UInt16 = CloseCode.normal.rawValue) { switch forceTimeout { - case .Some(let seconds) where seconds > 0: - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))), queue) { [unowned self] in - self.disconnectStream(nil) + case .some(let seconds) where seconds > 0: + callbackQueue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(seconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { [weak self] in + self?.disconnectStream(nil) } fallthrough - case .None: - writeError(CloseCode.Normal.rawValue) - + case .none: + writeError(closeCode) default: - self.disconnectStream(nil) + disconnectStream(nil) break } } - ///write a string to the websocket. This sends it as a text frame. - public func writeString(str: String) { - dequeueWrite(str.dataUsingEncoding(NSUTF8StringEncoding)!, code: .TextFrame) + /** + Write a string to the websocket. This sends it as a text frame. + If you supply a non-nil completion block, I will perform it when the write completes. + - parameter str: The string to write. + - parameter completion: The (optional) completion handler. + */ + public func write(string: String, completion: (() -> ())? = nil) { + guard isConnected else { return } + dequeueWrite(string.data(using: String.Encoding.utf8)!, code: .textFrame, writeCompletion: completion) } - ///write binary data to the websocket. This sends it as a binary frame. - public func writeData(data: NSData) { - dequeueWrite(data, code: .BinaryFrame) + /** + Write binary data to the websocket. This sends it as a binary frame. + If you supply a non-nil completion block, I will perform it when the write completes. + - parameter data: The data to write. + - parameter completion: The (optional) completion handler. + */ + public func write(data: Data, completion: (() -> ())? = nil) { + guard isConnected else { return } + dequeueWrite(data, code: .binaryFrame, writeCompletion: completion) } - //write a ping to the websocket. This sends it as a control frame. - //yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s - public func writePing(data: NSData) { - dequeueWrite(data, code: .Ping) + /** + Write a ping to the websocket. This sends it as a control frame. + Yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s + */ + public func write(ping: Data, completion: (() -> ())? = nil) { + guard isConnected else { return } + dequeueWrite(ping, code: .ping, writeCompletion: completion) } - //private methods below! - //private method that starts the connection + /** + Private method that starts the connection. + */ private func createHTTPRequest() { - let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET", - url, kCFHTTPVersion1_1).takeRetainedValue() + let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString, + url as CFURL, kCFHTTPVersion1_1).takeRetainedValue() var port = url.port if port == nil { - if ["wss", "https"].contains(url.scheme) { + if supportedSSLSchemes.contains(url.scheme!) { port = 443 } else { port = 80 @@ -207,190 +260,254 @@ public class WebSocket : NSObject, NSStreamDelegate { addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue) addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue) if let protocols = optionalProtocols { - addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joinWithSeparator(",")) + addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joined(separator: ",")) } addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey()) - addHeader(urlRequest, key: headerOriginName, val: url.absoluteString) + if let origin = origin { + addHeader(urlRequest, key: headerOriginName, val: origin) + } addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)") - for (key,value) in headers { + for (key, value) in headers { addHeader(urlRequest, key: key, val: value) } if let cfHTTPMessage = CFHTTPMessageCopySerializedMessage(urlRequest) { let serializedRequest = cfHTTPMessage.takeRetainedValue() - initStreamsWithData(serializedRequest, Int(port!)) + initStreamsWithData(serializedRequest as Data, Int(port!)) } } - //Add a header to the CFHTTPMessage by using the NSString bridges to CFString - private func addHeader(urlRequest: CFHTTPMessage, key: NSString, val: NSString) { - CFHTTPMessageSetHeaderFieldValue(urlRequest, key, val) + + /** + Add a header to the CFHTTPMessage by using the NSString bridges to CFString + */ + private func addHeader(_ urlRequest: CFHTTPMessage, key: String, val: String) { + CFHTTPMessageSetHeaderFieldValue(urlRequest, key as CFString, val as CFString) } - //generate a websocket key as needed in rfc + + /** + Generate a WebSocket key as needed in RFC. + */ private func generateWebSocketKey() -> String { var key = "" let seed = 16 for _ in 0..? var writeStream: Unmanaged? - let h: NSString = url.host! + let h = url.host! as NSString CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream) inputStream = readStream!.takeRetainedValue() outputStream = writeStream!.takeRetainedValue() guard let inStream = inputStream, let outStream = outputStream else { return } inStream.delegate = self outStream.delegate = self - if ["wss", "https"].contains(url.scheme) { - inStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) - outStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) + if supportedSSLSchemes.contains(url.scheme!) { + inStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey) + outStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey) } else { certValidated = true //not a https session, so no need to check SSL pinning } if voipEnabled { - inStream.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) - outStream.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) + inStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) + outStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) } - if selfSignedSSL { - let settings: [NSObject: NSObject] = [kCFStreamSSLValidatesCertificateChain: NSNumber(bool:false), kCFStreamSSLPeerName: kCFNull] - inStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) - outStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) + if disableSSLCertValidation { + let settings: [NSObject: NSObject] = [kCFStreamSSLValidatesCertificateChain: NSNumber(value: false), kCFStreamSSLPeerName: kCFNull] + inStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey) + outStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey) } if let cipherSuites = self.enabledSSLCipherSuites { - if let sslContextIn = CFReadStreamCopyProperty(inputStream, kCFStreamPropertySSLContext) as! SSLContextRef?, - sslContextOut = CFWriteStreamCopyProperty(outputStream, kCFStreamPropertySSLContext) as! SSLContextRef? { - let resIn = SSLSetEnabledCiphers(sslContextIn, cipherSuites, cipherSuites.count) - let resOut = SSLSetEnabledCiphers(sslContextOut, cipherSuites, cipherSuites.count) - if resIn != errSecSuccess { - let error = self.errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn)) - disconnectStream(error) - return - } - if resOut != errSecSuccess { - let error = self.errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut)) - disconnectStream(error) - return - } + if let sslContextIn = CFReadStreamCopyProperty(inputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext?, + let sslContextOut = CFWriteStreamCopyProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext? { + let resIn = SSLSetEnabledCiphers(sslContextIn, cipherSuites, cipherSuites.count) + let resOut = SSLSetEnabledCiphers(sslContextOut, cipherSuites, cipherSuites.count) + if resIn != errSecSuccess { + let error = self.errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn)) + disconnectStream(error) + return + } + if resOut != errSecSuccess { + let error = self.errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut)) + disconnectStream(error) + return + } } } - isRunLoop = true - inStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) - outStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + + CFReadStreamSetDispatchQueue(inStream, WebSocket.sharedWorkQueue) + CFWriteStreamSetDispatchQueue(outStream, WebSocket.sharedWorkQueue) inStream.open() outStream.open() - let bytes = UnsafePointer(data.bytes) - outStream.write(bytes, maxLength: data.length) - while(isRunLoop) { - NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture() as NSDate) - } - } - //delegate for the stream methods. Processes incoming bytes - public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { - if let sec = security where !certValidated && [.HasBytesAvailable, .HasSpaceAvailable].contains(eventCode) { - let possibleTrust: AnyObject? = aStream.propertyForKey(kCFStreamPropertySSLPeerTrust as String) - if let trust: AnyObject = possibleTrust { - let domain: AnyObject? = aStream.propertyForKey(kCFStreamSSLPeerName as String) - if sec.isValid(trust as! SecTrustRef, domain: domain as! String?) { - certValidated = true - } else { - let error = errorWithDetail("Invalid SSL certificate", code: 1) - disconnectStream(error) + self.mutex.lock() + self.readyToWrite = true + self.mutex.unlock() + + let bytes = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self) + var out = timeout * 1000000 // wait 5 seconds before giving up + writeQueue.addOperation { [weak self] in + while !outStream.hasSpaceAvailable { + usleep(100) // wait until the socket is ready + out -= 100 + if out < 0 { + self?.cleanupStream() + self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2)) return + } else if outStream.streamError != nil { + return // disconnectStream will be called. } } + outStream.write(bytes, maxLength: data.count) + } + } + + /** + Delegate for the stream methods. Processes incoming bytes + */ + public func stream(_ aStream: Stream, handle eventCode: Stream.Event) { + if let sec = security, !certValidated && [.hasBytesAvailable, .hasSpaceAvailable].contains(eventCode) { + let trust = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as AnyObject + let domain = aStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String + if sec.isValid(trust as! SecTrust, domain: domain) { + certValidated = true + } else { + let error = errorWithDetail("Invalid SSL certificate", code: 1) + disconnectStream(error) + return + } } - if eventCode == .HasBytesAvailable { + if eventCode == .hasBytesAvailable { if aStream == inputStream { processInputStream() } - } else if eventCode == .ErrorOccurred { - disconnectStream(aStream.streamError) - } else if eventCode == .EndEncountered { + } else if eventCode == .errorOccurred { + disconnectStream(aStream.streamError as NSError?) + } else if eventCode == .endEncountered { disconnectStream(nil) } } - //disconnect the stream object - private func disconnectStream(error: NSError?) { - writeQueue.waitUntilAllOperationsAreFinished() + + /** + Disconnect the stream object and notifies the delegate. + */ + private func disconnectStream(_ error: NSError?) { + if error == nil { + writeQueue.waitUntilAllOperationsAreFinished() + } else { + writeQueue.cancelAllOperations() + } + cleanupStream() + doDisconnect(error) + } + + /** + cleanup the streams. + */ + private func cleanupStream() { + outputStream?.delegate = nil + inputStream?.delegate = nil if let stream = inputStream { - stream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + CFReadStreamSetDispatchQueue(stream, nil) stream.close() } if let stream = outputStream { - stream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + CFWriteStreamSetDispatchQueue(stream, nil) stream.close() } outputStream = nil - isRunLoop = false - certValidated = false - doDisconnect(error) - connected = false + inputStream = nil } - ///handles the incoming bytes and sending them to the proper processing method + /** + Handles the incoming bytes and sending them to the proper processing method. + */ private func processInputStream() { let buf = NSMutableData(capacity: BUFFER_MAX) - let buffer = UnsafeMutablePointer(buf!.bytes) + let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self) let length = inputStream!.read(buffer, maxLength: BUFFER_MAX) guard length > 0 else { return } - - if !connected { - connected = processHTTP(buffer, bufferLen: length) + var process = false + if inputQueue.count == 0 { + process = true + } + inputQueue.append(Data(bytes: buffer, count: length)) + if process { + dequeueInput() + } + } + + /** + Dequeue the incoming input so it is processed in order. + */ + private func dequeueInput() { + while !inputQueue.isEmpty { + let data = inputQueue[0] + var work = data + if let fragBuffer = fragBuffer { + var combine = NSData(data: fragBuffer) as Data + combine.append(data) + work = combine + self.fragBuffer = nil + } + let buffer = UnsafeRawPointer((work as NSData).bytes).assumingMemoryBound(to: UInt8.self) + let length = work.count if !connected { - let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() - CFHTTPMessageAppendBytes(response, buffer, length) - let code = CFHTTPMessageGetResponseStatusCode(response) - doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code))) - } - } else { - var process = false - if inputQueue.count == 0 { - process = true - } - inputQueue.append(NSData(bytes: buffer, length: length)) - if process { - dequeueInput() + processTCPHandshake(buffer, bufferLen: length) + } else { + processRawMessagesInBuffer(buffer, bufferLen: length) } + inputQueue = inputQueue.filter{ $0 != data } } } - ///dequeue the incoming input so it is processed in order - private func dequeueInput() { - guard !inputQueue.isEmpty else { return } - - let data = inputQueue[0] - var work = data - if let fragBuffer = fragBuffer { - let combine = NSMutableData(data: fragBuffer) - combine.appendData(data) - work = combine - self.fragBuffer = nil - } - let buffer = UnsafePointer(work.bytes) - processRawMessage(buffer, bufferLen: work.length) - inputQueue = inputQueue.filter{$0 != data} - dequeueInput() - } - - ///Finds the HTTP Packet in the TCP stream, by looking for the CRLF. - private func processHTTP(buffer: UnsafePointer, bufferLen: Int) -> Bool { + + /** + Handle checking the inital connection status + */ + private func processTCPHandshake(_ buffer: UnsafePointer, bufferLen: Int) { + let code = processHTTP(buffer, bufferLen: bufferLen) + switch code { + case 0: + connected = true + guard canDispatch else {return} + callbackQueue.async { [weak self] in + guard let s = self else { return } + s.onConnect?() + s.delegate?.websocketDidConnect(socket: s) + s.notificationCenter.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self) + } + case -1: + fragBuffer = Data(bytes: buffer, count: bufferLen) + break // do nothing, we are going to collect more data + default: + doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code))) + } + } + + /** + Finds the HTTP Packet in the TCP stream, by looking for the CRLF. + */ + private func processHTTP(_ buffer: UnsafePointer, bufferLen: Int) -> Int { let CRLFBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")] var k = 0 var totalSize = 0 for i in 0.. 0 { - if validateResponse(buffer, bufferLen: totalSize) { - dispatch_async(queue) { [weak self] in - guard let s = self else { return } - s.onConnect?() - s.delegate?.websocketDidConnect(s) - } - totalSize += 1 //skip the last \n - let restSize = bufferLen - totalSize - if restSize > 0 { - processRawMessage((buffer+totalSize),bufferLen: restSize) - } - return true + let code = validateResponse(buffer, bufferLen: totalSize) + if code != 0 { + return code } + totalSize += 1 //skip the last \n + let restSize = bufferLen - totalSize + if restSize > 0 { + processRawMessagesInBuffer(buffer + totalSize, bufferLen: restSize) + } + return 0 //success } - return false + return -1 // Was unable to find the full TCP header. } - ///validates the HTTP is a 101 as per the RFC spec - private func validateResponse(buffer: UnsafePointer, bufferLen: Int) -> Bool { + /** + Validates the HTTP is a 101 as per the RFC spec. + */ + private func validateResponse(_ buffer: UnsafePointer, bufferLen: Int) -> Int { let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() CFHTTPMessageAppendBytes(response, buffer, bufferLen) - if CFHTTPMessageGetResponseStatusCode(response) != 101 { - return false + let code = CFHTTPMessageGetResponseStatusCode(response) + if code != httpSwitchProtocolCode { + return code } if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) { let headers = cfHeaders.takeRetainedValue() as NSDictionary - if let acceptKey = headers[headerWSAcceptName] as? NSString { + if let acceptKey = headers[headerWSAcceptName as NSString] as? NSString { if acceptKey.length > 0 { - return true + return 0 } } } - return false + return -1 } - ///read a 16 bit big endian value from a buffer - private static func readUint16(buffer: UnsafePointer, offset: Int) -> UInt16 { + /** + Read a 16 bit big endian value from a buffer + */ + private static func readUint16(_ buffer: UnsafePointer, offset: Int) -> UInt16 { return (UInt16(buffer[offset + 0]) << 8) | UInt16(buffer[offset + 1]) } - ///read a 64 bit big endian value from a buffer - private static func readUint64(buffer: UnsafePointer, offset: Int) -> UInt64 { + /** + Read a 64 bit big endian value from a buffer + */ + private static func readUint64(_ buffer: UnsafePointer, offset: Int) -> UInt64 { var value = UInt64(0) for i in 0...7 { value = (value << 8) | UInt64(buffer[offset + i]) @@ -449,27 +570,35 @@ public class WebSocket : NSObject, NSStreamDelegate { return value } - ///write a 16 bit big endian value to a buffer - private static func writeUint16(buffer: UnsafeMutablePointer, offset: Int, value: UInt16) { + /** + Write a 16-bit big endian value to a buffer. + */ + private static func writeUint16(_ buffer: UnsafeMutablePointer, offset: Int, value: UInt16) { buffer[offset + 0] = UInt8(value >> 8) buffer[offset + 1] = UInt8(value & 0xff) } - ///write a 64 bit big endian value to a buffer - private static func writeUint64(buffer: UnsafeMutablePointer, offset: Int, value: UInt64) { + /** + Write a 64-bit big endian value to a buffer. + */ + private static func writeUint64(_ buffer: UnsafeMutablePointer, offset: Int, value: UInt64) { for i in 0...7 { buffer[offset + i] = UInt8((value >> (8*UInt64(7 - i))) & 0xff) } } - ///process the websocket data - private func processRawMessage(buffer: UnsafePointer, bufferLen: Int) { + /** + Process one message at the start of `buffer`. Return another buffer (sharing storage) that contains the leftover contents of `buffer` that I didn't process. + */ + private func processOneRawMessage(inBuffer buffer: UnsafeBufferPointer) -> UnsafeBufferPointer { let response = readStack.last - if response != nil && bufferLen < 2 { - fragBuffer = NSData(bytes: buffer, length: bufferLen) - return + guard let baseAddress = buffer.baseAddress else {return emptyBuffer} + let bufferLen = buffer.count + if response != nil && bufferLen < 2 { + fragBuffer = Data(buffer: buffer) + return emptyBuffer } - if let response = response where response.bytesLeft > 0 { + if let response = response, response.bytesLeft > 0 { var len = response.bytesLeft var extra = bufferLen - response.bytesLeft if response.bytesLeft > bufferLen { @@ -477,123 +606,118 @@ public class WebSocket : NSObject, NSStreamDelegate { extra = 0 } response.bytesLeft -= len - response.buffer?.appendData(NSData(bytes: buffer, length: len)) - processResponse(response) - let offset = bufferLen - extra - if extra > 0 { - processExtra((buffer+offset), bufferLen: extra) - } - return + response.buffer?.append(Data(bytes: baseAddress, count: len)) + _ = processResponse(response) + return buffer.fromOffset(bufferLen - extra) } else { - let isFin = (FinMask & buffer[0]) - let receivedOpcode = OpCode(rawValue: (OpCodeMask & buffer[0])) - let isMasked = (MaskMask & buffer[1]) - let payloadLen = (PayloadLenMask & buffer[1]) + let isFin = (FinMask & baseAddress[0]) + let receivedOpcode = OpCode(rawValue: (OpCodeMask & baseAddress[0])) + let isMasked = (MaskMask & baseAddress[1]) + let payloadLen = (PayloadLenMask & baseAddress[1]) var offset = 2 - if (isMasked > 0 || (RSVMask & buffer[0]) > 0) && receivedOpcode != .Pong { - let errCode = CloseCode.ProtocolError.rawValue + if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong { + let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("masked and rsv data is not currently supported", code: errCode)) writeError(errCode) - return + return emptyBuffer } - let isControlFrame = (receivedOpcode == .ConnectionClose || receivedOpcode == .Ping) - if !isControlFrame && (receivedOpcode != .BinaryFrame && receivedOpcode != .ContinueFrame && - receivedOpcode != .TextFrame && receivedOpcode != .Pong) { - let errCode = CloseCode.ProtocolError.rawValue - doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode)) - writeError(errCode) - return + let isControlFrame = (receivedOpcode == .connectionClose || receivedOpcode == .ping) + if !isControlFrame && (receivedOpcode != .binaryFrame && receivedOpcode != .continueFrame && + receivedOpcode != .textFrame && receivedOpcode != .pong) { + let errCode = CloseCode.protocolError.rawValue + doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode)) + writeError(errCode) + return emptyBuffer } if isControlFrame && isFin == 0 { - let errCode = CloseCode.ProtocolError.rawValue + let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("control frames can't be fragmented", code: errCode)) writeError(errCode) - return + return emptyBuffer } - if receivedOpcode == .ConnectionClose { - var code = CloseCode.Normal.rawValue + if receivedOpcode == .connectionClose { + var code = CloseCode.normal.rawValue if payloadLen == 1 { - code = CloseCode.ProtocolError.rawValue + code = CloseCode.protocolError.rawValue } else if payloadLen > 1 { - code = WebSocket.readUint16(buffer, offset: offset) + code = WebSocket.readUint16(baseAddress, offset: offset) if code < 1000 || (code > 1003 && code < 1007) || (code > 1011 && code < 3000) { - code = CloseCode.ProtocolError.rawValue + code = CloseCode.protocolError.rawValue } offset += 2 } + var closeReason = "connection closed by server" if payloadLen > 2 { - let len = Int(payloadLen-2) + let len = Int(payloadLen - 2) if len > 0 { - let bytes = UnsafePointer((buffer+offset)) - let str: NSString? = NSString(data: NSData(bytes: bytes, length: len), encoding: NSUTF8StringEncoding) - if str == nil { - code = CloseCode.ProtocolError.rawValue + let bytes = baseAddress + offset + if let customCloseReason = String(data: Data(bytes: bytes, count: len), encoding: .utf8) { + closeReason = customCloseReason + } else { + code = CloseCode.protocolError.rawValue } } } - doDisconnect(errorWithDetail("connection closed by server", code: code)) + doDisconnect(errorWithDetail(closeReason, code: code)) writeError(code) - return + return emptyBuffer } if isControlFrame && payloadLen > 125 { - writeError(CloseCode.ProtocolError.rawValue) - return + writeError(CloseCode.protocolError.rawValue) + return emptyBuffer } var dataLength = UInt64(payloadLen) if dataLength == 127 { - dataLength = WebSocket.readUint64(buffer, offset: offset) - offset += sizeof(UInt64) + dataLength = WebSocket.readUint64(baseAddress, offset: offset) + offset += MemoryLayout.size } else if dataLength == 126 { - dataLength = UInt64(WebSocket.readUint16(buffer, offset: offset)) - offset += sizeof(UInt16) + dataLength = UInt64(WebSocket.readUint16(baseAddress, offset: offset)) + offset += MemoryLayout.size } if bufferLen < offset || UInt64(bufferLen - offset) < dataLength { - fragBuffer = NSData(bytes: buffer, length: bufferLen) - return + fragBuffer = Data(bytes: baseAddress, count: bufferLen) + return emptyBuffer } var len = dataLength if dataLength > UInt64(bufferLen) { len = UInt64(bufferLen-offset) } - var data: NSData! + let data: Data if len < 0 { len = 0 - data = NSData() + data = Data() } else { - data = NSData(bytes: UnsafePointer((buffer+offset)), length: Int(len)) - } - if receivedOpcode == .Pong { - dispatch_async(queue) { [weak self] in - guard let s = self else { return } - s.onPong?() - s.pongDelegate?.websocketDidReceivePong(s) - } - - let step = Int(offset+numericCast(len)) - let extra = bufferLen-step - if extra > 0 { - processRawMessage((buffer+step), bufferLen: extra) + data = Data(bytes: baseAddress+offset, count: Int(len)) + } + if receivedOpcode == .pong { + if canDispatch { + callbackQueue.async { [weak self] in + guard let s = self else { return } + let pongData: Data? = data.count > 0 ? data : nil + s.onPong?(pongData) + s.pongDelegate?.websocketDidReceivePong(socket: s, data: pongData) + } } - return + return buffer.fromOffset(offset + Int(len)) } var response = readStack.last if isControlFrame { - response = nil //don't append pings + response = nil // Don't append pings. } - if isFin == 0 && receivedOpcode == .ContinueFrame && response == nil { - let errCode = CloseCode.ProtocolError.rawValue + if isFin == 0 && receivedOpcode == .continueFrame && response == nil { + let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("continue frame before a binary or text frame", code: errCode)) writeError(errCode) - return + return emptyBuffer } var isNew = false if response == nil { - if receivedOpcode == .ContinueFrame { - let errCode = CloseCode.ProtocolError.rawValue + if receivedOpcode == .continueFrame { + let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("first frame can't be a continue frame", - code: errCode)) + code: errCode)) writeError(errCode) - return + return emptyBuffer } isNew = true response = WSResponse() @@ -601,69 +725,74 @@ public class WebSocket : NSObject, NSStreamDelegate { response!.bytesLeft = Int(dataLength) response!.buffer = NSMutableData(data: data) } else { - if receivedOpcode == .ContinueFrame { + if receivedOpcode == .continueFrame { response!.bytesLeft = Int(dataLength) } else { - let errCode = CloseCode.ProtocolError.rawValue + let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("second and beyond of fragment message must be a continue frame", - code: errCode)) + code: errCode)) writeError(errCode) - return + return emptyBuffer } - response!.buffer!.appendData(data) + response!.buffer!.append(data) } if let response = response { response.bytesLeft -= Int(len) - response.frameCount++ + response.frameCount += 1 response.isFin = isFin > 0 ? true : false if isNew { readStack.append(response) } - processResponse(response) + _ = processResponse(response) } - let step = Int(offset+numericCast(len)) - let extra = bufferLen-step - if extra > 0 { - processExtra((buffer+step), bufferLen: extra) - } + let step = Int(offset + numericCast(len)) + return buffer.fromOffset(step) } - } - ///process the extra of a buffer - private func processExtra(buffer: UnsafePointer, bufferLen: Int) { - if bufferLen < 2 { - fragBuffer = NSData(bytes: buffer, length: bufferLen) - } else { - processRawMessage(buffer, bufferLen: bufferLen) + /** + Process all messages in the buffer if possible. + */ + private func processRawMessagesInBuffer(_ pointer: UnsafePointer, bufferLen: Int) { + var buffer = UnsafeBufferPointer(start: pointer, count: bufferLen) + repeat { + buffer = processOneRawMessage(inBuffer: buffer) + } while buffer.count >= 2 + if buffer.count > 0 { + fragBuffer = Data(buffer: buffer) } } - ///process the finished response of a buffer - private func processResponse(response: WSResponse) -> Bool { + /** + Process the finished response of a buffer. + */ + private func processResponse(_ response: WSResponse) -> Bool { if response.isFin && response.bytesLeft <= 0 { - if response.code == .Ping { - let data = response.buffer! //local copy so it is perverse for writing - dequeueWrite(data, code: OpCode.Pong) - } else if response.code == .TextFrame { - let str: NSString? = NSString(data: response.buffer!, encoding: NSUTF8StringEncoding) + if response.code == .ping { + let data = response.buffer! // local copy so it is perverse for writing + dequeueWrite(data as Data, code: .pong) + } else if response.code == .textFrame { + let str: NSString? = NSString(data: response.buffer! as Data, encoding: String.Encoding.utf8.rawValue) if str == nil { - writeError(CloseCode.Encoding.rawValue) + writeError(CloseCode.encoding.rawValue) return false } - - dispatch_async(queue) { [weak self] in - guard let s = self else { return } - s.onText?(str! as String) - s.delegate?.websocketDidReceiveMessage(s, text: str! as String) + if canDispatch { + callbackQueue.async { [weak self] in + guard let s = self else { return } + s.onText?(str! as String) + s.delegate?.websocketDidReceiveMessage(socket: s, text: str! as String) + } } - } else if response.code == .BinaryFrame { - let data = response.buffer! //local copy so it is perverse for writing - dispatch_async(queue) { [weak self] in - guard let s = self else { return } - s.onData?(data) - s.delegate?.websocketDidReceiveData(s, data: data) + } else if response.code == .binaryFrame { + if canDispatch { + let data = response.buffer! // local copy so it is perverse for writing + callbackQueue.async { [weak self] in + guard let s = self else { return } + s.onData?(data as Data) + s.delegate?.websocketDidReceiveData(socket: s, data: data as Data) + } } } readStack.removeLast() @@ -672,75 +801,82 @@ public class WebSocket : NSObject, NSStreamDelegate { return false } - ///Create an error - private func errorWithDetail(detail: String, code: UInt16) -> NSError { + /** + Create an error + */ + private func errorWithDetail(_ detail: String, code: UInt16) -> NSError { var details = [String: String]() details[NSLocalizedDescriptionKey] = detail return NSError(domain: WebSocket.ErrorDomain, code: Int(code), userInfo: details) } - ///write a an error to the socket - private func writeError(code: UInt16) { - let buf = NSMutableData(capacity: sizeof(UInt16)) - let buffer = UnsafeMutablePointer(buf!.bytes) + /** + Write an error to the socket + */ + private func writeError(_ code: UInt16) { + let buf = NSMutableData(capacity: MemoryLayout.size) + let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self) WebSocket.writeUint16(buffer, offset: 0, value: code) - dequeueWrite(NSData(bytes: buffer, length: sizeof(UInt16)), code: .ConnectionClose) + dequeueWrite(Data(bytes: buffer, count: MemoryLayout.size), code: .connectionClose) } - ///used to write things to the stream - private func dequeueWrite(data: NSData, code: OpCode) { - guard isConnected else { return } - - writeQueue.addOperationWithBlock { [weak self] in + + /** + Used to write things to the stream + */ + private func dequeueWrite(_ data: Data, code: OpCode, writeCompletion: (() -> ())? = nil) { + writeQueue.addOperation { [weak self] in //stream isn't ready, let's wait guard let s = self else { return } var offset = 2 - let bytes = UnsafeMutablePointer(data.bytes) - let dataLength = data.length + let dataLength = data.count let frame = NSMutableData(capacity: dataLength + s.MaxFrameSize) - let buffer = UnsafeMutablePointer(frame!.mutableBytes) + let buffer = UnsafeMutableRawPointer(frame!.mutableBytes).assumingMemoryBound(to: UInt8.self) buffer[0] = s.FinMask | code.rawValue if dataLength < 126 { buffer[1] = CUnsignedChar(dataLength) } else if dataLength <= Int(UInt16.max) { buffer[1] = 126 WebSocket.writeUint16(buffer, offset: offset, value: UInt16(dataLength)) - offset += sizeof(UInt16) + offset += MemoryLayout.size } else { buffer[1] = 127 WebSocket.writeUint64(buffer, offset: offset, value: UInt64(dataLength)) - offset += sizeof(UInt64) + offset += MemoryLayout.size } buffer[1] |= s.MaskMask let maskKey = UnsafeMutablePointer(buffer + offset) - SecRandomCopyBytes(kSecRandomDefault, Int(sizeof(UInt32)), maskKey) - offset += sizeof(UInt32) + _ = SecRandomCopyBytes(kSecRandomDefault, Int(MemoryLayout.size), maskKey) + offset += MemoryLayout.size for i in 0...size] offset += 1 } var total = 0 while true { - if !s.isConnected { - break - } guard let outStream = s.outputStream else { break } - let writeBuffer = UnsafePointer(frame!.bytes+total) + let writeBuffer = UnsafeRawPointer(frame!.bytes+total).assumingMemoryBound(to: UInt8.self) let len = outStream.write(writeBuffer, maxLength: offset-total) if len < 0 { - var error: NSError? + var error: Error? if let streamError = outStream.streamError { error = streamError } else { - let errCode = InternalErrorCode.OutputStreamWriteError.rawValue + let errCode = InternalErrorCode.outputStreamWriteError.rawValue error = s.errorWithDetail("output stream error during write", code: errCode) } - s.doDisconnect(error) + s.doDisconnect(error as NSError?) break } else { total += len } if total >= offset { + if let queue = self?.callbackQueue, let callback = writeCompletion { + queue.async { + callback() + } + } + break } } @@ -748,245 +884,48 @@ public class WebSocket : NSObject, NSStreamDelegate { } } - ///used to preform the disconnect delegate - private func doDisconnect(error: NSError?) { + /** + Used to preform the disconnect delegate + */ + private func doDisconnect(_ error: NSError?) { guard !didDisconnect else { return } - - dispatch_async(queue) { [weak self] in + didDisconnect = true + connected = false + guard canDispatch else {return} + callbackQueue.async { [weak self] in guard let s = self else { return } - s.didDisconnect = true s.onDisconnect?(error) - s.delegate?.websocketDidDisconnect(s, error: error) + s.delegate?.websocketDidDisconnect(socket: s, error: error) + let userInfo = error.map{ [WebsocketDisconnectionErrorKeyName: $0] } + s.notificationCenter.post(name: NSNotification.Name(WebsocketDidDisconnectNotification), object: self, userInfo: userInfo) } } -} - -private class SSLCert { - var certData: NSData? - var key: SecKeyRef? + // MARK: - Deinit - /** - Designated init for certificates - - - parameter data: is the binary data of the certificate - - - returns: a representation security object to be used with - */ - init(data: NSData) { - self.certData = data + deinit { + mutex.lock() + readyToWrite = false + mutex.unlock() + cleanupStream() } - /** - Designated init for public keys - - - parameter key: is the public key to be used - - - returns: a representation security object to be used with - */ - init(key: SecKeyRef) { - self.key = key - } } -private class SSLSecurity { - var validatedDN = true //should the domain name be validated? - - var isReady = false //is the key processing done? - var certificates: [NSData]? //the certificates - var pubKeys: [SecKeyRef]? //the public keys - var usePublicKeys = false //use public keys or certificate validation? - - /** - Use certs from main app bundle - - - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation +private extension Data { - - returns: a representation security object to be used with - */ - convenience init(usePublicKeys: Bool = false) { - let paths = NSBundle.mainBundle().pathsForResourcesOfType("cer", inDirectory: ".") - - let certs = paths.reduce([SSLCert]()) { (var certs: [SSLCert], path: String) -> [SSLCert] in - if let data = NSData(contentsOfFile: path) { - certs.append(SSLCert(data: data)) - } - return certs - } - - self.init(certs: certs, usePublicKeys: usePublicKeys) + init(buffer: UnsafeBufferPointer) { + self.init(bytes: buffer.baseAddress!, count: buffer.count) } - /** - Designated init - - - parameter keys: is the certificates or public keys to use - - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation - - - returns: a representation security object to be used with - */ - init(certs: [SSLCert], usePublicKeys: Bool) { - self.usePublicKeys = usePublicKeys - - if self.usePublicKeys { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)) { - let pubKeys = certs.reduce([SecKeyRef]()) { (var pubKeys: [SecKeyRef], cert: SSLCert) -> [SecKeyRef] in - if let data = cert.certData where cert.key == nil { - cert.key = self.extractPublicKey(data) - } - if let key = cert.key { - pubKeys.append(key) - } - return pubKeys - } - - self.pubKeys = pubKeys - self.isReady = true - } - } else { - let certificates = certs.reduce([NSData]()) { (var certificates: [NSData], cert: SSLCert) -> [NSData] in - if let data = cert.certData { - certificates.append(data) - } - return certificates - } - self.certificates = certificates - self.isReady = true - } - } - - /** - Valid the trust and domain name. - - - parameter trust: is the serverTrust to validate - - parameter domain: is the CN domain to validate - - - returns: if the key was successfully validated - */ - func isValid(trust: SecTrustRef, domain: String?) -> Bool { - - var tries = 0 - while(!self.isReady) { - usleep(1000) - tries += 1 - if tries > 5 { - return false //doesn't appear it is going to ever be ready... - } - } - var policy: SecPolicyRef - if self.validatedDN { - policy = SecPolicyCreateSSL(true, domain) - } else { - policy = SecPolicyCreateBasicX509() - } - SecTrustSetPolicies(trust,policy) - if self.usePublicKeys { - if let keys = self.pubKeys { - let serverPubKeys = publicKeyChainForTrust(trust) - for serverKey in serverPubKeys as [AnyObject] { - for key in keys as [AnyObject] { - if serverKey.isEqual(key) { - return true - } - } - } - } - } else if let certs = self.certificates { - let serverCerts = certificateChainForTrust(trust) - var collect = [SecCertificate]() - for cert in certs { - collect.append(SecCertificateCreateWithData(nil,cert)!) - } - SecTrustSetAnchorCertificates(trust,collect) - var result: SecTrustResultType = 0 - SecTrustEvaluate(trust,&result) - let r = Int(result) - if r == kSecTrustResultUnspecified || r == kSecTrustResultProceed { - var trustedCount = 0 - for serverCert in serverCerts { - for cert in certs { - if cert == serverCert { - trustedCount++ - break - } - } - } - if trustedCount == serverCerts.count { - return true - } - } - } - return false - } - - /** - Get the public key from a certificate data - - - parameter data: is the certificate to pull the public key from - - - returns: a public key - */ - func extractPublicKey(data: NSData) -> SecKeyRef? { - guard let cert = SecCertificateCreateWithData(nil, data) else { return nil } - - return extractPublicKeyFromCert(cert, policy: SecPolicyCreateBasicX509()) - } - - /** - Get the public key from a certificate - - - parameter data: is the certificate to pull the public key from - - - returns: a public key - */ - func extractPublicKeyFromCert(cert: SecCertificate, policy: SecPolicy) -> SecKeyRef? { - var possibleTrust: SecTrust? - SecTrustCreateWithCertificates(cert, policy, &possibleTrust) - - guard let trust = possibleTrust else { return nil } - - var result: SecTrustResultType = 0 - SecTrustEvaluate(trust, &result) - return SecTrustCopyPublicKey(trust) - } - - /** - Get the certificate chain for the trust - - - parameter trust: is the trust to lookup the certificate chain for - - - returns: the certificate chain for the trust - */ - func certificateChainForTrust(trust: SecTrustRef) -> [NSData] { - let certificates = (0.. [NSData] in - let cert = SecTrustGetCertificateAtIndex(trust, index) - certificates.append(SecCertificateCopyData(cert!)) - return certificates - } - - return certificates - } +} + +private extension UnsafeBufferPointer { - /** - Get the public key chain for the trust - - - parameter trust: is the trust to lookup the certificate chain and extract the public keys - - - returns: the public keys from the certifcate chain for the trust - */ - func publicKeyChainForTrust(trust: SecTrustRef) -> [SecKeyRef] { - let policy = SecPolicyCreateBasicX509() - let keys = (0.. [SecKeyRef] in - let cert = SecTrustGetCertificateAtIndex(trust, index) - if let key = extractPublicKeyFromCert(cert!, policy: policy) { - keys.append(key) - } - - return keys - } - - return keys + func fromOffset(_ offset: Int) -> UnsafeBufferPointer { + return UnsafeBufferPointer(start: baseAddress?.advanced(by: offset), count: count - offset) } - -} \ No newline at end of file +} + +private let emptyBuffer = UnsafeBufferPointer(start: nil, count: 0)