diff --git a/.gitignore b/.gitignore index dcd8c16b..25daaa64 100644 --- a/.gitignore +++ b/.gitignore @@ -1,65 +1,68 @@ -.DS_Store -*.xcuserdatad - -# Created by https://www.gitignore.io/api/xcode,osx - -### OSX ### -*.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r +.DS_Store +*.xcuserdatad + +# Created by https://www.gitignore.io/api/xcode,osx + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Xcode ### -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -## Build generated -build/ -DerivedData/ - -## Various settings -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata/ - -## Other -*.moved-aside -*.xccheckout -*.xcscmblueprint - -### Xcode Patch ### -*.xcodeproj/* -!*.xcodeproj/project.pbxproj -!*.xcodeproj/xcshareddata/ -!*.xcworkspace/contents.xcworkspacedata -/*.gcno - -# End of https://www.gitignore.io/api/xcode,osx + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Xcode ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno + +# End of https://www.gitignore.io/api/xcode,osx + +Pods/ +*.xcworkspace/ diff --git a/README.md b/README.md index ba2752d5..9ee42b14 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,18 @@ NFX.sharedInstance().ignoreURL("the_url") ``` Tip: You can use the url of the host (for example "https://www.github.com") to ignore all paths of it +## Alternative to Charles - use netfox_mac app + +No need to mess around with Charles Proxy, SSL configurations in order to connect to your iPhone or iPad. We have created netfox_mac app which can connect to all apps (iOS or mac) that are running in local network. It will be easier for your QA person / team to know what API calls are made on device or if one of them has failed. + +Basically any iOS app that wants to expose it's network data to other netfox_mac app for inspection should start discovery service using: + +```swift +NFX.sharedInstance().startServer() +``` +After that netfox_mac app can connect using the same UI as in the mac app. + + ## Features - Search: You can easily search among requests via @@ -144,8 +156,26 @@ Tip: You can use the url of the host (for example "https://www.github.com") to i - Clear data within the app - Statistics: Check cool things like average response time, total response size and more for your selected types of responses - Info: Check your IP address, your app version and build number and other things within the app +- Live updates between apps running netfox with NFX.sharedInstance.startServer() and netfox_mac application +- Directory-like structure of requests +- Conversion from JSON to Codable class - More to come.. ;) +### netfox_mac app working in parallel with iOS app +![netfox Mac app demo](netfox_mac_demo.gif) + +If you want to use netfox with the netfox_mac application, you should start netfox in the following way: + +#### Swift +
+NFX.sharedInstance().startServer()
+
+ +#### Obj-C +
+[[NFX sharedInstance] startServer];
+
+ ## Integrations [Droar](https://github.com/myriadmobile/netfox-Droar): A modular, single-line installation debugging window. @@ -159,6 +189,8 @@ Tip: You can use the url of the host (for example "https://www.github.com") to i Special thanks to [tbaranes](https://github.com/tbaranes) and [vincedev](https://github.com/vincedev) for their contribution on OSX library! +Special thanks to [Tapptitude](https://tapptitude.com) team for their contribution on netfox_mac app! + ## Licence All source code is licensed under [MIT License](https://github.com/kasketis/netfox/blob/master/LICENSE). Which means you could do virtually anything with the code. I will appreciate it very much if you keep an attribution where appropriate. diff --git a/netfox.podspec b/netfox.podspec index 7035b842..f4754739 100644 --- a/netfox.podspec +++ b/netfox.podspec @@ -16,7 +16,10 @@ DESC s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.11' s.requires_arc = true - s.source_files = "netfox/Core/*.{swift,h,m}" - s.ios.source_files = "netfox/iOS/*.swift" - s.osx.source_files = "netfox/OSX/*.{swift,xib}" + s.source_files = "netfox/Core/**/*.{swift,h,m}" + s.ios.source_files = "netfox/iOS/**/*.swift" + s.osx.source_files = "netfox/OSX/**/*.{swift,xib}" + + # s.dependency "Swifter" + end \ No newline at end of file diff --git a/netfox.xcodeproj/project.pbxproj b/netfox.xcodeproj/project.pbxproj old mode 100644 new mode 100755 index fef6a3c9..3656a5a5 --- a/netfox.xcodeproj/project.pbxproj +++ b/netfox.xcodeproj/project.pbxproj @@ -7,6 +7,17 @@ objects = { /* Begin PBXBuildFile section */ + 234DBCD61FA33BD90086CB79 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 234DBCD51FA33BD90086CB79 /* AppDelegate.swift */; }; + 234DBCD81FA33BD90086CB79 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 234DBCD71FA33BD90086CB79 /* Assets.xcassets */; }; + 234DBCDB1FA33BD90086CB79 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 234DBCD91FA33BD90086CB79 /* MainMenu.xib */; }; + 234DBCE11FA33BFE0086CB79 /* netfox_osx.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E20FD2E91C6912D400DCFF61 /* netfox_osx.framework */; }; + 23580CD120401E400009C40C /* netfox_osx.framework in Copy Netfox framework */ = {isa = PBXBuildFile; fileRef = E20FD2E91C6912D400DCFF61 /* netfox_osx.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 23ABD6D01FA9C40000CD6186 /* NFXServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23ABD6CF1FA9C40000CD6186 /* NFXServer.swift */; }; + 23ABD6D11FA9C53F00CD6186 /* NFXServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23ABD6CF1FA9C40000CD6186 /* NFXServer.swift */; }; + 23ABD6D61FA9E08C00CD6186 /* NFXNetServiceMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23ABD6D51FA9E08C00CD6186 /* NFXNetServiceMonitor.swift */; }; + 23ABD6D71FA9E08C00CD6186 /* NFXNetServiceMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23ABD6D51FA9E08C00CD6186 /* NFXNetServiceMonitor.swift */; }; + 23B61B592022FF1F00048320 /* NFXClientConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23B61B582022FF1F00048320 /* NFXClientConnection.swift */; }; + 23B61B5A2022FF1F00048320 /* NFXClientConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23B61B582022FF1F00048320 /* NFXClientConnection.swift */; }; 026274D9214C6BC400AE1BDF /* WKWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026274D8214C6BC400AE1BDF /* WKWebViewController.swift */; }; 8201A39D204E3E3F00AB2C3D /* NFXAuthenticationChallengeSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8201A39C204E3E3F00AB2C3D /* NFXAuthenticationChallengeSender.swift */; }; 8201A39E204E451900AB2C3D /* NFXAuthenticationChallengeSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8201A39C204E3E3F00AB2C3D /* NFXAuthenticationChallengeSender.swift */; }; @@ -17,6 +28,17 @@ 8229AD6C1F8FB34300A9D613 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8229AD6B1F8FB34300A9D613 /* Assets.xcassets */; }; 8229AD6F1F8FB34300A9D613 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8229AD6D1F8FB34300A9D613 /* LaunchScreen.storyboard */; }; 8229AD771F8FB4B500A9D613 /* netfox_ios.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3BC020F1C09CDA000C17F3A /* netfox_ios.framework */; }; + 8F98A4DE202992FE007B2BB1 /* NFXPathNodeListCell_OSX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F98A4D9202992A2007B2BB1 /* NFXPathNodeListCell_OSX.swift */; }; + 8F98A4E12029944D007B2BB1 /* NFXPathNodeListCell_OSX.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8F98A4DF20299399007B2BB1 /* NFXPathNodeListCell_OSX.xib */; }; + 8F98A4E3202994CB007B2BB1 /* NFXPathNodeListController_OSX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F98A4E2202994CB007B2BB1 /* NFXPathNodeListController_OSX.swift */; }; + 8F9DEE9120331F3C00E76B7E /* NFXJson2Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FBFFB4C20331DD000DF9319 /* NFXJson2Codable.swift */; }; + 8F9DEE942033391800E76B7E /* NFXJsonParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F9DEE932033391800E76B7E /* NFXJsonParser.swift */; }; + 8F9DEE962033393800E76B7E /* NFXCodableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F9DEE952033393800E76B7E /* NFXCodableClass.swift */; }; + 8F9DEE982033398500E76B7E /* String+CamelCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F9DEE972033398500E76B7E /* String+CamelCase.swift */; }; + 8FBC1A8B2028ACEB00ABDF22 /* NFXPathNodeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FBC1A8A2028ACEB00ABDF22 /* NFXPathNodeManager.swift */; }; + 8FBC1A8E2028AF2A00ABDF22 /* NFXPathNodeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FBC1A8A2028ACEB00ABDF22 /* NFXPathNodeManager.swift */; }; + 8FD42BA62028A2D20084211A /* NFXPathNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FD42BA52028A2D20084211A /* NFXPathNode.swift */; }; + 8FD42BA72028A3BB0084211A /* NFXPathNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FD42BA52028A2D20084211A /* NFXPathNode.swift */; }; 826C4E9E1F979AB3008B440C /* NFXLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 826C4E9C1F979AB3008B440C /* NFXLoader.h */; }; 826C4E9F1F979AB3008B440C /* NFXLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 826C4E9D1F979AB3008B440C /* NFXLoader.m */; }; 82F6E1031F8FD81C002B31BD /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F6E1021F8FD81C002B31BD /* TextViewController.swift */; }; @@ -75,6 +97,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 234DBCE21FA33D980086CB79 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B3BC02061C09CDA000C17F3A /* Project object */; + proxyType = 1; + remoteGlobalIDString = E20FD2E81C6912D400DCFF61; + remoteInfo = netfox_osx; + }; 8229AD741F8FB37000A9D613 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B3BC02061C09CDA000C17F3A /* Project object */; @@ -84,7 +113,30 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 23580CD020401E2E0009C40C /* Copy Netfox framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 23580CD120401E400009C40C /* netfox_osx.framework in Copy Netfox framework */, + ); + name = "Copy Netfox framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 234DBCD31FA33BD90086CB79 /* netfox_mac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = netfox_mac.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 234DBCD51FA33BD90086CB79 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 234DBCD71FA33BD90086CB79 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 234DBCDA1FA33BD90086CB79 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 234DBCDC1FA33BD90086CB79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 234DBCDD1FA33BD90086CB79 /* netfox_mac.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = netfox_mac.entitlements; sourceTree = ""; }; + 23ABD6CF1FA9C40000CD6186 /* NFXServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFXServer.swift; sourceTree = ""; }; + 23ABD6D51FA9E08C00CD6186 /* NFXNetServiceMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NFXNetServiceMonitor.swift; sourceTree = ""; }; + 23B61B582022FF1F00048320 /* NFXClientConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFXClientConnection.swift; sourceTree = ""; }; 026274D8214C6BC400AE1BDF /* WKWebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKWebViewController.swift; sourceTree = ""; }; 8201A39C204E3E3F00AB2C3D /* NFXAuthenticationChallengeSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFXAuthenticationChallengeSender.swift; sourceTree = ""; }; 8229AD621F8FB34300A9D613 /* netfox_ios_demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = netfox_ios_demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -92,6 +144,15 @@ 8229AD661F8FB34300A9D613 /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; 8229AD691F8FB34300A9D613 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 8229AD6B1F8FB34300A9D613 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8F98A4D9202992A2007B2BB1 /* NFXPathNodeListCell_OSX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFXPathNodeListCell_OSX.swift; sourceTree = ""; }; + 8F98A4DF20299399007B2BB1 /* NFXPathNodeListCell_OSX.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NFXPathNodeListCell_OSX.xib; sourceTree = ""; }; + 8F98A4E2202994CB007B2BB1 /* NFXPathNodeListController_OSX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFXPathNodeListController_OSX.swift; sourceTree = ""; }; + 8F9DEE932033391800E76B7E /* NFXJsonParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFXJsonParser.swift; sourceTree = ""; }; + 8F9DEE952033393800E76B7E /* NFXCodableClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFXCodableClass.swift; sourceTree = ""; }; + 8F9DEE972033398500E76B7E /* String+CamelCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+CamelCase.swift"; sourceTree = ""; }; + 8FBC1A8A2028ACEB00ABDF22 /* NFXPathNodeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFXPathNodeManager.swift; sourceTree = ""; }; + 8FBFFB4C20331DD000DF9319 /* NFXJson2Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFXJson2Codable.swift; sourceTree = ""; }; + 8FD42BA52028A2D20084211A /* NFXPathNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFXPathNode.swift; sourceTree = ""; }; 8229AD6E1F8FB34300A9D613 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 8229AD701F8FB34300A9D613 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 826C4E9C1F979AB3008B440C /* NFXLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NFXLoader.h; sourceTree = ""; }; @@ -137,6 +198,14 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 234DBCD01FA33BD90086CB79 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 234DBCE11FA33BFE0086CB79 /* netfox_osx.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8229AD5F1F8FB34300A9D613 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -162,6 +231,29 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 234DBCD41FA33BD90086CB79 /* netfox_mac */ = { + isa = PBXGroup; + children = ( + 234DBCD51FA33BD90086CB79 /* AppDelegate.swift */, + 234DBCD71FA33BD90086CB79 /* Assets.xcassets */, + 234DBCD91FA33BD90086CB79 /* MainMenu.xib */, + 234DBCDC1FA33BD90086CB79 /* Info.plist */, + 234DBCDD1FA33BD90086CB79 /* netfox_mac.entitlements */, + ); + path = netfox_mac; + sourceTree = ""; + }; + 8F9DEE92203338F700E76B7E /* Json2Codable */ = { + isa = PBXGroup; + children = ( + 8F9DEE952033393800E76B7E /* NFXCodableClass.swift */, + 8FBFFB4C20331DD000DF9319 /* NFXJson2Codable.swift */, + 8F9DEE932033391800E76B7E /* NFXJsonParser.swift */, + 8F9DEE972033398500E76B7E /* String+CamelCase.swift */, + ); + path = Json2Codable; + sourceTree = ""; + }; 8229AD631F8FB34300A9D613 /* netfox_ios_demo */ = { isa = PBXGroup; children = ( @@ -190,6 +282,7 @@ children = ( CEFE42B41C0DDBD8001A56E0 /* netfox */, 8229AD631F8FB34300A9D613 /* netfox_ios_demo */, + 234DBCD41FA33BD90086CB79 /* netfox_mac */, B3BC02101C09CDA000C17F3A /* Products */, 8229AD761F8FB4B500A9D613 /* Frameworks */, ); @@ -201,6 +294,7 @@ B3BC020F1C09CDA000C17F3A /* netfox_ios.framework */, E20FD2E91C6912D400DCFF61 /* netfox_osx.framework */, 8229AD621F8FB34300A9D613 /* netfox_ios_demo.app */, + 234DBCD31FA33BD90086CB79 /* netfox_mac.app */, ); name = Products; sourceTree = ""; @@ -227,6 +321,12 @@ B3F8BA7E1C833ABC00F9FBEA /* NFXRawBodyDetailsController.swift */, B3F8BA7F1C833ABC00F9FBEA /* NFXSettingsController.swift */, B3F8BA801C833ABC00F9FBEA /* NFXStatisticsController.swift */, + 23ABD6CF1FA9C40000CD6186 /* NFXServer.swift */, + 23ABD6D51FA9E08C00CD6186 /* NFXNetServiceMonitor.swift */, + 23B61B582022FF1F00048320 /* NFXClientConnection.swift */, + 8FD42BA52028A2D20084211A /* NFXPathNode.swift */, + 8FBC1A8A2028ACEB00ABDF22 /* NFXPathNodeManager.swift */, + 8F9DEE92203338F700E76B7E /* Json2Codable */, B3F8BA811C833ABC00F9FBEA /* NFXWindowController.swift */, ); path = Core; @@ -259,6 +359,9 @@ B3F8D6781C833B1700F9FBEA /* NFXResponseTypeCell_OSX.xib */, B3F8D6791C833B1700F9FBEA /* NFXSettingsController_OSX.swift */, B3F8D67A1C833B1700F9FBEA /* NFXStatisticsController_OSX.swift */, + 8F98A4D9202992A2007B2BB1 /* NFXPathNodeListCell_OSX.swift */, + 8F98A4DF20299399007B2BB1 /* NFXPathNodeListCell_OSX.xib */, + 8F98A4E2202994CB007B2BB1 /* NFXPathNodeListController_OSX.swift */, ); path = OSX; sourceTree = ""; @@ -294,6 +397,25 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 234DBCD21FA33BD90086CB79 /* netfox_mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = 234DBCE01FA33BD90086CB79 /* Build configuration list for PBXNativeTarget "netfox_mac" */; + buildPhases = ( + 234DBCCF1FA33BD90086CB79 /* Sources */, + 234DBCD01FA33BD90086CB79 /* Frameworks */, + 234DBCD11FA33BD90086CB79 /* Resources */, + 23580CD020401E2E0009C40C /* Copy Netfox framework */, + ); + buildRules = ( + ); + dependencies = ( + 234DBCE31FA33D980086CB79 /* PBXTargetDependency */, + ); + name = netfox_mac; + productName = netfox_mac; + productReference = 234DBCD31FA33BD90086CB79 /* netfox_mac.app */; + productType = "com.apple.product-type.application"; + }; 8229AD611F8FB34300A9D613 /* netfox_ios_demo */ = { isa = PBXNativeTarget; buildConfigurationList = 8229AD711F8FB34300A9D613 /* Build configuration list for PBXNativeTarget "netfox_ios_demo" */; @@ -354,10 +476,14 @@ B3BC02061C09CDA000C17F3A /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0720; + LastSwiftUpdateCheck = 0900; LastUpgradeCheck = 0930; ORGANIZATIONNAME = kasketis; TargetAttributes = { + 234DBCD21FA33BD90086CB79 = { + CreatedOnToolsVersion = 9.0.1; + ProvisioningStyle = Automatic; + }; 8229AD611F8FB34300A9D613 = { CreatedOnToolsVersion = 9.0; ProvisioningStyle = Automatic; @@ -372,7 +498,7 @@ }; }; }; - buildConfigurationList = B3BC02091C09CDA000C17F3A /* Build configuration list for PBXProject "netfox" */; + buildConfigurationList = B3BC02091C09CDA000C17F3A /* Build configuration list for PBXProject "netfox_ios" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; @@ -388,11 +514,21 @@ B3BC020E1C09CDA000C17F3A /* netfox_ios */, E20FD2E81C6912D400DCFF61 /* netfox_osx */, 8229AD611F8FB34300A9D613 /* netfox_ios_demo */, + 234DBCD21FA33BD90086CB79 /* netfox_mac */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 234DBCD11FA33BD90086CB79 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 234DBCD81FA33BD90086CB79 /* Assets.xcassets in Resources */, + 234DBCDB1FA33BD90086CB79 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8229AD601F8FB34300A9D613 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -414,6 +550,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8F98A4E12029944D007B2BB1 /* NFXPathNodeListCell_OSX.xib in Resources */, B3F8D6821C833B1700F9FBEA /* NFXResponseTypeCell_OSX.xib in Resources */, B3F8D67B1C833B1700F9FBEA /* NetfoxWindow.xib in Resources */, B3F8D67F1C833B1700F9FBEA /* NFXListCell_OSX.xib in Resources */, @@ -423,6 +560,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 234DBCCF1FA33BD90086CB79 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 234DBCD61FA33BD90086CB79 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8229AD5E1F8FB34300A9D613 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -440,6 +585,7 @@ buildActionMask = 2147483647; files = ( B3F8BA821C833ABC00F9FBEA /* NFX.swift in Sources */, + 8FD42BA62028A2D20084211A /* NFXPathNode.swift in Sources */, B3F8BA8A1C833ABC00F9FBEA /* NFXGenericBodyDetailsController.swift in Sources */, B3F8BA901C833ABC00F9FBEA /* NFXHTTPModel.swift in Sources */, 8201A39D204E3E3F00AB2C3D /* NFXAuthenticationChallengeSender.swift in Sources */, @@ -448,8 +594,11 @@ B3F8BAB11C833AC700F9FBEA /* NFXSettingsController_iOS.swift in Sources */, B3F8BAB01C833AC700F9FBEA /* NFXListController_iOS.swift in Sources */, B3F8BA8E1C833ABC00F9FBEA /* NFXHelper.swift in Sources */, + 23ABD6D01FA9C40000CD6186 /* NFXServer.swift in Sources */, B3F8BA861C833ABC00F9FBEA /* NFXConstants.swift in Sources */, + 23B61B592022FF1F00048320 /* NFXClientConnection.swift in Sources */, B3F8BAAD1C833AC700F9FBEA /* NFXHelper_iOS.swift in Sources */, + 23ABD6D61FA9E08C00CD6186 /* NFXNetServiceMonitor.swift in Sources */, B3F8BA9A1C833ABC00F9FBEA /* NFXProtocol.swift in Sources */, B3F8BA941C833ABC00F9FBEA /* NFXImageBodyDetailsController.swift in Sources */, B3F8BA9E1C833ABC00F9FBEA /* NFXSettingsController.swift in Sources */, @@ -458,6 +607,7 @@ 826C4E9F1F979AB3008B440C /* NFXLoader.m in Sources */, B3F8BA8C1C833ABC00F9FBEA /* NFXGenericController.swift in Sources */, B3F8BA921C833ABC00F9FBEA /* NFXHTTPModelManager.swift in Sources */, + 8FBC1A8B2028ACEB00ABDF22 /* NFXPathNodeManager.swift in Sources */, B3F8BA881C833ABC00F9FBEA /* NFXDetailsController.swift in Sources */, B3F8BA981C833ABC00F9FBEA /* NFXListController.swift in Sources */, B3F8BAAE1C833AC700F9FBEA /* NFXInfoController_iOS.swift in Sources */, @@ -481,21 +631,32 @@ B3F8BAA11C833ABC00F9FBEA /* NFXStatisticsController.swift in Sources */, B3F8D67D1C833B1700F9FBEA /* NFXInfoController_OSX.swift in Sources */, B3F8D6831C833B1700F9FBEA /* NFXSettingsController_OSX.swift in Sources */, + 8FBC1A8E2028AF2A00ABDF22 /* NFXPathNodeManager.swift in Sources */, B3F8D67C1C833B1700F9FBEA /* NFXDetailsController_OSX.swift in Sources */, B3F8BA8F1C833ABC00F9FBEA /* NFXHelper.swift in Sources */, B3F8BA871C833ABC00F9FBEA /* NFXConstants.swift in Sources */, + 8F98A4E3202994CB007B2BB1 /* NFXPathNodeListController_OSX.swift in Sources */, + 8F9DEE962033393800E76B7E /* NFXCodableClass.swift in Sources */, B3F8BA9B1C833ABC00F9FBEA /* NFXProtocol.swift in Sources */, B3F8D6811C833B1700F9FBEA /* NFXResponseTypeCell_OSX.swift in Sources */, + 23B61B5A2022FF1F00048320 /* NFXClientConnection.swift in Sources */, B3F8BA951C833ABC00F9FBEA /* NFXImageBodyDetailsController.swift in Sources */, + 8F9DEE9120331F3C00E76B7E /* NFXJson2Codable.swift in Sources */, B3F8BA9F1C833ABC00F9FBEA /* NFXSettingsController.swift in Sources */, B3F8BAA31C833ABC00F9FBEA /* NFXWindowController.swift in Sources */, B3F8BA851C833ABC00F9FBEA /* NFXAssets.swift in Sources */, + 8F98A4DE202992FE007B2BB1 /* NFXPathNodeListCell_OSX.swift in Sources */, + 8F9DEE942033391800E76B7E /* NFXJsonParser.swift in Sources */, B3F8D6841C833B1700F9FBEA /* NFXStatisticsController_OSX.swift in Sources */, B3F8BA8D1C833ABC00F9FBEA /* NFXGenericController.swift in Sources */, + 23ABD6D71FA9E08C00CD6186 /* NFXNetServiceMonitor.swift in Sources */, + 8F9DEE982033398500E76B7E /* String+CamelCase.swift in Sources */, B3F8BA931C833ABC00F9FBEA /* NFXHTTPModelManager.swift in Sources */, B3F8BA891C833ABC00F9FBEA /* NFXDetailsController.swift in Sources */, + 23ABD6D11FA9C53F00CD6186 /* NFXServer.swift in Sources */, B3F8BA991C833ABC00F9FBEA /* NFXListController.swift in Sources */, B3F8BA9D1C833ABC00F9FBEA /* NFXRawBodyDetailsController.swift in Sources */, + 8FD42BA72028A3BB0084211A /* NFXPathNode.swift in Sources */, B3F8D6801C833B1700F9FBEA /* NFXListController_OSX.swift in Sources */, B3F8BA971C833ABC00F9FBEA /* NFXInfoController.swift in Sources */, ); @@ -504,6 +665,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 234DBCE31FA33D980086CB79 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E20FD2E81C6912D400DCFF61 /* netfox_osx */; + targetProxy = 234DBCE21FA33D980086CB79 /* PBXContainerItemProxy */; + }; 8229AD751F8FB37000A9D613 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B3BC020E1C09CDA000C17F3A /* netfox_ios */; @@ -512,6 +678,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 234DBCD91FA33BD90086CB79 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 234DBCDA1FA33BD90086CB79 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; 8229AD681F8FB34300A9D613 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -531,6 +705,55 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 234DBCDE1FA33BD90086CB79 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = netfox_mac/netfox_mac.entitlements; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = netfox_mac/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = "com.tapptitude.netfox-mac"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + 234DBCDF1FA33BD90086CB79 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = netfox_mac/netfox_mac.entitlements; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = netfox_mac/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = "com.tapptitude.netfox-mac"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_VERSION = 4.0; + }; + name = Release; + }; 8229AD721F8FB34300A9D613 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -795,6 +1018,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 234DBCE01FA33BD90086CB79 /* Build configuration list for PBXNativeTarget "netfox_mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 234DBCDE1FA33BD90086CB79 /* Debug */, + 234DBCDF1FA33BD90086CB79 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 8229AD711F8FB34300A9D613 /* Build configuration list for PBXNativeTarget "netfox_ios_demo" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -804,7 +1036,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B3BC02091C09CDA000C17F3A /* Build configuration list for PBXProject "netfox" */ = { + B3BC02091C09CDA000C17F3A /* Build configuration list for PBXProject "netfox_ios" */ = { isa = XCConfigurationList; buildConfigurations = ( B3BC02151C09CDA000C17F3A /* Debug */, diff --git a/netfox.xcodeproj/xcshareddata/xcschemes/netfox_ios.xcscheme b/netfox.xcodeproj/xcshareddata/xcschemes/netfox_ios.xcscheme index c1d296c8..6bd7d213 100644 --- a/netfox.xcodeproj/xcshareddata/xcschemes/netfox_ios.xcscheme +++ b/netfox.xcodeproj/xcshareddata/xcschemes/netfox_ios.xcscheme @@ -1,6 +1,6 @@ @@ -36,6 +37,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/netfox.xcodeproj/xcshareddata/xcschemes/netfox_osx.xcscheme b/netfox.xcodeproj/xcshareddata/xcschemes/netfox_osx.xcscheme index 964be81b..90ad5e4f 100644 --- a/netfox.xcodeproj/xcshareddata/xcschemes/netfox_osx.xcscheme +++ b/netfox.xcodeproj/xcshareddata/xcschemes/netfox_osx.xcscheme @@ -1,6 +1,6 @@ @@ -36,6 +37,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/netfox/Core/Json2Codable/NFXCodableClass.swift b/netfox/Core/Json2Codable/NFXCodableClass.swift new file mode 100644 index 00000000..14203232 --- /dev/null +++ b/netfox/Core/Json2Codable/NFXCodableClass.swift @@ -0,0 +1,156 @@ +// +// NFXCodableClass.swift +// netfox_osx +// +// Created by Ștefan Suciu on 2/13/18. +// Copyright © 2018 kasketis. All rights reserved. +// + +import Foundation + + +fileprivate class NFXCodableProperty: CustomStringConvertible { + + var name: String + var type: String + var values: [AnyHashable] + var isOptional: Bool + + init(name: String, type: String, isOptional: Bool, value: AnyHashable? = nil) { + self.name = name + self.type = type + self.isOptional = isOptional + self.values = value != nil ? [value!] : [] + } + + fileprivate func getName() -> String { + return name.camelCasedString + } + + fileprivate func getType() -> String { + return type.camelCasedString.uppercaseFirstLetter().singular + } + + fileprivate func getOptional() -> String { + return isOptional ? "?" : "!" + } + + var description: String { + return "\tvar \(getName()): \(getType())\(getOptional())" + } + + var codingKeyDescription: String { + return "\t\tcase \(getName()) = \"\(name)\"" + } +} + + +fileprivate struct NFXCodableEnum: CustomStringConvertible { + + var name: String + var type: String + var cases: [AnyHashable] + + fileprivate func getName() -> String { + return name.camelCasedString.uppercaseFirstLetter().singular + } + + fileprivate func getType() -> String { + return type.camelCasedString.uppercaseFirstLetter().singular + } + + init(name: String, type: String, cases: [AnyHashable]) { + self.name = name + self.type = type + self.cases = cases + } + + var description: String { + var str = "enum \(getName()): \(getType()), Codable {\n" + + if type == "Int" { + str += cases.map{ "\n\tcase type\($0) = \($0)" }.joined() + } else { + str += cases.map{ "\n\tcase \($0) = \"\($0)\"" }.joined() + } + + str += "\n}\n\n" + return str + } +} + + +class NFXCodableClass: CustomStringConvertible { + + var className: String + fileprivate var properties: [NFXCodableProperty] = [] + fileprivate var enums: [NFXCodableEnum] = [] + + init(className: String) { + self.className = className + } + + fileprivate func getClassName(_ string: String) -> String { + return string.camelCasedString.uppercaseFirstLetter().singular + } + + func addProperty(name: String, type: String, value: AnyHashable? = nil) { + if let property = properties.first(where: { $0.name == name }) { + if property.type.contains("Any") && !type.contains("Any") { + property.type = type + } else if type == "Any" { + property.isOptional = true + } + if let value = value { + property.values.append(value) + } + } else { + let property = NFXCodableProperty(name: name, type: type, isOptional: type == "Any", value: value) + properties.append(property) + } + } + + func detectEnums() { + properties.filter{ !$0.name.contains("id") && ($0.type == "Int" || $0.type == "String") && !$0.values.isEmpty } + .forEach { (property: NFXCodableProperty) in + let uniqueValues = Array(Set(property.values)) + + // need a better decision mechanism + if Double(uniqueValues.count) / Double(property.values.count) < 0.5 { + let codableEnum = NFXCodableEnum(name: property.name, type: property.type, cases: uniqueValues) + enums.append(codableEnum) + property.type = property.name + } + } + } + + var description: String { + return properties.isEmpty ? className : classDescription + } + + fileprivate var classDescription: String { + var str = enumsDescription + str += "class \(getClassName(className)): Codable {\n\n" + str += properties.map{ $0.description }.joined(separator: "\n") + str += codingKeysDescription + str += "\n}\n\n" + return str + } + + fileprivate var codingKeysDescription: String { + var str = "" + + let filteredProperties = properties.filter{ $0.getName() != $0.name } + if !filteredProperties.isEmpty { + str += "\n\n\tenum CodingKeys: String, CodingKey {\n" + str += filteredProperties.map{ $0.codingKeyDescription }.joined(separator: "\n") + str += "\n\t}" + } + + return str + } + + fileprivate var enumsDescription: String { + return enums.map{ $0.description }.joined() + } +} diff --git a/netfox/Core/Json2Codable/NFXJson2Codable.swift b/netfox/Core/Json2Codable/NFXJson2Codable.swift new file mode 100644 index 00000000..c617a814 --- /dev/null +++ b/netfox/Core/Json2Codable/NFXJson2Codable.swift @@ -0,0 +1,134 @@ +// +// NFXJson2Codable.swift +// netfox_ios +// +// Created by Ștefan Suciu on 2/13/18. +// Copyright © 2018 kasketis. All rights reserved. +// + +import Foundation + +public class NFXJson2Codable { + + private let boolParser = NFXJsonParser() + private let intParser = NFXJsonParser() + private let doubleParser = NFXJsonParser() + private let stringParser = NFXJsonParser() + private let dictionaryParser = NFXJsonParser<[String: Any]>() + private let arrayParser = NFXJsonParser<[Any]>() + private let urlParser = NFXJsonParser() + private let dateParser = NFXJsonParser() + + private var codableClasses: [NFXCodableClass] = [] + + func convertToCodable(name: String, from array: [Any]) -> NFXCodableClass { + if let item = array.first, dictionaryParser.canParse(item) { + return array.map{ convertToCodable(name: name, from: dictionaryParser.parse($0)) }.first! + } + + return getCodableClass(name: convertToProperty(key: name, value: array.first!)) + } + + func convertToCodable(name: String, from dictionary: [String: Any]) -> NFXCodableClass { + let codableClass = getCodableClass(name: name) + + dictionary.keys.forEach { key in + let type = convertToProperty(key: key, value: dictionary[key]!) + if type == "Int" || type == "String" { + codableClass.addProperty(name: key, type: type, value: dictionary[key]! as? AnyHashable) + } else { + codableClass.addProperty(name: key, type: type) + } + } + + return codableClass + } + + private func convertToProperty(key: String, value: Any) -> String { + if boolParser.canParse(value) { + return boolParser.getPropertyType() + } + + if intParser.canParse(value) { + if dateParser.canParse(key: key, value: intParser.parse(value)) { + return dateParser.getPropertyType() + } + + return intParser.getPropertyType() + } + + if doubleParser.canParse(value) { + return doubleParser.getPropertyType() + } + + if stringParser.canParse(value) { + let stringValue = stringParser.parse(value) + + if urlParser.canParse(stringValue) { + return urlParser.getPropertyType() + } + + if dateParser.canParse(stringValue) { + return dateParser.getPropertyType() + } + + return stringParser.getPropertyType() + } + + if dictionaryParser.canParse(value) { + let dictionary = dictionaryParser.parse(value) + + if let _ = dictionary.first { + let _ = convertToCodable(name: key, from: dictionary) + return dictionaryParser.getPropertyType(name: key) + } else { + return "Any" + } + } + + if arrayParser.canParse(value) { + let array = arrayParser.parse(value) + + if let _ = array.first { + let codableClass = convertToCodable(name: key, from: array) + return "[\(codableClass.className)]" + } else { + return "[Any]" + } + } + + return "Any" + } + + private func getCodableClass(name: String) -> NFXCodableClass { + var codableClass: NFXCodableClass + + if let foundClass = codableClasses.first(where: { $0.className == name }) { + codableClass = foundClass + } else { + codableClass = NFXCodableClass(className: name) + codableClasses.append(codableClass) + } + + return codableClass + } + + func getResourceName(from url: String?) -> String { + guard var url = url else { + return "ClassName" + } + + url = String(url.split(separator: "?").first!) + + var components = url.split(separator: "/") + if let _ = Int(components.last ?? "") { + components = Array(components.dropLast()) + } + + return String(components.last ?? "") + } + + func printClasses() -> String { + return codableClasses.map{ $0.description }.joined() + } +} diff --git a/netfox/Core/Json2Codable/NFXJsonParser.swift b/netfox/Core/Json2Codable/NFXJsonParser.swift new file mode 100644 index 00000000..c21fa6a4 --- /dev/null +++ b/netfox/Core/Json2Codable/NFXJsonParser.swift @@ -0,0 +1,71 @@ +// +// NFXJsonParser.swift +// netfox_osx +// +// Created by Ștefan Suciu on 2/13/18. +// Copyright © 2018 kasketis. All rights reserved. +// + +import Foundation + + +class NFXJsonParser { + + func canParse(_ value: Any) -> Bool { + return value is T + } + + func parse(_ value: Any) -> T { + return value as! T + } + + func getPropertyType() -> String { + return "\(T.self)" + } +} + +extension NFXJsonParser where T == [String: Any] { + + func getPropertyType(name: String) -> String { + return "\(name)" + } +} + +extension NFXJsonParser where T == URL { + + func canParse(_ value: Any) -> Bool { + let stringValue = value as! String + return stringValue.hasPrefix("https://") || stringValue.hasPrefix("http://") + } +} + +extension NFXJsonParser where T == Date { + + private var keys: [String] { + return ["_at", "date", "day"] + } + + func canParse(_ value: String) -> Bool { + let dateFormats = ["yyyy-MM-dd HH:mm:ss.ZZZZZ", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ssZZZZZZ"] + let dateFormatter = DateFormatter() + + dateFormatter.dateFormat = dateFormats.first! + let accumulator = dateFormatter.date(from: value) != nil + + return dateFormats.dropFirst().map{ + dateFormatter.dateFormat = $0 + return dateFormatter.date(from: value) != nil + }.reduce(accumulator, { $0 || $1 }) + } + + func canParse(key: String, value: Int) -> Bool { + return keys.dropFirst().map{ key.contains($0) }.reduce(key.contains(keys.first!), { $0 || $1 }) + } +} + +extension NFXJsonParser where T == Bool { + + func canParse(_ value: Any) -> Bool { + return String(describing: type(of: value)).contains("Boolean") + } +} diff --git a/netfox/Core/Json2Codable/String+CamelCase.swift b/netfox/Core/Json2Codable/String+CamelCase.swift new file mode 100644 index 00000000..1354cb43 --- /dev/null +++ b/netfox/Core/Json2Codable/String+CamelCase.swift @@ -0,0 +1,60 @@ +// +// String+CamelCase.swift +// netfox_osx +// +// Created by Ștefan Suciu on 2/13/18. +// Copyright © 2018 kasketis. All rights reserved. +// + +import Foundation + +extension String { + + func uppercaseFirstLetter() -> String { + if hasPrefix("[") && hasSuffix("]") { + let leadingParanthesisCount = components(separatedBy: "[").count - 1 + let endingParanthesisCount = components(separatedBy: "]").count - 1 + let startIndex = index(self.startIndex, offsetBy: leadingParanthesisCount) + let endIndex = index(self.endIndex, offsetBy: -endingParanthesisCount) + let stringWithoutParanthesis = self[startIndex.. String { + return prefix(1).lowercased() + dropFirst() + } + + func replacingLastOccurences(of searchString: String, with replacementString: String) -> String { + if let range = self.range(of: searchString, options: [.backwards, .caseInsensitive]) { + return self.replacingCharacters(in: range, with: replacementString) + } + + return self + } + + var camelCasedString: String { + let splittedComponents = components(separatedBy: CharacterSet(charactersIn: "_- ")) + return splittedComponents.first! + splittedComponents.dropFirst().map{ $0.uppercaseFirstLetter() }.joined() + } + + var singular: String { + let paranthesisCount = components(separatedBy: "]").count - 1 + let stringWithoutParanthesis = dropLast(paranthesisCount) + + if stringWithoutParanthesis.hasSuffix("ies") { + return replacingLastOccurences(of: "ies", with: "y") + } + + if stringWithoutParanthesis.hasSuffix("s") { + return stringWithoutParanthesis.dropLast() + String(repeating: "]", count: paranthesisCount) + } + + return self + } +} diff --git a/netfox/Core/NFX.swift b/netfox/Core/NFX.swift index 89e78d0c..51044fe7 100755 --- a/netfox/Core/NFX.swift +++ b/netfox/Core/NFX.swift @@ -6,6 +6,7 @@ // import Foundation + #if os(OSX) import Cocoa #else @@ -63,6 +64,7 @@ open class NFX: NSObject fileprivate var ignoredURLs = [String]() fileprivate var filters = [Bool]() fileprivate var lastVisitDate: Date = Date() + var server: NFXServer? internal var cacheStoragePolicy = URLCache.StoragePolicy.notAllowed @objc open func start() @@ -94,6 +96,20 @@ open class NFX: NSObject #endif } + @objc public func startServer() { + if !self.started { + self.start() + } + + self.server = NFXServer() + self.server?.startServer() + } + + @objc public func stopServer() { + self.server?.stopServer() + self.server = nil + } + fileprivate func showMessage(_ msg: String) { print("netfox \(nfxVersion) - [https://github.com/kasketis/netfox]: \(msg)") } @@ -261,7 +277,8 @@ extension NFX { navigationController.navigationBar.tintColor = UIColor.NFXOrangeColor() navigationController.navigationBar.barTintColor = UIColor.NFXStarkWhiteColor() #if !swift(>=4.0) - navigationController.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.NFXOrangeColor()] + + navigationController.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.NFXOrangeColor()] #else navigationController.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.NFXOrangeColor()] #endif @@ -285,6 +302,14 @@ extension NFX { public func windowDidClose() { self.presented = false + + if Bundle.main.bundleIdentifier == "com.tapptitude.netfox-mac" { + #if !swift(>=4.0) + NSApplication.shared().terminate(self) + #else + NSApplication.shared.terminate(self) + #endif + } } private func setupNetfoxMenuItem() { diff --git a/netfox/Core/NFXAssets.swift b/netfox/Core/NFXAssets.swift index cff3403e..738e6f70 100644 --- a/netfox/Core/NFXAssets.swift +++ b/netfox/Core/NFXAssets.swift @@ -12,6 +12,13 @@ enum NFXAssetName { case close case info case statistics + case cloud + case folder + case fileDownloading + case fileSuccess + case fileWarning + case fileUnauthorized + case serverError } class NFXAssets @@ -24,6 +31,13 @@ class NFXAssets case .close: return getCloseImageBase64() case .info: return getInfoImageBase64() case .statistics: return getStatisticsImageBase64() + case .cloud: return getCloudImageBase64() + case .folder: return getFolderImageBase64() + case .fileDownloading: return getFileDownloadingBase64() + case .fileSuccess: return getFileSuccessBase64() + case .fileWarning: return getFileWarningBase64() + case .fileUnauthorized: return getFileUnauthorizedBase64() + case .serverError: return getServerErrorBase64() } }() @@ -50,4 +64,37 @@ class NFXAssets return "iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAAflBMVEUAAADsXijsXijsXijsXijtXSfsXijsXSjsXijsXijsXSbsXijtXSjsXijsXifsXijsXiftXifsXifsXCfsXifsXifsXifsXifsXSftXSfsWyjsXijsXifsXifsXijtXifsXiftXybsXSfsXijtXSftXifsWibsXijuYCfsXijgd1NoAAAAKXRSTlMANNuwVBbuh/CUA9UG+t/QvCz0D+jYy3RKOQrlxqmOgGUgv7VvVwxbHXzMYTYAAAEjSURBVDjL7ZRJcoMwEEVlEgkhMU8GzOgp+fe/YChXxQSqK27vvPBbQfdbSD1IkMSxYONb6/Nta+n4B4XWZFjgCQTCZMciCWfZEUyctzzTDH3GlgfgSMmTCuXWlREQUXILbIcz0UijiZC/AiC4/FXNgOBMXdCckI4jEPvLYLdId1Q1pgOi+dZZhDRZjlASpfO7MsXZv32OCNxbdh9gT9U5BoLqN1orlNI0HnRNNiUHvCUsj8htiJOkO/gdlauMi3sRiXabTSc0ULFnI2kvLzaiLyjn1SeLKsdzb92OoA7DmooLClMURnDxAI/ruphxua4+HDTP9qAyazMFj+VK4zhGMmwXqplXdV7URsF96Mr7tjywe6jr8ndV6P+Ru0KuNrHoVukfuacsKwHjtUEAAAAASUVORK5CYII=" } + class func getCloudImageBase64() -> String + { + return "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAA3RJREFUeJztmU9oFFccx7+/36yk8ZA0s6F1cyutlxxEsWJPtceGiNpDYlvqYUUNQjabSA4ehGGhQi8LmUlIGwRzESktVgUhx54KBQWhaA+i8SBoEMnCgn8S7O/nwQiym9ns252Z3aTvc/y9+b33fd+Z37w38wCLxWKxWCwWi8VisVgs/zco6g49z+N0Ov0VgEEAXwDYKSIfAmBmLgG4D+BvEblRKpX+LBQKErUGEyIzwPO8lOu6x1X1LDN/Uk+OiDwkop+Wl5cvFgqF11FpMSESA3zf7yeiSwD2NNjFbWY+Njo6ejcKPSY0bUAQBIMi8hszb2+mHxF54TjO0Vwud6NZTSY0ZUAQBIMArgFIRSMHr4nomyRNaNgA3/f7VfVms3d+HZ4z8/6kyqEhAzzPS6XT6ZsAdkes5x23ieigqh4F8LWI7GLmjwCIiDxl5n8ALKyurl6enJx81sxADRng+/4pIpprZuCNEBFl5o30vQQw1dHR8ePIyMiLRsYxNsDzPO7p6blf71KXEP+q6qF8Pv/ANJFNE1zXPdBmkweAfiL6a2Zm5jPTRGMDiOigaU5CfCwi14vFYqdJkrEBeLu9bVf6U6nUOZOERgzY2UBOYhDRxPT0dLre640NWPuwaWc6VfW7ei82MmBubm47M28z15Q4Q/W+C+oyQFXJ9/1jKysri83pSowvHcdZDILgB1WtudRvuA9Y2/X9DOBEZPISRFUvZDKZ08PDw/+t117zCVBVcl13Fpt08gBARCeXlpZmw9qdWsm9vb3fAzgfuark2TswMHBvYWHhTmVDaAkUi8VOx3EWmXlHvNqSQUQed3d3f5rNZl+9Hw8tAcdxhrfK5AGAmfvK5fJQVTwsgYiOxCupJRyuDNQy4PN4tSQPEe2rjIUaICKZeOW0hL7KQKgBzKzxamkJVWcQoQao6pN4tbSEx5WBWu+AW/FqaQlVc6r1BFyNV0vyEFHVnEIN6Orq+h3AlikDVX0kIlcq46EGZLPZVyJyJl5ZyUFE4/l8fqUyXvNjaHx8/FdV/SU+WckgIsHY2Ngf67Vt+D8gk8mMbmYTRCQolUoTYe11nwtMTU19C6DIzFWbiXZEVR8x80Qul6uq+/cxOhiZn5//oFwuD6nqEQB7AfS10S+yVbxd528R0VURubJezVssFovFYrFYLBaLxQK8AdiMC3Y6JoDbAAAAAElFTkSuQmCC" + } + + class func getFolderImageBase64() -> String + { + return "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" + } + + class func getFileDownloadingBase64() -> String + { + return "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" + } + + class func getFileSuccessBase64() -> String + { + return "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" + } + + class func getFileWarningBase64() -> String + { + return "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" + } + + class func getFileUnauthorizedBase64() -> String + { + return "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" + } + + class func getServerErrorBase64() -> String { + return "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAv1JREFUeJzt2rGLHkUcxvHPjodgYiVc7gQliUHBaGWtgmkk2Es0ighiOrFJZWMREPwPYhQFi+RAUWysIlhIElRsPFEJaDSIGm1MFETnNxZ3F16Tu/O93O7OqfOFgeXd993f8zw7s8zMvjQajUaj0Wg0/p90fV0ouB0PFu7FnbgVN+K6xMXgWyx2fIB3E2f7ql2NwkzwWOZ0oWykZc4Ejxdmavu4JoL9mS83anyVIL4I9tf2MzXB9swrmzW+ShAvF7bV9rcuhfnMx32bn2gfBXO1fa7KsvlNd/kpesLnWy6EwrbMJ0Obnwjhwy01HAqvjWV+IoRjtX2D4KGxzU+E8GrmVObnTGT+zPyQeT9zJLin9DiXuYrCTOZsrQCmDOlU8MAgAQRP1Da4kSFT+n5uZM7UNrbBEE4HN/ViPrhjYLFvBrPBjszbPYdwQx8BPDNkAMHsRK25nsM9uukAMieGDODKegMEfP80PtM65+6+5vS2AIUj03xvvQBu7klLFRL3FZ4Pdm/4x8GeTPybh8DE8yAyC8HOac0/mfltSPNjBjARxKXg4D+Zf25oIbUCWGnB4TXv/FgiagawHMKjK3W75eK3BZ+mPiYQU9JdsYhZLZShCH7tuCtxLi1/8OKY5muT2F54AbpgT1dhi3qyB5Sl4xizflA6diccGLPwhIDL216F+bHrJ7qOR1Jh39jFoXB0eQ0wX3iphoZgX5f5Pm21TciRCL7rMjmtPyX+zxL8kZBrC6lISfiptoqKXEj4rEbl4K3CjsJc8E4NDVhMHe/VqNxxKC3dgR87nq6k4WTC8RhxGvr3+pcPhtvbX4NYmngtpMRXiTfGFlA4tjIPiDpvgY4nzq0shnYFi2krvY8bkOBix97E+QQdX3ccqi1sLDqeSpy/6kRweMx1eY0WPLtuOsHBzKXaQvtumV+Ch6fqIsHOzImhN0ZHMp4zrwe3rDEc1qawCwdiacW4F7OJ66dKsRLB77hg6S95JzsWOr6pravRaDQajUaj0Wg0GluJvwDvfvlgh9X/mgAAAABJRU5ErkJggg==" + } } diff --git a/netfox/Core/NFXClientConnection.swift b/netfox/Core/NFXClientConnection.swift new file mode 100644 index 00000000..34e1deeb --- /dev/null +++ b/netfox/Core/NFXClientConnection.swift @@ -0,0 +1,221 @@ +// +// NFXClientConnection.swift +// netfox +// +// Created by Alexandru Tudose on 01/02/2018. +// Copyright © 2018 kasketis. All rights reserved. +// + +import Foundation + +class NFXClientConnection: NSObject { + let inputStream: InputStream + let outputStream: OutputStream + + private var thread: Thread! + private var runLoop: CFRunLoop? + + var _onClose: (() -> Void)? + + init (inputStream: InputStream, outputStream: OutputStream) { + self.inputStream = inputStream + self.outputStream = outputStream + } + + deinit { + inputStream.close() + outputStream.close() + } + + func scheduleOnBackgroundRunLoop() { + let threadName = String(describing: self).components(separatedBy: .punctuationCharacters)[1] + thread = Thread(target: self, selector: #selector(startRunLoop), object: nil) + thread.name = "\(threadName)-\(UUID().uuidString)" + thread.start() + } + + @objc func startRunLoop() { + scheduleOnRunLoop() + self.runLoop = CFRunLoopGetCurrent() + CFRunLoopRun() + } + + @objc func stopRunLoop() { + self.thread?.cancel() + self.thread = nil + if let runLoop = runLoop { + CFRunLoopStop(runLoop) + } + } + + @objc func scheduleOnRunLoop() { + inputStream.schedule(in: RunLoop.current, forMode: .defaultRunLoopMode) + outputStream.schedule(in: RunLoop.current, forMode: .defaultRunLoopMode) + inputStream.open() + outputStream.open() + } + + var onClose: (() -> Void)? { + get { return _onClose } + set { _onClose = {[unowned self] in + self.inputStream.delegate = nil + self.stopRunLoop() + self.inputStream.close() + self.outputStream.close() + DispatchQueue.main.async { + newValue?() + } + } + } + } + + static let serialQueue = DispatchQueue(label: "NFXClientConnection") + @objc func writeModel(_ model: NFXHTTPModel) { + NFXClientConnection.serialQueue.async { + let models = [ model.toJSON() ] + let jsonData = try! JSONSerialization.data(withJSONObject: models, options: []) + self.writeData(jsonData) + } + } + + @objc func writeAllModels() { + NFXClientConnection.serialQueue.async { + let models = NFXHTTPModelManager.sharedInstance.getModels() + models.reversed().chunked(by: 20).forEach({ items in + print(items.count) + let models = items.map({ $0.toJSON() }) + let jsonData = try! JSONSerialization.data(withJSONObject: models, options: []) + self.writeData(jsonData) + }) + } + } + + func writeData(_ data: Data) { + // write size of payload + let messageSize = Int32(data.count) + outputStream.write(toByteArrary(value: messageSize), maxLength: MemoryLayout.size(ofValue: messageSize)) + + // write payload + let bytes = [UInt8](data) + var bytesWritten = 0 + while bytes.count > bytesWritten { + let count = bytes.count - bytesWritten + let writeCount = outputStream.write([UInt8](bytes[bytesWritten...bytes.count - 1]), maxLength: count) + if writeCount == -1 { + onClose?() + print(outputStream.streamError ?? "") + print("Netfox connection - An error occured while writing data") + return + } + + bytesWritten += writeCount + } + } + + func prepareForReadingStream() { + inputStream.delegate = self + } + + func prepareForWritingStream() { + outputStream.delegate = self + } + + var bufferData: Data = Data() + var toReadCount: Int = 0 + + func readData() { + if toReadCount == 0 { + var sizeBuffer: [UInt8] = [0,0,0,0] + inputStream.read(&sizeBuffer, maxLength: sizeBuffer.count) + let data = NSData(bytes: sizeBuffer, length: sizeBuffer.count) + toReadCount = Int(data.uint32.littleEndian) + bufferData = Data() + } + + let bufferSize = min(toReadCount, 2048) + var buffer = Array(repeating: 0, count: bufferSize) + let readCount = inputStream.read(&buffer, maxLength: bufferSize) + bufferData.append(buffer, count: readCount) + toReadCount -= readCount + if toReadCount == 0 { + NFX.sharedInstance().addJSONModels(bufferData) + bufferData = Data() + } + } +} + + +extension NFXClientConnection: StreamDelegate { + public func stream(_ aStream: Stream, handle eventCode: Stream.Event) { + guard let _ = aStream as? InputStream else { + return + } + + switch eventCode { + case .hasBytesAvailable: + readData() + case .errorOccurred, .endEncountered: + print(eventCode) + onClose?() + case .openCompleted, .hasSpaceAvailable: + break + default: + break + } + } +} + +extension NFX { + public func addJSONModels(_ data: Data) { + guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else { + return + } + + if let jsonModels = json as? [[String: Any]] { + let models: [NFXHTTPModel] = jsonModels.flatMap({ + let model = NFXHTTPModel() + model.fromJSON(json: $0) + return model + }) + + models.reversed().forEach({ NFXHTTPModelManager.sharedInstance.add($0) }) + + DispatchQueue.main.async { + NotificationCenter.default.post(name: Notification.Name(rawValue: "NFXReloadData"), object: nil) + } + } + } +} + + +func toByteArrary(value: T) -> [UInt8] where T: FixedWidthInteger{ + var bigEndian = value.littleEndian + let count = MemoryLayout.size + let bytePtr = withUnsafePointer(to: &bigEndian) { + $0.withMemoryRebound(to: UInt8.self, capacity: count) { + UnsafeBufferPointer(start: $0, count: count) + } + } + + return Array(bytePtr) +} + +extension NSData { + var uint32: UInt32 { + get { + var number: UInt32 = 0 + self.getBytes(&number, length: MemoryLayout.size) + return number + } + } +} + + +extension Array { + func chunked(by chunkSize:Int) -> [[Element]] { + let groups = stride(from: 0, to: self.count, by: chunkSize).map { + Array(self[$0..<[$0 + chunkSize, self.count].min()!]) + } + return groups + } +} diff --git a/netfox/Core/NFXGenericController.swift b/netfox/Core/NFXGenericController.swift index 8249b555..04ec6aaf 100755 --- a/netfox/Core/NFXGenericController.swift +++ b/netfox/Core/NFXGenericController.swift @@ -45,8 +45,8 @@ class NFXGenericController: NFXViewController for match in matchesBodyHeaders { #if !swift(>=4.0) - tempMutableString.addAttribute(NSFontAttributeName, value: NFXFont.NFXFontBold(size: 14), range: match.range) - tempMutableString.addAttribute(NSForegroundColorAttributeName, value: NFXColor.NFXOrangeColor(), range: match.range) + tempMutableString.addAttribute(NSAttributedStringKey.font, value: NFXFont.NFXFontBold(size: 14), range: match.range) + tempMutableString.addAttribute(NSAttributedStringKey.foregroundColor, value: NFXColor.NFXOrangeColor(), range: match.range) #else tempMutableString.addAttribute(NSAttributedStringKey.font, value: NFXFont.NFXFontBold(size: 14), range: match.range) tempMutableString.addAttribute(NSAttributedStringKey.foregroundColor, value: NFXColor.NFXOrangeColor(), range: match.range) @@ -58,7 +58,7 @@ class NFXGenericController: NFXViewController for match in matchesKeys { #if !swift(>=4.0) - tempMutableString.addAttribute(NSForegroundColorAttributeName, value: NFXColor.NFXBlackColor(), range: match.range) + tempMutableString.addAttribute(NSAttributedStringKey.foregroundColor, value: NFXColor.NFXBlackColor(), range: match.range) #else tempMutableString.addAttribute(NSAttributedStringKey.foregroundColor, value: NFXColor.NFXBlackColor(), range: match.range) #endif diff --git a/netfox/Core/NFXHTTPModel.swift b/netfox/Core/NFXHTTPModel.swift index 9e223bd7..50f5907c 100755 --- a/netfox/Core/NFXHTTPModel.swift +++ b/netfox/Core/NFXHTTPModel.swift @@ -56,6 +56,7 @@ fileprivate func < (lhs: T?, rhs: T?) -> Bool { self.requestTimeout = request.getNFXTimeout() self.requestHeaders = request.getNFXHeaders() self.requestType = requestHeaders?["Content-Type"] as! String? + self.requestCurl = request.getCurl() } @@ -344,3 +345,69 @@ fileprivate func < (lhs: T?, rhs: T?) -> Bool { } } + + +/// allow serialization +extension NFXHTTPModel { + func toJSON() -> [String: Any] { + var json: [String: Any] = [:] + + json["requestURL"] = requestURL + json["requestMethod"] = requestMethod + + json["requestCachePolicy"] = requestCachePolicy + json["requestDate"] = requestDate?.timeIntervalSince1970 + json["requestTime"] = requestTime + json["requestTimeout"] = requestTimeout + json["requestHeaders"] = requestHeaders + json["requestBodyLength"] = requestBodyLength + json["requestType"] = requestType + json["responseStatus"] = responseStatus + json["responseType"] = responseType + json["responseDate"] = responseDate?.timeIntervalSince1970 + + json["responseTime"] = responseTime + json["responseHeaders"] = responseHeaders + json["responseBodyLength"] = responseBodyLength + json["timeInterval"] = timeInterval + json["randomHash"] = randomHash + json["shortType"] = shortType + json["noResponse"] = noResponse + + + json["requestBody"] = getRequestBody() + json["responseBody"] = getResponseBody() + + return json + } + + func fromJSON(json: [String: Any]) { + requestURL = json["requestURL"] as? String + requestMethod = json["requestMethod"] as? String + + requestCachePolicy = json["requestCachePolicy"] as? String + requestDate = json["requestDate"].flatMap({ $0 as? TimeInterval }).flatMap({ Date(timeIntervalSince1970: $0) }) + requestTime = json["requestTime"] as? String + requestTimeout = json["requestTimeout"] as? String + requestHeaders = json["requestHeaders"] as? [AnyHashable: Any] + requestBodyLength = json["requestBodyLength"] as? Int + requestType = json["requestType"] as? String + responseStatus = json["responseStatus"] as? Int + responseType = json["responseType"] as? String + responseDate = json["responseDate"].flatMap({ $0 as? TimeInterval }).flatMap({ Date(timeIntervalSince1970: $0) }) + + responseTime = json["responseTime"] as? String + responseHeaders = json["responseHeaders"] as? [AnyHashable: Any] + responseBodyLength = json["responseBodyLength"] as? Int + timeInterval = json["timeInterval"] as? Float + randomHash = json["randomHash"] as? NSString + shortType = json["shortType"] as! NSString + noResponse = json["noResponse"] as? Bool ?? true + + let requestBody = json["requestBody"] as? String ?? "" + let responseBody = json["responseBody"] as? String ?? "" + + saveRequestBodyData(requestBody.data(using: .utf8)!) + saveResponseBodyData(responseBody.data(using: .utf8)!) + } +} diff --git a/netfox/Core/NFXHTTPModelManager.swift b/netfox/Core/NFXHTTPModelManager.swift index 0cb549da..bd4be6c6 100755 --- a/netfox/Core/NFXHTTPModelManager.swift +++ b/netfox/Core/NFXHTTPModelManager.swift @@ -20,6 +20,10 @@ final class NFXHTTPModelManager: NSObject syncQueue.async { self.models.insert(obj, at: 0) NotificationCenter.default.post(name: NSNotification.Name.NFXAddedModel, object: obj) + + #if os(OSX) + NFXPathNodeManager.sharedInstance.add(obj) + #endif } } @@ -28,6 +32,10 @@ final class NFXHTTPModelManager: NSObject syncQueue.async { self.models.removeAll() NotificationCenter.default.post(name: NSNotification.Name.NFXClearedModels, object: nil) + + #if os(OSX) + NFXPathNodeManager.sharedInstance.clear() + #endif } } diff --git a/netfox/Core/NFXHelper.swift b/netfox/Core/NFXHelper.swift index 61091dc1..0f071a83 100644 --- a/netfox/Core/NFXHelper.swift +++ b/netfox/Core/NFXHelper.swift @@ -210,62 +210,89 @@ extension URLResponse extension NFXImage { - class func NFXSettings() -> NFXImage - { - #if os (iOS) - return UIImage(data: NFXAssets.getImage(NFXAssetName.settings), scale: 1.7)! - #elseif os(OSX) - return NSImage(data: NFXAssets.getImage(NFXAssetName.settings))! - #endif - } + static let settings: NFXImage = { + #if os (iOS) + return UIImage(data: NFXAssets.getImage(NFXAssetName.settings), scale: 1.7)! + #elseif os(OSX) + return NSImage(data: NFXAssets.getImage(NFXAssetName.settings))! + #endif + }() - class func NFXClose() -> NFXImage - { - #if os (iOS) - return UIImage(data: NFXAssets.getImage(NFXAssetName.close), scale: 1.7)! - #elseif os(OSX) - return NSImage(data: NFXAssets.getImage(NFXAssetName.close))! - #endif - } - - class func NFXInfo() -> NFXImage - { - #if os (iOS) - return UIImage(data: NFXAssets.getImage(NFXAssetName.info), scale: 1.7)! - #elseif os(OSX) - return NSImage(data: NFXAssets.getImage(NFXAssetName.info))! - #endif - } - - class func NFXStatistics() -> NFXImage - { - #if os (iOS) - return UIImage(data: NFXAssets.getImage(NFXAssetName.statistics), scale: 1.7)! - #elseif os(OSX) - return NSImage(data: NFXAssets.getImage(NFXAssetName.statistics))! + static let close: NFXImage = { + #if os (iOS) + return UIImage(data: NFXAssets.getImage(NFXAssetName.close), scale: 1.7)! + #elseif os(OSX) + return NSImage(data: NFXAssets.getImage(NFXAssetName.close))! + #endif + }() + + static let info: NFXImage = { + #if os (iOS) + return UIImage(data: NFXAssets.getImage(NFXAssetName.info), scale: 1.7)! + #elseif os(OSX) + return NSImage(data: NFXAssets.getImage(NFXAssetName.info))! + #endif + }() + + static let statistics: NFXImage = { + #if os (iOS) + return UIImage(data: NFXAssets.getImage(NFXAssetName.statistics), scale: 1.7)! + #elseif os(OSX) + return NSImage(data: NFXAssets.getImage(NFXAssetName.statistics))! + #endif + }() + + #if os (OSX) + static let cloud: NFXImage = { + return NSImage(data: NFXAssets.getImage(NFXAssetName.cloud))! + }() + + static let folder: NFXImage = { + return NSImage(data: NFXAssets.getImage(NFXAssetName.folder))! + }() + + static let fileDownloading: NFXImage = { + return NSImage(data: NFXAssets.getImage(NFXAssetName.fileDownloading))! + }() + + static let fileSuccess: NFXImage = { + return NSImage(data: NFXAssets.getImage(NFXAssetName.fileSuccess))! + }() + + static let fileWarning: NFXImage = { + return NSImage(data: NFXAssets.getImage(NFXAssetName.fileWarning))! + }() + + static let fileUnauthorized: NFXImage = { + return NSImage(data: NFXAssets.getImage(NFXAssetName.fileUnauthorized))! + }() + + static let serverError: NFXImage = { + return NSImage(data: NFXAssets.getImage(NFXAssetName.serverError))! + }() #endif - } } extension InputStream { - func readfully() -> Data { - var result = Data() - var buffer = [UInt8](repeating: 0, count: 4096) - - open() - var amount = 0 - repeat { - amount = read(&buffer, maxLength: buffer.count) - if amount > 0 { - result.append(buffer, count: amount) - } - } while amount > 0 - - close() - - return result - } + func readfully() -> Data { + var result = Data() + var buffer = [UInt8](repeating: 0, count: 4096) + + open() + + var amount = 0 + repeat { + amount = read(&buffer, maxLength: buffer.count) + if amount > 0 { + result.append(buffer, count: amount) + } + } while amount > 0 + + close() + + return result + } } extension Date @@ -343,7 +370,12 @@ class NFXDebugInfo let session = URLSession.shared session.dataTask(with: req as URLRequest, completionHandler: { (data, response, error) in do { - let rawJsonData = try JSONSerialization.jsonObject(with: data!, options: [.allowFragments]) + guard let data = data else { + completion("-") + return + } + + let rawJsonData = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) if let ipAddress = (rawJsonData as AnyObject).value(forKey: "ip") { completion(ipAddress as! String) } else { diff --git a/netfox/Core/NFXNetServiceMonitor.swift b/netfox/Core/NFXNetServiceMonitor.swift new file mode 100644 index 00000000..73cab7c2 --- /dev/null +++ b/netfox/Core/NFXNetServiceMonitor.swift @@ -0,0 +1,148 @@ +// +// NFXNetService.swift +// netfox_mac +// +// Created by Alexandru Tudose on 01/11/2017. +// Copyright © 2017 kasketis. All rights reserved. +// + +import Foundation + +#if os(OSX) +class NFXNetServiceMonitor: NSObject { + + static let shared = NFXNetServiceMonitor() + class FoundService { + var service: NetService + var address: String + var canConnect = true + + init(service: NetService, address: String) { + self.service = service + self.address = address + } + + var name: String { + return service.name + " " + (service.hostName ?? "") + } + } + + var foundServices: [FoundService] = [] + var processingServices: [NetService] = [] + let serviceBrowser = NetServiceBrowser() + + var clients: [NFXClientConnection] = [] + + func browseForAvailableNFXServices() { + windowController?.popupButton.removeAllItems() + + serviceBrowser.delegate = self + serviceBrowser.searchForServices(ofType: NFXServer.Options.bonjourServiceType, inDomain: "") + } + + func foundServer(address: String, service: NetService) { + let foundService = FoundService(service: service, address: address) + if let index = foundServices.index(where: { $0.name == foundService.name }) { + foundServices[index] = foundService + let popupItem = windowController?.popupButton.item(at: index) + popupItem?.isEnabled = true + + let isSelected = windowController?.popupButton.indexOfSelectedItem == index + if isSelected { + fetchServiceContent(service: service) + } + } else { + if foundServices.isEmpty { + fetchServiceContent(service: service) + } + + foundServices.append(foundService) + windowController?.popupButton.addItem(withTitle: foundService.name ) + print(" ", windowController!.popupButton.itemTitles) + } + } + + func removeService(_ service: NetService) { + if let index = foundServices.index(where: { $0.service === service }) { + let item = foundServices[index] + item.canConnect = false + let popupItem = windowController?.popupButton.item(at: index) + popupItem?.isEnabled = false + } + } + + var windowController: NFXWindowController? { + return NFX.sharedInstance().windowController + } + + func fetchServiceContent(service: NetService) { + NFXHTTPModelManager.sharedInstance.clear() + NotificationCenter.default.post(name: Notification.Name(rawValue: "NFXReloadData"), object: nil) + + var inputStream: InputStream? + var outputStream: OutputStream? + let didOpen = service.getInputStream(&inputStream, outputStream: &outputStream) + if didOpen { + let client = NFXClientConnection(inputStream: inputStream!, outputStream: outputStream!) + client.scheduleOnBackgroundRunLoop() + client.prepareForReadingStream() + clients.forEach({ $0.stopRunLoop() }) + clients = [client] + client.onClose = { [unowned self] in + self.clients = [] + self.removeService(service) + } + } + } +} + + +extension NFXNetServiceMonitor: NetServiceBrowserDelegate { + func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) { + service.delegate = self + service.resolve(withTimeout: 0) + processingServices.append(service) + } + + func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) { + print(#function) + } +} + +extension NFXNetServiceMonitor: NetServiceDelegate { + func netServiceDidResolveAddress(_ sender: NetService) { + print("Found service: \(sender.hostName ?? "") \(sender)") + + let addresses = sender.addresses?.flatMap({ (data: Data) -> String? in + let nsData = data as NSData + let inetAddress: sockaddr_in = nsData.castToCPointer() + if inetAddress.sin_family == __uint8_t(AF_INET) { + return String(cString: inet_ntoa(inetAddress.sin_addr), encoding: .ascii) + } else { + return nil + } + }) + + if let address = addresses?.first { + self.foundServer(address: address, service: sender) + } + } + + func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber]) { + print("failed to resove service \(sender): \(errorDict)") + } +} + + + +extension NSData { + func castToCPointer() -> T { + let mem = UnsafeMutablePointer.allocate(capacity: MemoryLayout.size) + self.getBytes(mem, length: MemoryLayout.size) + return mem.move() + } +} + + + +#endif diff --git a/netfox/Core/NFXPathNode.swift b/netfox/Core/NFXPathNode.swift new file mode 100644 index 00000000..ae28822d --- /dev/null +++ b/netfox/Core/NFXPathNode.swift @@ -0,0 +1,72 @@ +// +// NFXPathNode.swift +// netfox_ios +// +// Created by Ștefan Suciu on 2/5/18. +// Copyright © 2018 kasketis. All rights reserved. +// + +import Foundation + +#if os(OSX) + +class NFXPathNode { + + var name: String + var children: [NFXPathNode] + weak var parent: NFXPathNode? + var httpModel: NFXHTTPModel? + var isExpanded: Bool + + init(name: String) { + self.name = name + self.children = [] + self.isExpanded = true + } + + func insert(_ node: NFXPathNode) { + children.append(node) + node.parent = self + } + + func find(_ node: NFXPathNode) -> NFXPathNode? { + if name == node.name && httpModel == nil { + return self + } + + return children.flatMap{ $0.find(node) }.first + } + + func depth() -> Int { + if parent == nil { + return 0 + } + + return parent!.depth() + 1 + } + + func findLeaves() -> [NFXHTTPModel] { + if children.isEmpty { + return httpModel != nil ? [httpModel!] : [] + } + + return children.map{ $0.findLeaves() }.reduce([], +) + } + + func toArray() -> [NFXPathNode] { + if !isExpanded { + return [self] + } + + return [self] + children.map{ $0.toArray() }.reduce([], +) + } + + func printTree(_ level: Int = 0) { + print(String(repeating: " ", count: level) + "\(name) -> \(httpModel?.requestURL ?? "")") + for child in children { + child.printTree(level + 4) + } + } +} + +#endif diff --git a/netfox/Core/NFXPathNodeManager.swift b/netfox/Core/NFXPathNodeManager.swift new file mode 100644 index 00000000..b05b321e --- /dev/null +++ b/netfox/Core/NFXPathNodeManager.swift @@ -0,0 +1,65 @@ +// +// NFXPathNodeManager.swift +// netfox_ios +// +// Created by Ștefan Suciu on 2/5/18. +// Copyright © 2018 kasketis. All rights reserved. +// + +import Foundation + +#if os(OSX) + +final class NFXPathNodeManager { + + static let sharedInstance = NFXPathNodeManager() + fileprivate var rootNode = NFXPathNode(name: "root") + + func add(_ arr: [NFXHTTPModel]) { + arr.forEach{ add($0) } + } + + func add(_ obj: NFXHTTPModel) { + guard let nodes = obj.requestURL?.split(separator: "/").dropFirst().map({ NFXPathNode(name: String($0)) }) else { + return + } + + let nodesWithoutLast = nodes.dropLast() + var previousNode = rootNode + for node in nodesWithoutLast { + if let foundNode = previousNode.find(node) { + previousNode = foundNode + } else { + previousNode.insert(node) + previousNode = node + } + } + let resourceNode = NFXPathNode(name: nodes.last?.name ?? "-") + resourceNode.httpModel = obj + previousNode.insert(resourceNode) + } + + func update(_ obj: NFXHTTPModel) { + guard let name = obj.requestURL?.split(separator: "/").last else { + return + } + + let node = NFXPathNode(name: String(name)) + rootNode.find(node)?.httpModel = obj + } + + func clear() { + rootNode.children = [] + } + + func getHttpModels() -> [NFXHTTPModel] { + return rootNode.findLeaves() + } + + func getTableModels() -> [NFXPathNode] { + var models = rootNode.toArray() + models.remove(at: 0) + return models + } +} +#endif diff --git a/netfox/Core/NFXProtocol.swift b/netfox/Core/NFXProtocol.swift index 60ed3dc8..fbe3d452 100755 --- a/netfox/Core/NFXProtocol.swift +++ b/netfox/Core/NFXProtocol.swift @@ -111,6 +111,7 @@ open class NFXProtocol: URLProtocol { if (self.model != nil) { NFXHTTPModelManager.sharedInstance.add(self.model!) + NFX.sharedInstance().server?.broadcastModel(self.model!) } NotificationCenter.default.post(name: Notification.Name.NFXReloadData, object: nil) diff --git a/netfox/Core/NFXServer.swift b/netfox/Core/NFXServer.swift new file mode 100644 index 00000000..f39396dd --- /dev/null +++ b/netfox/Core/NFXServer.swift @@ -0,0 +1,82 @@ +// +// NFXServer.swift +// netfox_ios +// +// Created by Alexandru Tudose on 01/11/2017. +// Copyright © 2017 kasketis. All rights reserved. +// + +import Foundation + +public class NFXServer: NSObject { + public struct Options { + public static let bonjourServiceType = "_NFX._tcp." + public static let port: UInt16 = 12222 + } + + var netService: NetService? + var port: UInt16 = Options.port + var numberOfRetries = 0 + var connectedClients: [NFXClientConnection] = [] + + public func startServer() { + publishHttpService() + #if os(iOS) + NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: .UIApplicationDidBecomeActive, object: nil) + #endif + } + + public func stopServer() { + netService?.stop() + netService = nil + NotificationCenter.default.removeObserver(self) + } + + func publishHttpService() { + let bundleIdentifier = Bundle.main.bundleIdentifier ?? "" + let netService = NetService(domain: "", type: NFXServer.Options.bonjourServiceType, name: bundleIdentifier, port: Int32(port)) + netService.delegate = self + netService.publish(options: [.listenForConnections]) + self.netService = netService + } + + func broadcastModel(_ model: NFXHTTPModel) { + connectedClients.forEach({ $0.writeModel(model) }) + } + + @objc func applicationDidBecomeActive() { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + if self.connectedClients.isEmpty { + self.stopServer() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.startServer() + } + } + } + } +} + +extension NFXServer: NetServiceDelegate { + public func netServiceDidPublish(_ sender: NetService) { + + } + + public func netService(_ sender: NetService, didNotPublish errorDict: [String : NSNumber]) { + print("failed to publish http service: \(errorDict)") + } + + public func netService(_ sender: NetService, didAcceptConnectionWith inputStream: InputStream, outputStream: OutputStream) { + let client = NFXClientConnection(inputStream: inputStream, outputStream: outputStream) + client.scheduleOnBackgroundRunLoop() + connectedClients.append(client) + client.prepareForWritingStream() + client.writeAllModels() + client.onClose = { [unowned self] in + if let index = self.connectedClients.index(of: client) { + self.connectedClients.remove(at: index) + } + } + } +} + diff --git a/netfox/Core/NFXWindowController.swift b/netfox/Core/NFXWindowController.swift index aa4788e4..18091956 100644 --- a/netfox/Core/NFXWindowController.swift +++ b/netfox/Core/NFXWindowController.swift @@ -19,9 +19,12 @@ class NFXWindowController: NSWindowController, NSWindowDelegate, NFXWindowContro @IBOutlet var infoButton: NSButton! @IBOutlet var statisticsButton: NSButton! + @IBOutlet var popupButton: NSPopUpButton! @IBOutlet var listView: NSView! @IBOutlet var detailsView: NSView! - @IBOutlet var listViewController: NFXListController_OSX! + @IBOutlet var tableView: NSTableView! + @IBOutlet var listViewController: NFXPathNodeListController_OSX! + @IBOutlet var structuredListViewController: NFXPathNodeListController_OSX! @IBOutlet var detailsViewController: NFXDetailsController_OSX! @IBOutlet var settingsPopover: NSPopover! @@ -33,6 +36,7 @@ class NFXWindowController: NSWindowController, NSWindowDelegate, NFXWindowContro @IBOutlet var infoViewController: NFXInfoController_OSX! @IBOutlet var infoView: NSView! + @IBOutlet var segmentedControl: NSSegmentedControl! @IBOutlet var statisticsViewController: NFXStatisticsController_OSX! @IBOutlet var statisticsView: NSView! @@ -44,10 +48,11 @@ class NFXWindowController: NSWindowController, NSWindowDelegate, NFXWindowContro infoButton.image = NSImage(data: NFXAssets.getImage(.info)) statisticsButton.image = NSImage(data: NFXAssets.getImage(.statistics)) - listViewController.view = listView listViewController.delegate = self detailsViewController.view = detailsView + structuredListViewController.delegate = self + settingsViewController.view = settingsView infoViewController.view = infoView statisticsViewController.view = statisticsView @@ -59,6 +64,8 @@ class NFXWindowController: NSWindowController, NSWindowDelegate, NFXWindowContro override func windowDidLoad() { super.windowDidLoad() self.window?.delegate = self + + NFXNetServiceMonitor.shared.browseForAvailableNFXServices() } // MARK: NSWindowDelegate @@ -71,18 +78,42 @@ class NFXWindowController: NSWindowController, NSWindowDelegate, NFXWindowContro // MARK: Actions - @IBAction func settingsClicked(sender: AnyObject?) { + @IBAction func settingsClicked(_ sender: AnyObject?) { settingsPopover.show(relativeTo: NSZeroRect, of: settingsButton, preferredEdge: NSRectEdge.maxY) } - @IBAction func infoClicked(sender: AnyObject?) { + @IBAction func infoClicked(_ sender: AnyObject?) { infoPopover.show(relativeTo: NSZeroRect, of: infoButton, preferredEdge: NSRectEdge.maxY) } - @IBAction func statisticsClicked(sender: AnyObject?) { + @IBAction func statisticsClicked(_ sender: AnyObject?) { statisticsPopover.show(relativeTo: NSZeroRect, of: statisticsButton, preferredEdge: NSRectEdge.maxY) } - + + @IBAction func hostClicked(_ sender: Any) { + let foundService = NFXNetServiceMonitor.shared.foundServices[popupButton.indexOfSelectedItem] + NFXNetServiceMonitor.shared.fetchServiceContent(service: foundService.service) + } + + @IBAction func segmentedAction(_ sender: Any) { + switch segmentedControl.selectedSegment { + case 0: + listViewController.searchField.delegate = listViewController + listViewController.tableView = tableView + tableView.delegate = listViewController + tableView.dataSource = listViewController + tableView.reloadData() + case 1: + structuredListViewController.searchField.delegate = structuredListViewController + structuredListViewController.tableView = tableView + tableView.delegate = structuredListViewController + tableView.dataSource = structuredListViewController + tableView.reloadData() + default: + abort() + } + } + } extension NFXWindowController { diff --git a/netfox/OSX/NFXDetailsController_OSX.swift b/netfox/OSX/NFXDetailsController_OSX.swift index d7fffd87..5c10697a 100644 --- a/netfox/OSX/NFXDetailsController_OSX.swift +++ b/netfox/OSX/NFXDetailsController_OSX.swift @@ -16,6 +16,7 @@ class NFXDetailsController_OSX: NFXDetailsController { @IBOutlet var textViewBodyRequest: NSTextView! @IBOutlet var textViewResponse: NSTextView! @IBOutlet var textViewBodyResponse: NSTextView! + @IBOutlet var textViewCodable: NSTextView! override func viewDidLoad() { super.viewDidLoad() @@ -35,15 +36,41 @@ class NFXDetailsController_OSX: NFXDetailsController { self.textViewBodyRequest.textStorage?.setAttributedString(bodyRequest) self.textViewResponse.textStorage?.setAttributedString(self.getResponseStringFromObject(model)) - let bodyResponse: NSAttributedString + var bodyResponse: NSAttributedString if model.responseBodyLength == 0 { bodyResponse = self.formatNFXString(String(self.getResponseBodyStringFooter(model))) } else { bodyResponse = self.formatNFXString(String(model.getResponseBody())) } self.textViewBodyResponse.textStorage?.setAttributedString(bodyResponse) + + guard model.responseBodyLength != 0 else { + bodyResponse = self.formatNFXString(String(self.getResponseBodyStringFooter(model))) + self.textViewCodable.textStorage?.setAttributedString(bodyResponse) + return + } + + do { + let str = model.getResponseBody() as String + let data = str.data(using: .utf8)! + let converter = NFXJson2Codable() + if let dictionary = try JSONSerialization.jsonObject(with: data) as? [String: Any] { + let _ = converter.convertToCodable( + name: converter.getResourceName(from: model.requestURL), + from: dictionary + ) + self.textViewCodable.textStorage?.setAttributedString(NSAttributedString(string: converter.printClasses())) + } else if let array = try JSONSerialization.jsonObject(with: data) as? [Any], !array.isEmpty { + let _ = converter.convertToCodable( + name: converter.getResourceName(from: model.requestURL), + from: array + ) + self.textViewCodable.textStorage?.setAttributedString(NSAttributedString(string: converter.printClasses())) + } + } catch { + self.textViewCodable.textStorage?.setAttributedString(NSAttributedString(string: "Something went wrong with decoding. :(")) + } } - } #endif diff --git a/netfox/OSX/NFXListCell_OSX.xib b/netfox/OSX/NFXListCell_OSX.xib old mode 100644 new mode 100755 index f124b830..66a9cae9 --- a/netfox/OSX/NFXListCell_OSX.xib +++ b/netfox/OSX/NFXListCell_OSX.xib @@ -1,15 +1,16 @@ - - + + - + + - - + + @@ -18,7 +19,7 @@ - + @@ -26,7 +27,7 @@ - + @@ -52,20 +53,21 @@ - + - + - + - + + Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label - + @@ -73,7 +75,7 @@ - + @@ -82,7 +84,7 @@ - + @@ -94,7 +96,7 @@ - + @@ -120,7 +122,7 @@ - + diff --git a/netfox/OSX/NFXListController_OSX.swift b/netfox/OSX/NFXListController_OSX.swift old mode 100644 new mode 100755 index 2e907f65..cbe142ca --- a/netfox/OSX/NFXListController_OSX.swift +++ b/netfox/OSX/NFXListController_OSX.swift @@ -24,10 +24,11 @@ class NFXListController_OSX: NFXListController, NSTableViewDelegate, NSTableView // MARK: View Life Cycle override func awakeFromNib() { + let bundle = Bundle(for: type(of: self)) #if !swift(>=4.0) - tableView.register(NSNib(nibNamed: cellIdentifier, bundle: nil), forIdentifier: cellIdentifier) + tableView.register(NSNib(nibNamed: cellIdentifier, bundle: bundle), forIdentifier: cellIdentifier) #else - tableView.register(NSNib(nibNamed: NSNib.Name(rawValue: cellIdentifier), bundle: nil), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier)) + tableView.register(NSNib(nibNamed: NSNib.Name(rawValue: cellIdentifier), bundle: bundle), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier)) #endif searchField.delegate = self @@ -57,7 +58,7 @@ class NFXListController_OSX: NFXListController, NSTableViewDelegate, NSTableView reloadTableViewData() } - func controlTextDidChange(obj: NSNotification) { + @objc override func controlTextDidChange(_ obj: Notification) { guard let searchField = obj.object as? NSSearchField else { return } @@ -68,7 +69,7 @@ class NFXListController_OSX: NFXListController, NSTableViewDelegate, NSTableView // MARK: UITableViewDataSource - func numberOfRowsInTableView(tableView: NSTableView) -> Int { + func numberOfRows(in tableView: NSTableView) -> Int { if (self.isSearchControllerActive) { return self.filteredTableData.count } else { diff --git a/netfox/OSX/NFXPathNodeListCell_OSX.swift b/netfox/OSX/NFXPathNodeListCell_OSX.swift new file mode 100644 index 00000000..e9d1a3d8 --- /dev/null +++ b/netfox/OSX/NFXPathNodeListCell_OSX.swift @@ -0,0 +1,99 @@ +// +// NFXPathNodeListCell_OSX.swift +// netfox_ios +// +// Created by Ștefan Suciu on 2/6/18. +// Copyright © 2018 kasketis. All rights reserved. +// + +#if os(OSX) + +import Cocoa + +let cloudImage = NFXImage.cloud +let folderImage = NFXImage.folder +let fileDownloadingImage = NFXImage.fileDownloading +let fileSuccessImage = NFXImage.fileSuccess +let fileWarningImage = NFXImage.fileWarning +let fileUnauthorizedImage = NFXImage.fileUnauthorized +let serverErrorImage = NFXImage.serverError + +class NFXPathNodeListCell_OSX: NSTableCellView { + + @IBOutlet var statusView: NSView! + @IBOutlet var URLLabel: NSTextField! + @IBOutlet var _imageView: NSImageView! + + @IBOutlet var circleView: NSView! + @IBOutlet weak var leadingConstraint: NSLayoutConstraint! + + let padding: CGFloat = 5 + + // MARK: Life cycle + + override func awakeFromNib() { + layer?.backgroundColor = NFXColor.clear.cgColor + + circleView.layer?.backgroundColor = NSColor.NFXGray44Color().cgColor + circleView.layer?.cornerRadius = 4 + circleView.alphaValue = 0.2 + URLLabel.font = NSFont.NFXFont(size: 12) + } + + func isNew() { + circleView.isHidden = false + } + + func isOld() { + circleView.isHidden = true + } + + func configForObject(obj: NFXPathNode) { + leadingConstraint.constant = CGFloat(obj.depth()) * 16.0 + URLLabel.stringValue = obj.name + + if let httpModel = obj.httpModel { + configForObject(obj: httpModel) + } else if obj.parent?.parent == nil { + _imageView.image = cloudImage + } else { + _imageView.image = folderImage + } + } + + func configForObject(obj: NFXHTTPModel) { + setStatus(status: obj.responseStatus) + isNewBasedOnDate(responseDate: obj.responseDate as NSDate? ?? NSDate()) + } + + func setStatus(status: Int?) { + guard let status = status else { + _imageView.image = fileWarningImage + return + } + + if status >= 200 && status < 300 { + _imageView.image = fileSuccessImage + } else if status >= 400 && status < 500 { + if status == 403 { + _imageView.image = fileUnauthorizedImage + } else { + _imageView.image = fileWarningImage + } + } else if status >= 500 && status < 600 { + _imageView.image = serverErrorImage + } else { + _imageView.image = fileDownloadingImage + } + } + + func isNewBasedOnDate(responseDate: NSDate) { + if responseDate.isGreaterThan(NFX.sharedInstance().getLastVisitDate()) { + isNew() + } else { + isOld() + } + } +} + +#endif diff --git a/netfox/OSX/NFXPathNodeListCell_OSX.xib b/netfox/OSX/NFXPathNodeListCell_OSX.xib new file mode 100755 index 00000000..11dc8833 --- /dev/null +++ b/netfox/OSX/NFXPathNodeListCell_OSX.xib @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label Multiline Label + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/netfox/OSX/NFXPathNodeListController_OSX.swift b/netfox/OSX/NFXPathNodeListController_OSX.swift new file mode 100644 index 00000000..fdc3749f --- /dev/null +++ b/netfox/OSX/NFXPathNodeListController_OSX.swift @@ -0,0 +1,144 @@ +// +// NFXPathNodeListController_OSX.swift +// netfox_osx +// +// Created by Ștefan Suciu on 2/6/18. +// Copyright © 2018 kasketis. All rights reserved. +// + +#if os(OSX) + +import Cocoa + +class NFXPathNodeListController_OSX: NFXListController, NSTableViewDelegate, NSTableViewDataSource, NSSearchFieldDelegate { + + // MARK: Properties + + @IBOutlet var searchField: NSTextField! + @IBOutlet var tableView: NSTableView! + + var isSearchControllerActive: Bool = false + var delegate: NFXWindowControllerDelegate? + + private let cellIdentifier = "NFXPathNodeListCell_OSX" + + fileprivate let modelManager = NFXPathNodeManager.sharedInstance + fileprivate var pathNodeTableData: [NFXPathNode] = [] + + // MARK: View Life Cycle + + override func awakeFromNib() { + let bundle = Bundle(for: type(of: self)) + if Bundle.main.bundleIdentifier == "com.tapptitude.netfox-mac" { + #if !swift(>=4.0) + tableView.register(NSNib(nibNamed: cellIdentifier, bundle: bundle), forIdentifier: cellIdentifier) + #else + tableView.register(NSNib(nibNamed: NSNib.Name(rawValue: cellIdentifier), bundle: bundle), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier)) + #endif + } + searchField.delegate = self + + NotificationCenter.default.addObserver(self, selector: #selector(NFXListController.reloadTableViewData), name: .NFXReloadData, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(NFXPathNodeListController_OSX.deactivateSearchController), name: .NFXDeactivateSearch, object: nil) + + pathNodeTableData = modelManager.getTableModels() + } + + // MARK: Notifications + + override func reloadTableViewData() { + DispatchQueue.main.async { + if self.searchField.stringValue.isEmpty { + self.pathNodeTableData = self.modelManager.getTableModels() + } else { + let filtered = self.filter(models: self.modelManager.getHttpModels(), searchString: self.searchField.stringValue) + let model = NFXPathNodeManager() + model.add(filtered) + self.pathNodeTableData = model.getTableModels() + } + + self.tableView.reloadData() + } + } + + @objc func deactivateSearchController() { + isSearchControllerActive = false + } + + // MARK: Search + + func filter(models: [NFXHTTPModel], searchString: String) -> [NFXHTTPModel] { + let predicateURL = NSPredicate(format: "requestURL contains[cd] '\(searchString)'") + let predicateMethod = NSPredicate(format: "requestMethod contains[cd] '\(searchString)'") + let predicateType = NSPredicate(format: "responseType contains[cd] '\(searchString)'") + let predicates = [predicateURL, predicateMethod, predicateType] + let searchPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicates) + + let array = (models as NSArray).filtered(using: searchPredicate) + return array as! [NFXHTTPModel] + } + + func updateSearchResultsForSearchController() { + updateSearchResultsForSearchControllerWithString(searchField.stringValue) + reloadTableViewData() + } + + @objc override func controlTextDidChange(_ obj: Notification) { + guard let searchField = obj.object as? NSSearchField else { + return + } + + isSearchControllerActive = searchField.stringValue.count > 0 + updateSearchResultsForSearchController() + } + + // MARK: UITableViewDataSource + + func numberOfRows(in tableView: NSTableView) -> Int { + return pathNodeTableData.count + } + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + #if !swift(>=4.0) + guard let cell = tableView.make(withIdentifier: cellIdentifier, owner: nil) as? NFXPathNodeListCell_OSX else { + return nil + } #else + guard let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: nil) as? NFXPathNodeListCell_OSX else { + return nil + } #endif + + let obj = pathNodeTableData[row] + cell.configForObject(obj: obj) + + return cell + } + + // MARK: NSTableViewDelegate + + func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { + return 20 + } + + func tableViewSelectionDidChange(_ notification: Notification) { + guard tableView.selectedRow >= 0 else { + return + } + + if let httpModel = pathNodeTableData[tableView.selectedRow].httpModel { + delegate?.httpModelSelectedDidChange(model: httpModel) + } else { + let node = pathNodeTableData[tableView.selectedRow] + if !node.isExpanded { + node.isExpanded = true + pathNodeTableData.insert(contentsOf: node.children, at: tableView.selectedRow + 1) + reloadTableViewData() + } else { + node.isExpanded = false + reloadTableViewData() + } + } + } +} + +#endif + diff --git a/netfox/OSX/NFXResponseTypeCell_OSX.xib b/netfox/OSX/NFXResponseTypeCell_OSX.xib index 239171a2..78de7b31 100644 --- a/netfox/OSX/NFXResponseTypeCell_OSX.xib +++ b/netfox/OSX/NFXResponseTypeCell_OSX.xib @@ -1,8 +1,9 @@ - - + + - + + diff --git a/netfox/OSX/NFXSettingsController_OSX.swift b/netfox/OSX/NFXSettingsController_OSX.swift old mode 100644 new mode 100755 index b8901a94..74f69ec5 --- a/netfox/OSX/NFXSettingsController_OSX.swift +++ b/netfox/OSX/NFXSettingsController_OSX.swift @@ -25,11 +25,12 @@ class NFXSettingsController_OSX: NFXSettingsController, NSTableViewDataSource, N nfxVersionLabel.stringValue = nfxVersionString nfxURLButton.title = nfxURL + let bundle = Bundle(for: type(of:self)) #if !swift(>=4.0) - responseTypesTableView.register(NSNib(nibNamed: cellIdentifier, bundle: nil), forIdentifier: cellIdentifier) + responseTypesTableView.register(NSNib(nibNamed: cellIdentifier, bundle: bundle), forIdentifier: cellIdentifier) #else - responseTypesTableView.register(NSNib(nibNamed: NSNib.Name(rawValue: cellIdentifier), bundle: nil), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier)) + responseTypesTableView.register(NSNib(nibNamed: NSNib.Name(rawValue: cellIdentifier), bundle: bundle), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier)) #endif reloadTableData() @@ -42,7 +43,7 @@ class NFXSettingsController_OSX: NFXSettingsController, NSTableViewDataSource, N // MARK: Actions - @IBAction func loggingButtonClicked(sender: NSButton) { + @IBAction func loggingButtonClicked(_ sender: NSButton) { var senderStateOn: Bool #if !swift(>=4.0) senderStateOn = sender.state == NSControlStateValueOn @@ -57,12 +58,13 @@ class NFXSettingsController_OSX: NFXSettingsController, NSTableViewDataSource, N } } - @IBAction func clearDataClicked(sender: AnyObject?) { + @IBAction func clearDataClicked(_ sender: AnyObject?) { NFX.sharedInstance().clearOldData() NotificationCenter.default.post(name: NSNotification.Name.NFXReloadData, object: nil) } - @IBAction func nfxURLButtonClicked(sender: NSButton) { + + @IBAction func nfxURLButtonClicked(_ sender: NSButton) { #if !swift(>=4.0) NSWorkspace.shared().open(NSURL(string: nfxURL)! as URL) #else @@ -70,7 +72,7 @@ class NFXSettingsController_OSX: NFXSettingsController, NSTableViewDataSource, N #endif } - @IBAction func toggleResponseTypeClicked(sender: NSButton) { + @IBAction func toggleResponseTypeClicked(_ sender: NSButton) { filters[sender.tag] = !filters[sender.tag] NFX.sharedInstance().cacheFilters(filters) NotificationCenter.default.post(name: NSNotification.Name.NFXReloadData, object: nil) @@ -84,7 +86,7 @@ class NFXSettingsController_OSX: NFXSettingsController, NSTableViewDataSource, N // MARK: Table View Delegate and DataSource - func numberOfRowsInTableView(tableView: NSTableView) -> Int { + func numberOfRows(in tableView: NSTableView) -> Int { return tableData.count } @@ -108,7 +110,7 @@ class NFXSettingsController_OSX: NFXSettingsController, NSTableViewDataSource, N #endif cell.activeCheckbox.tag = row cell.activeCheckbox.target = self - cell.activeCheckbox.action = #selector(toggleResponseTypeClicked(sender:)) + cell.activeCheckbox.action = #selector(toggleResponseTypeClicked(_:)) return cell } diff --git a/netfox/OSX/NetfoxWindow.xib b/netfox/OSX/NetfoxWindow.xib old mode 100644 new mode 100755 index 77305834..bf3e9ece --- a/netfox/OSX/NetfoxWindow.xib +++ b/netfox/OSX/NetfoxWindow.xib @@ -1,8 +1,9 @@ - - + + - + + @@ -15,6 +16,8 @@ + + @@ -23,6 +26,8 @@ + + @@ -30,22 +35,40 @@ - - + + - + - + - + + + + + + + + + + + + + + + + + + + - +