├── .gitignore ├── .travis.yml ├── .xctool-args ├── LICENSE ├── Podfile ├── README.md ├── StompKit.podspec ├── StompKit.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── StompKit.xcscheme ├── StompKit ├── StompKit.h └── StompKit.m └── StompKitTests ├── StompKitTests-Info.plist ├── StompKitTests.h ├── StompKitTests.m └── en.lproj └── InfoPlist.strings /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build 4 | */build/* 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | *.xcworkspace 14 | !default.xcworkspace 15 | xcuserdata 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | .idea/ 20 | *.hmap 21 | *.xccheckout 22 | 23 | #CocoaPods 24 | Podfile.lock 25 | Pods/ 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | before_install: 4 | - gem install cocoapods 5 | - brew update 6 | - brew uninstall xctool && brew install xctool 7 | 8 | script: xctool build ONLY_ACTIVE_ARCH=NO 9 | -------------------------------------------------------------------------------- /.xctool-args: -------------------------------------------------------------------------------- 1 | [ 2 | "-workspace", "StompKit.xcworkspace", 3 | "-scheme", "StompKit", 4 | "-sdk", "iphonesimulator", 5 | "-arch", "i386" 6 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | xcodeproj 'StompKit.xcodeproj' 2 | 3 | platform :ios, '5.0' 4 | 5 | pod 'CocoaAsyncSocket', '7.3.2' 6 | 7 | target 'StompKitTests', :exclusive => true do 8 | pod 'Kiwi', '2.2' 9 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | StompKit 2 | ======== 3 | 4 | STOMP Objective-C Client for iOS 5 | 6 | StompKit is a rewrite of [objc-stomp](https://github.com/juretta/objc-stomp) to create a modern event-driven Objective-C library using ARC, Grand Central Dispatch and blocks. 7 | 8 | This library uses the Grand Central Dispatch version of [CocoaAsyncSocket](https://github.com/robbiehanson/CocoaAsyncSocket). 9 | 10 | # Project Status 11 | 12 | __This project is _no longer maintained_ ([some context about this decision](http://jmesnil.net/weblog/2015/09/04/stepping-out-from-personal-open-source-projects/)).__ 13 | 14 | __If you encounter bugs with it or need enhancements, you can fork it and modify it as the project is under the Apache License 2.0.__ 15 | 16 | [![Build Status](https://travis-ci.org/mobile-web-messaging/StompKit.png?branch=master)](https://travis-ci.org/mobile-web-messaging/StompKit) 17 | 18 | # Installation 19 | 20 | ## Using CocoaPods 21 | 22 | StompKit is available on [CocoaPods](http://cocoapods.org/?q=StompKit). 23 | 24 | 25 | On your ```Podfile``` add this project: 26 | 27 | ``` 28 | ... 29 | pod 'StompKit', :git => 'https://github.com/mobile-web-messaging/StompKit.git' 30 | ... 31 | ``` 32 | 33 | For the first time, run ```pod install```, if you are updating the project invoke ```pod update```. 34 | 35 | # Usage 36 | 37 | Import the `StompKit.h` header file 38 | 39 | ```objc 40 | #import 41 | ``` 42 | 43 | Send a message: 44 | 45 | ```objc 46 | // create the client 47 | STOMPClient *client = [[STOMPClient alloc] initWithHost:@"localhost" 48 | port:61613]; 49 | // connect to the broker 50 | [client connectWithLogin:@"mylogin" 51 | passcode:@"mypassword" 52 | completionHandler:^(STOMPFrame *_, NSError *error) { 53 | if (err) { 54 | NSLog(@"%@", error); 55 | return; 56 | } 57 | 58 | // send a message 59 | [client sendTo:@"/queue/myqueue" body:@"Hello, iOS!"]; 60 | // and disconnect 61 | [client disconnect]; 62 | }]; 63 | ``` 64 | 65 | Subscribe to receive message: 66 | 67 | ```objc 68 | // create the client 69 | STOMPClient *client = [[STOMPClient alloc] initWithHost:@"localhost" 70 | port:61613]; 71 | // connect to the broker 72 | [client connectWithLogin:@"mylogin" 73 | passcode:@"mypassword" 74 | completionHandler:^(STOMPFrame *_, NSError *error) { 75 | if (err) { 76 | NSLog(@"%@", error); 77 | return; 78 | } 79 | 80 | // subscribe to the destination 81 | [client subscribeTo:@"/queue/myqueue" 82 | headers:@{@"selector": @"color = 'red'"} 83 | messageHandler:^(STOMPMessage *message) { 84 | // callback when the client receive a message 85 | // for the /queue/myqueue destination 86 | 87 | NSLog(@"got message %@", message.body); // => "Hello, iOS" 88 | }]; 89 | }]; 90 | ``` 91 | 92 | # Authors 93 | 94 | * [Jeff Mesnil](http://jmesnil.net/) 95 | 96 | -------------------------------------------------------------------------------- /StompKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "StompKit" 3 | s.version = "0.1.1" 4 | s.summary = "STOMP Objective-C Client for iOS." 5 | s.homepage = "https://github.com/mobile-web-messaging/StompKit" 6 | s.license = 'Apache License, Version 2.0' 7 | s.author = "Jeff Mesnil" 8 | s.source = { :git => 'https://github.com/mobile-web-messaging/StompKit.git', :tag => "#{s.version}" } 9 | s.platform = :ios, 5.0 10 | s.source_files = 'StompKit/*.{h,m}' 11 | s.public_header_files = 'StompKit/StompKit.h' 12 | s.requires_arc = true 13 | s.dependency 'CocoaAsyncSocket', '7.3.4' 14 | end 15 | -------------------------------------------------------------------------------- /StompKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6C24B5EB8E0343E483E53CA2 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37A4F2569A574CA6A0704DA6 /* libPods.a */; }; 11 | 932BA57C18056C4B00A03257 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 932BA57B18056C4B00A03257 /* Foundation.framework */; }; 12 | 932BA58118056C4B00A03257 /* StompKit.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 932BA58018056C4B00A03257 /* StompKit.h */; }; 13 | 932BA58318056C4B00A03257 /* StompKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 932BA58218056C4B00A03257 /* StompKit.m */; }; 14 | 932BA58A18056C4C00A03257 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 932BA58918056C4C00A03257 /* XCTest.framework */; }; 15 | 932BA58B18056C4C00A03257 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 932BA57B18056C4B00A03257 /* Foundation.framework */; }; 16 | 932BA58D18056C4C00A03257 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 932BA58C18056C4C00A03257 /* UIKit.framework */; }; 17 | 932BA59018056C4C00A03257 /* libStompKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 932BA57818056C4B00A03257 /* libStompKit.a */; }; 18 | 932BA59618056C4C00A03257 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 932BA59418056C4C00A03257 /* InfoPlist.strings */; }; 19 | 932BA59818056C4C00A03257 /* StompKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 932BA59718056C4C00A03257 /* StompKitTests.m */; }; 20 | D1313E2FF21A4E15A007CA43 /* libPods-StompKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 591868DBFE214F55AB478DA7 /* libPods-StompKitTests.a */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 932BA58E18056C4C00A03257 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 932BA57018056C4B00A03257 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 932BA57718056C4B00A03257; 29 | remoteInfo = StompKit; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXCopyFilesBuildPhase section */ 34 | 932BA57618056C4B00A03257 /* CopyFiles */ = { 35 | isa = PBXCopyFilesBuildPhase; 36 | buildActionMask = 2147483647; 37 | dstPath = "include/$(PRODUCT_NAME)"; 38 | dstSubfolderSpec = 16; 39 | files = ( 40 | 932BA58118056C4B00A03257 /* StompKit.h in CopyFiles */, 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXCopyFilesBuildPhase section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | 37A4F2569A574CA6A0704DA6 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 44F558268B504F65BBF4384B /* Pods-StompKitTests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StompKitTests.xcconfig"; path = "Pods/Pods-StompKitTests.xcconfig"; sourceTree = ""; }; 49 | 591868DBFE214F55AB478DA7 /* libPods-StompKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-StompKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 9320F3901816698700FF599F /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = ""; }; 51 | 9320F3911816698700FF599F /* StompKit.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = StompKit.podspec; sourceTree = ""; }; 52 | 932BA57818056C4B00A03257 /* libStompKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libStompKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 932BA57B18056C4B00A03257 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 54 | 932BA58018056C4B00A03257 /* StompKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StompKit.h; sourceTree = ""; }; 55 | 932BA58218056C4B00A03257 /* StompKit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StompKit.m; sourceTree = ""; }; 56 | 932BA58818056C4C00A03257 /* StompKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StompKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 932BA58918056C4C00A03257 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 58 | 932BA58C18056C4C00A03257 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 59 | 932BA59318056C4C00A03257 /* StompKitTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "StompKitTests-Info.plist"; sourceTree = ""; }; 60 | 932BA59518056C4C00A03257 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 61 | 932BA59718056C4C00A03257 /* StompKitTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StompKitTests.m; sourceTree = ""; }; 62 | 938827ED18056CA2009A1164 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 63 | 938827EE18056CA2009A1164 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = ""; }; 64 | 938827F218057877009A1164 /* StompKitTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StompKitTests.h; sourceTree = ""; }; 65 | BFE05B97D82D403E8462EDC5 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | 932BA57518056C4B00A03257 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | 932BA57C18056C4B00A03257 /* Foundation.framework in Frameworks */, 74 | 6C24B5EB8E0343E483E53CA2 /* libPods.a in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | 932BA58518056C4C00A03257 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 932BA58A18056C4C00A03257 /* XCTest.framework in Frameworks */, 83 | 932BA58D18056C4C00A03257 /* UIKit.framework in Frameworks */, 84 | 932BA59018056C4C00A03257 /* libStompKit.a in Frameworks */, 85 | 932BA58B18056C4C00A03257 /* Foundation.framework in Frameworks */, 86 | D1313E2FF21A4E15A007CA43 /* libPods-StompKitTests.a in Frameworks */, 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | /* End PBXFrameworksBuildPhase section */ 91 | 92 | /* Begin PBXGroup section */ 93 | 932BA56F18056C4B00A03257 = { 94 | isa = PBXGroup; 95 | children = ( 96 | 9320F3901816698700FF599F /* Podfile */, 97 | 9320F3911816698700FF599F /* StompKit.podspec */, 98 | 938827ED18056CA2009A1164 /* LICENSE */, 99 | 938827EE18056CA2009A1164 /* README.md */, 100 | 932BA57D18056C4B00A03257 /* StompKit */, 101 | 932BA59118056C4C00A03257 /* StompKitTests */, 102 | 932BA57A18056C4B00A03257 /* Frameworks */, 103 | 932BA57918056C4B00A03257 /* Products */, 104 | BFE05B97D82D403E8462EDC5 /* Pods.xcconfig */, 105 | 44F558268B504F65BBF4384B /* Pods-StompKitTests.xcconfig */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | 932BA57918056C4B00A03257 /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 932BA57818056C4B00A03257 /* libStompKit.a */, 113 | 932BA58818056C4C00A03257 /* StompKitTests.xctest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 932BA57A18056C4B00A03257 /* Frameworks */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 932BA57B18056C4B00A03257 /* Foundation.framework */, 122 | 932BA58918056C4C00A03257 /* XCTest.framework */, 123 | 932BA58C18056C4C00A03257 /* UIKit.framework */, 124 | 37A4F2569A574CA6A0704DA6 /* libPods.a */, 125 | 591868DBFE214F55AB478DA7 /* libPods-StompKitTests.a */, 126 | ); 127 | name = Frameworks; 128 | sourceTree = ""; 129 | }; 130 | 932BA57D18056C4B00A03257 /* StompKit */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 932BA58018056C4B00A03257 /* StompKit.h */, 134 | 932BA58218056C4B00A03257 /* StompKit.m */, 135 | ); 136 | path = StompKit; 137 | sourceTree = ""; 138 | }; 139 | 932BA59118056C4C00A03257 /* StompKitTests */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 938827F218057877009A1164 /* StompKitTests.h */, 143 | 932BA59718056C4C00A03257 /* StompKitTests.m */, 144 | 932BA59218056C4C00A03257 /* Supporting Files */, 145 | ); 146 | path = StompKitTests; 147 | sourceTree = ""; 148 | }; 149 | 932BA59218056C4C00A03257 /* Supporting Files */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 932BA59318056C4C00A03257 /* StompKitTests-Info.plist */, 153 | 932BA59418056C4C00A03257 /* InfoPlist.strings */, 154 | ); 155 | name = "Supporting Files"; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXGroup section */ 159 | 160 | /* Begin PBXNativeTarget section */ 161 | 932BA57718056C4B00A03257 /* StompKit */ = { 162 | isa = PBXNativeTarget; 163 | buildConfigurationList = 932BA59B18056C4C00A03257 /* Build configuration list for PBXNativeTarget "StompKit" */; 164 | buildPhases = ( 165 | F14EDA9ACA514E238C2E405D /* Check Pods Manifest.lock */, 166 | 932BA57418056C4B00A03257 /* Sources */, 167 | 932BA57518056C4B00A03257 /* Frameworks */, 168 | 932BA57618056C4B00A03257 /* CopyFiles */, 169 | F35F0CAAF0E740C2A96A5AC7 /* Copy Pods Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | ); 175 | name = StompKit; 176 | productName = StompKit; 177 | productReference = 932BA57818056C4B00A03257 /* libStompKit.a */; 178 | productType = "com.apple.product-type.library.static"; 179 | }; 180 | 932BA58718056C4C00A03257 /* StompKitTests */ = { 181 | isa = PBXNativeTarget; 182 | buildConfigurationList = 932BA59E18056C4C00A03257 /* Build configuration list for PBXNativeTarget "StompKitTests" */; 183 | buildPhases = ( 184 | 125CC95EDE78443AA2A8A7CA /* Check Pods Manifest.lock */, 185 | 932BA58418056C4C00A03257 /* Sources */, 186 | 932BA58518056C4C00A03257 /* Frameworks */, 187 | 932BA58618056C4C00A03257 /* Resources */, 188 | D78F70268CF740B1A5B2133D /* Copy Pods Resources */, 189 | ); 190 | buildRules = ( 191 | ); 192 | dependencies = ( 193 | 932BA58F18056C4C00A03257 /* PBXTargetDependency */, 194 | ); 195 | name = StompKitTests; 196 | productName = StompKitTests; 197 | productReference = 932BA58818056C4C00A03257 /* StompKitTests.xctest */; 198 | productType = "com.apple.product-type.bundle.unit-test"; 199 | }; 200 | /* End PBXNativeTarget section */ 201 | 202 | /* Begin PBXProject section */ 203 | 932BA57018056C4B00A03257 /* Project object */ = { 204 | isa = PBXProject; 205 | attributes = { 206 | LastUpgradeCheck = 0500; 207 | ORGANIZATIONNAME = "Jeff Mesnil"; 208 | }; 209 | buildConfigurationList = 932BA57318056C4B00A03257 /* Build configuration list for PBXProject "StompKit" */; 210 | compatibilityVersion = "Xcode 3.2"; 211 | developmentRegion = English; 212 | hasScannedForEncodings = 0; 213 | knownRegions = ( 214 | en, 215 | ); 216 | mainGroup = 932BA56F18056C4B00A03257; 217 | productRefGroup = 932BA57918056C4B00A03257 /* Products */; 218 | projectDirPath = ""; 219 | projectRoot = ""; 220 | targets = ( 221 | 932BA57718056C4B00A03257 /* StompKit */, 222 | 932BA58718056C4C00A03257 /* StompKitTests */, 223 | ); 224 | }; 225 | /* End PBXProject section */ 226 | 227 | /* Begin PBXResourcesBuildPhase section */ 228 | 932BA58618056C4C00A03257 /* Resources */ = { 229 | isa = PBXResourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 932BA59618056C4C00A03257 /* InfoPlist.strings in Resources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXResourcesBuildPhase section */ 237 | 238 | /* Begin PBXShellScriptBuildPhase section */ 239 | 125CC95EDE78443AA2A8A7CA /* Check Pods Manifest.lock */ = { 240 | isa = PBXShellScriptBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | ); 244 | inputPaths = ( 245 | ); 246 | name = "Check Pods Manifest.lock"; 247 | outputPaths = ( 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | shellPath = /bin/sh; 251 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 252 | showEnvVarsInLog = 0; 253 | }; 254 | D78F70268CF740B1A5B2133D /* Copy Pods Resources */ = { 255 | isa = PBXShellScriptBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | ); 259 | inputPaths = ( 260 | ); 261 | name = "Copy Pods Resources"; 262 | outputPaths = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | shellPath = /bin/sh; 266 | shellScript = "\"${SRCROOT}/Pods/Pods-StompKitTests-resources.sh\"\n"; 267 | showEnvVarsInLog = 0; 268 | }; 269 | F14EDA9ACA514E238C2E405D /* Check Pods Manifest.lock */ = { 270 | isa = PBXShellScriptBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | ); 274 | inputPaths = ( 275 | ); 276 | name = "Check Pods Manifest.lock"; 277 | outputPaths = ( 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | shellPath = /bin/sh; 281 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 282 | showEnvVarsInLog = 0; 283 | }; 284 | F35F0CAAF0E740C2A96A5AC7 /* Copy Pods Resources */ = { 285 | isa = PBXShellScriptBuildPhase; 286 | buildActionMask = 2147483647; 287 | files = ( 288 | ); 289 | inputPaths = ( 290 | ); 291 | name = "Copy Pods Resources"; 292 | outputPaths = ( 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | shellPath = /bin/sh; 296 | shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n"; 297 | showEnvVarsInLog = 0; 298 | }; 299 | /* End PBXShellScriptBuildPhase section */ 300 | 301 | /* Begin PBXSourcesBuildPhase section */ 302 | 932BA57418056C4B00A03257 /* Sources */ = { 303 | isa = PBXSourcesBuildPhase; 304 | buildActionMask = 2147483647; 305 | files = ( 306 | 932BA58318056C4B00A03257 /* StompKit.m in Sources */, 307 | ); 308 | runOnlyForDeploymentPostprocessing = 0; 309 | }; 310 | 932BA58418056C4C00A03257 /* Sources */ = { 311 | isa = PBXSourcesBuildPhase; 312 | buildActionMask = 2147483647; 313 | files = ( 314 | 932BA59818056C4C00A03257 /* StompKitTests.m in Sources */, 315 | ); 316 | runOnlyForDeploymentPostprocessing = 0; 317 | }; 318 | /* End PBXSourcesBuildPhase section */ 319 | 320 | /* Begin PBXTargetDependency section */ 321 | 932BA58F18056C4C00A03257 /* PBXTargetDependency */ = { 322 | isa = PBXTargetDependency; 323 | target = 932BA57718056C4B00A03257 /* StompKit */; 324 | targetProxy = 932BA58E18056C4C00A03257 /* PBXContainerItemProxy */; 325 | }; 326 | /* End PBXTargetDependency section */ 327 | 328 | /* Begin PBXVariantGroup section */ 329 | 932BA59418056C4C00A03257 /* InfoPlist.strings */ = { 330 | isa = PBXVariantGroup; 331 | children = ( 332 | 932BA59518056C4C00A03257 /* en */, 333 | ); 334 | name = InfoPlist.strings; 335 | sourceTree = ""; 336 | }; 337 | /* End PBXVariantGroup section */ 338 | 339 | /* Begin XCBuildConfiguration section */ 340 | 932BA59918056C4C00A03257 /* Debug */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ALWAYS_SEARCH_USER_PATHS = NO; 344 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 346 | CLANG_CXX_LIBRARY = "libc++"; 347 | CLANG_ENABLE_MODULES = YES; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BOOL_CONVERSION = YES; 350 | CLANG_WARN_CONSTANT_CONVERSION = YES; 351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 352 | CLANG_WARN_EMPTY_BODY = YES; 353 | CLANG_WARN_ENUM_CONVERSION = YES; 354 | CLANG_WARN_INT_CONVERSION = YES; 355 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 356 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 357 | COPY_PHASE_STRIP = NO; 358 | GCC_C_LANGUAGE_STANDARD = gnu99; 359 | GCC_DYNAMIC_NO_PIC = NO; 360 | GCC_OPTIMIZATION_LEVEL = 0; 361 | GCC_PREPROCESSOR_DEFINITIONS = ( 362 | "DEBUG=1", 363 | "$(inherited)", 364 | ); 365 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 366 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 367 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 368 | GCC_WARN_UNDECLARED_SELECTOR = YES; 369 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 370 | GCC_WARN_UNUSED_FUNCTION = YES; 371 | GCC_WARN_UNUSED_VARIABLE = YES; 372 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 373 | ONLY_ACTIVE_ARCH = YES; 374 | SDKROOT = iphoneos; 375 | }; 376 | name = Debug; 377 | }; 378 | 932BA59A18056C4C00A03257 /* Release */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | ALWAYS_SEARCH_USER_PATHS = NO; 382 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 383 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 384 | CLANG_CXX_LIBRARY = "libc++"; 385 | CLANG_ENABLE_MODULES = YES; 386 | CLANG_ENABLE_OBJC_ARC = YES; 387 | CLANG_WARN_BOOL_CONVERSION = YES; 388 | CLANG_WARN_CONSTANT_CONVERSION = YES; 389 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 390 | CLANG_WARN_EMPTY_BODY = YES; 391 | CLANG_WARN_ENUM_CONVERSION = YES; 392 | CLANG_WARN_INT_CONVERSION = YES; 393 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 394 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 395 | COPY_PHASE_STRIP = YES; 396 | ENABLE_NS_ASSERTIONS = NO; 397 | GCC_C_LANGUAGE_STANDARD = gnu99; 398 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 399 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 400 | GCC_WARN_UNDECLARED_SELECTOR = YES; 401 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 402 | GCC_WARN_UNUSED_FUNCTION = YES; 403 | GCC_WARN_UNUSED_VARIABLE = YES; 404 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 405 | SDKROOT = iphoneos; 406 | VALIDATE_PRODUCT = YES; 407 | }; 408 | name = Release; 409 | }; 410 | 932BA59C18056C4C00A03257 /* Debug */ = { 411 | isa = XCBuildConfiguration; 412 | baseConfigurationReference = BFE05B97D82D403E8462EDC5 /* Pods.xcconfig */; 413 | buildSettings = { 414 | DSTROOT = /tmp/StompKit.dst; 415 | OTHER_LDFLAGS = "-ObjC"; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | SKIP_INSTALL = YES; 418 | }; 419 | name = Debug; 420 | }; 421 | 932BA59D18056C4C00A03257 /* Release */ = { 422 | isa = XCBuildConfiguration; 423 | baseConfigurationReference = BFE05B97D82D403E8462EDC5 /* Pods.xcconfig */; 424 | buildSettings = { 425 | DSTROOT = /tmp/StompKit.dst; 426 | OTHER_LDFLAGS = "-ObjC"; 427 | PRODUCT_NAME = "$(TARGET_NAME)"; 428 | SKIP_INSTALL = YES; 429 | }; 430 | name = Release; 431 | }; 432 | 932BA59F18056C4C00A03257 /* Debug */ = { 433 | isa = XCBuildConfiguration; 434 | baseConfigurationReference = 44F558268B504F65BBF4384B /* Pods-StompKitTests.xcconfig */; 435 | buildSettings = { 436 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 437 | FRAMEWORK_SEARCH_PATHS = ( 438 | "$(SDKROOT)/Developer/Library/Frameworks", 439 | "$(inherited)", 440 | "$(DEVELOPER_FRAMEWORKS_DIR)", 441 | ); 442 | GCC_PREPROCESSOR_DEFINITIONS = ( 443 | "DEBUG=1", 444 | "$(inherited)", 445 | ); 446 | INFOPLIST_FILE = "StompKitTests/StompKitTests-Info.plist"; 447 | PRODUCT_NAME = "$(TARGET_NAME)"; 448 | WRAPPER_EXTENSION = xctest; 449 | }; 450 | name = Debug; 451 | }; 452 | 932BA5A018056C4C00A03257 /* Release */ = { 453 | isa = XCBuildConfiguration; 454 | baseConfigurationReference = 44F558268B504F65BBF4384B /* Pods-StompKitTests.xcconfig */; 455 | buildSettings = { 456 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 457 | FRAMEWORK_SEARCH_PATHS = ( 458 | "$(SDKROOT)/Developer/Library/Frameworks", 459 | "$(inherited)", 460 | "$(DEVELOPER_FRAMEWORKS_DIR)", 461 | ); 462 | INFOPLIST_FILE = "StompKitTests/StompKitTests-Info.plist"; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | WRAPPER_EXTENSION = xctest; 465 | }; 466 | name = Release; 467 | }; 468 | /* End XCBuildConfiguration section */ 469 | 470 | /* Begin XCConfigurationList section */ 471 | 932BA57318056C4B00A03257 /* Build configuration list for PBXProject "StompKit" */ = { 472 | isa = XCConfigurationList; 473 | buildConfigurations = ( 474 | 932BA59918056C4C00A03257 /* Debug */, 475 | 932BA59A18056C4C00A03257 /* Release */, 476 | ); 477 | defaultConfigurationIsVisible = 0; 478 | defaultConfigurationName = Release; 479 | }; 480 | 932BA59B18056C4C00A03257 /* Build configuration list for PBXNativeTarget "StompKit" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | 932BA59C18056C4C00A03257 /* Debug */, 484 | 932BA59D18056C4C00A03257 /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | 932BA59E18056C4C00A03257 /* Build configuration list for PBXNativeTarget "StompKitTests" */ = { 490 | isa = XCConfigurationList; 491 | buildConfigurations = ( 492 | 932BA59F18056C4C00A03257 /* Debug */, 493 | 932BA5A018056C4C00A03257 /* Release */, 494 | ); 495 | defaultConfigurationIsVisible = 0; 496 | defaultConfigurationName = Release; 497 | }; 498 | /* End XCConfigurationList section */ 499 | }; 500 | rootObject = 932BA57018056C4B00A03257 /* Project object */; 501 | } 502 | -------------------------------------------------------------------------------- /StompKit.xcodeproj/xcshareddata/xcschemes/StompKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 52 | 53 | 54 | 55 | 61 | 62 | 64 | 65 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /StompKit/StompKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // StompKit.h 3 | // StompKit 4 | // 5 | // Created by Jeff Mesnil on 09/10/2013. 6 | // Copyright (c) 2013 Jeff Mesnil. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #pragma mark Frame headers 12 | 13 | #define kHeaderAcceptVersion @"accept-version" 14 | #define kHeaderAck @"ack" 15 | #define kHeaderContentLength @"content-length" 16 | #define kHeaderDestination @"destination" 17 | #define kHeaderHeartBeat @"heart-beat" 18 | #define kHeaderHost @"host" 19 | #define kHeaderID @"id" 20 | #define kHeaderLogin @"login" 21 | #define kHeaderMessage @"message" 22 | #define kHeaderPasscode @"passcode" 23 | #define kHeaderReceipt @"receipt" 24 | #define kHeaderReceiptID @"receipt-id" 25 | #define kHeaderSession @"session" 26 | #define kHeaderSubscription @"subscription" 27 | #define kHeaderTransaction @"transaction" 28 | 29 | #pragma mark Ack Header Values 30 | 31 | #define kAckAuto @"auto" 32 | #define kAckClient @"client" 33 | #define kAckClientIndividual @"client-individual" 34 | 35 | @class STOMPFrame; 36 | @class STOMPMessage; 37 | 38 | typedef void (^STOMPFrameHandler)(STOMPFrame *frame); 39 | typedef void (^STOMPMessageHandler)(STOMPMessage *message); 40 | 41 | #pragma mark STOMP Frame 42 | 43 | @interface STOMPFrame : NSObject 44 | 45 | @property (nonatomic, copy, readonly) NSString *command; 46 | @property (nonatomic, copy, readonly) NSDictionary *headers; 47 | @property (nonatomic, copy, readonly) NSString *body; 48 | 49 | @end 50 | 51 | #pragma mark STOMP Message 52 | 53 | @interface STOMPMessage : STOMPFrame 54 | 55 | - (void)ack; 56 | - (void)ack:(NSDictionary *)theHeaders; 57 | - (void)nack; 58 | - (void)nack:(NSDictionary *)theHeaders; 59 | 60 | @end 61 | 62 | #pragma mark STOMP Subscription 63 | 64 | @interface STOMPSubscription : NSObject 65 | 66 | @property (nonatomic, copy, readonly) NSString *identifier; 67 | 68 | - (void)unsubscribe; 69 | 70 | @end 71 | 72 | #pragma mark STOMP Transaction 73 | 74 | @interface STOMPTransaction : NSObject 75 | 76 | @property (nonatomic, copy, readonly) NSString *identifier; 77 | 78 | - (void)commit; 79 | - (void)abort; 80 | 81 | @end 82 | 83 | #pragma mark STOMP Client 84 | 85 | @interface STOMPClient : NSObject 86 | 87 | @property (nonatomic, copy) STOMPFrameHandler receiptHandler; 88 | @property (nonatomic, copy) void (^errorHandler)(NSError *error); 89 | @property (nonatomic, assign) BOOL connected; 90 | 91 | - (id)initWithHost:(NSString *)theHost 92 | port:(NSUInteger)thePort; 93 | 94 | - (void)connectWithLogin:(NSString *)login 95 | passcode:(NSString *)passcode 96 | completionHandler:(void (^)(STOMPFrame *connectedFrame, NSError *error))completionHandler; 97 | - (void)connectWithHeaders:(NSDictionary *)headers 98 | completionHandler:(void (^)(STOMPFrame *connectedFrame, NSError *error))completionHandler; 99 | 100 | - (void)sendTo:(NSString *)destination 101 | body:(NSString *)body; 102 | - (void)sendTo:(NSString *)destination 103 | headers:(NSDictionary *)headers 104 | body:(NSString *)body; 105 | 106 | - (STOMPSubscription *)subscribeTo:(NSString *)destination 107 | messageHandler:(STOMPMessageHandler)handler; 108 | - (STOMPSubscription *)subscribeTo:(NSString *)destination 109 | headers:(NSDictionary *)headers 110 | messageHandler:(STOMPMessageHandler)handler; 111 | 112 | - (STOMPTransaction *)begin; 113 | - (STOMPTransaction *)begin:(NSString *)identifier; 114 | 115 | - (void)disconnect; 116 | - (void)disconnect:(void (^)(NSError *error))completionHandler; 117 | 118 | @end -------------------------------------------------------------------------------- /StompKit/StompKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // StompKit.m 3 | // StompKit 4 | // 5 | // Created by Jeff Mesnil on 09/10/2013. 6 | // Copyright (c) 2013 Jeff Mesnil. All rights reserved. 7 | // 8 | 9 | #import "StompKit.h" 10 | #import "GCDAsyncSocket.h" 11 | 12 | #define kDefaultTimeout 5 13 | #define kVersion1_2 @"1.2" 14 | #define kNoHeartBeat @"0,0" 15 | 16 | #pragma mark Logging macros 17 | 18 | #if 0 // set to 1 to enable logs 19 | 20 | #define LogDebug(frmt, ...) NSLog(frmt, ##__VA_ARGS__); 21 | 22 | #else 23 | 24 | #define LogDebug(frmt, ...) {} 25 | 26 | #endif 27 | 28 | #pragma mark Frame commands 29 | 30 | #define kCommandAbort @"ABORT" 31 | #define kCommandAck @"ACK" 32 | #define kCommandBegin @"BEGIN" 33 | #define kCommandCommit @"COMMIT" 34 | #define kCommandConnect @"CONNECT" 35 | #define kCommandConnected @"CONNECTED" 36 | #define kCommandDisconnect @"DISCONNECT" 37 | #define kCommandError @"ERROR" 38 | #define kCommandMessage @"MESSAGE" 39 | #define kCommandNack @"NACK" 40 | #define kCommandReceipt @"RECEIPT" 41 | #define kCommandSend @"SEND" 42 | #define kCommandSubscribe @"SUBSCRIBE" 43 | #define kCommandUnsubscribe @"UNSUBSCRIBE" 44 | 45 | #pragma mark Control characters 46 | 47 | #define kLineFeed @"\x0A" 48 | #define kNullChar @"\x00" 49 | #define kHeaderSeparator @":" 50 | 51 | #pragma mark - 52 | #pragma mark STOMP Client private interface 53 | 54 | @interface STOMPClient() 55 | 56 | @property (nonatomic, retain) GCDAsyncSocket *socket; 57 | @property (nonatomic, copy) NSString *host; 58 | @property (nonatomic) NSUInteger port; 59 | @property (nonatomic) NSString *clientHeartBeat; 60 | @property (nonatomic, weak) NSTimer *pinger; 61 | @property (nonatomic, weak) NSTimer *ponger; 62 | 63 | @property (nonatomic, copy) void (^disconnectedHandler)(NSError *error); 64 | @property (nonatomic, copy) void (^connectionCompletionHandler)(STOMPFrame *connectedFrame, NSError *error); 65 | @property (nonatomic, retain) NSMutableDictionary *subscriptions; 66 | 67 | - (void) sendFrameWithCommand:(NSString *)command 68 | headers:(NSDictionary *)headers 69 | body:(NSString *)body; 70 | 71 | @end 72 | 73 | #pragma mark STOMP Frame 74 | 75 | @interface STOMPFrame() 76 | 77 | - (id)initWithCommand:(NSString *)theCommand 78 | headers:(NSDictionary *)theHeaders 79 | body:(NSString *)theBody; 80 | 81 | - (NSData *)toData; 82 | 83 | @end 84 | 85 | @implementation STOMPFrame 86 | 87 | @synthesize command, headers, body; 88 | 89 | - (id)initWithCommand:(NSString *)theCommand 90 | headers:(NSDictionary *)theHeaders 91 | body:(NSString *)theBody { 92 | if(self = [super init]) { 93 | command = theCommand; 94 | headers = theHeaders; 95 | body = theBody; 96 | } 97 | return self; 98 | } 99 | 100 | - (NSString *)toString { 101 | NSMutableString *frame = [NSMutableString stringWithString: [self.command stringByAppendingString:kLineFeed]]; 102 | for (id key in self.headers) { 103 | [frame appendString:[NSString stringWithFormat:@"%@%@%@%@", key, kHeaderSeparator, self.headers[key], kLineFeed]]; 104 | } 105 | [frame appendString:kLineFeed]; 106 | if (self.body) { 107 | [frame appendString:self.body]; 108 | } 109 | [frame appendString:kNullChar]; 110 | return frame; 111 | } 112 | 113 | - (NSData *)toData { 114 | return [[self toString] dataUsingEncoding:NSUTF8StringEncoding]; 115 | } 116 | 117 | + (STOMPFrame *) STOMPFrameFromData:(NSData *)data { 118 | NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length])]; 119 | NSString *msg = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding]; 120 | LogDebug(@"<<< %@", msg); 121 | NSMutableArray *contents = (NSMutableArray *)[[msg componentsSeparatedByString:kLineFeed] mutableCopy]; 122 | while ([contents count] > 0 && [contents[0] isEqual:@""]) { 123 | [contents removeObjectAtIndex:0]; 124 | } 125 | NSString *command = [[contents objectAtIndex:0] copy]; 126 | NSMutableDictionary *headers = [[NSMutableDictionary alloc] init]; 127 | NSMutableString *body = [[NSMutableString alloc] init]; 128 | BOOL hasHeaders = NO; 129 | [contents removeObjectAtIndex:0]; 130 | for(NSString *line in contents) { 131 | if(hasHeaders) { 132 | for (int i=0; i < [line length]; i++) { 133 | unichar c = [line characterAtIndex:i]; 134 | if (c != '\x00') { 135 | [body appendString:[NSString stringWithFormat:@"%c", c]]; 136 | } 137 | } 138 | } else { 139 | if ([line isEqual:@""]) { 140 | hasHeaders = YES; 141 | } else { 142 | NSMutableArray *parts = [NSMutableArray arrayWithArray:[line componentsSeparatedByString:kHeaderSeparator]]; 143 | // key ist the first part 144 | NSString *key = parts[0]; 145 | [parts removeObjectAtIndex:0]; 146 | headers[key] = [parts componentsJoinedByString:kHeaderSeparator]; 147 | } 148 | } 149 | } 150 | return [[STOMPFrame alloc] initWithCommand:command headers:headers body:body]; 151 | } 152 | 153 | - (NSString *)description { 154 | return [self toString]; 155 | } 156 | 157 | 158 | @end 159 | 160 | #pragma mark STOMP Message 161 | 162 | @interface STOMPMessage() 163 | 164 | @property (nonatomic, retain) STOMPClient *client; 165 | 166 | + (STOMPMessage *)STOMPMessageFromFrame:(STOMPFrame *)frame 167 | client:(STOMPClient *)client; 168 | 169 | @end 170 | 171 | @implementation STOMPMessage 172 | 173 | @synthesize client; 174 | 175 | - (id)initWithClient:(STOMPClient *)theClient 176 | headers:(NSDictionary *)theHeaders 177 | body:(NSString *)theBody { 178 | if (self = [super initWithCommand:kCommandMessage 179 | headers:theHeaders 180 | body:theBody]) { 181 | self.client = theClient; 182 | } 183 | return self; 184 | } 185 | 186 | - (void)ack { 187 | [self ackWithCommand:kCommandAck headers:nil]; 188 | } 189 | 190 | - (void)ack: (NSDictionary *)theHeaders { 191 | [self ackWithCommand:kCommandAck headers:theHeaders]; 192 | } 193 | 194 | - (void)nack { 195 | [self ackWithCommand:kCommandNack headers:nil]; 196 | } 197 | 198 | - (void)nack: (NSDictionary *)theHeaders { 199 | [self ackWithCommand:kCommandNack headers:theHeaders]; 200 | } 201 | 202 | - (void)ackWithCommand: (NSString *)command 203 | headers: (NSDictionary *)theHeaders { 204 | NSMutableDictionary *ackHeaders = [[NSMutableDictionary alloc] initWithDictionary:theHeaders]; 205 | ackHeaders[kHeaderID] = self.headers[kHeaderAck]; 206 | [self.client sendFrameWithCommand:command 207 | headers:ackHeaders 208 | body:nil]; 209 | } 210 | 211 | + (STOMPMessage *)STOMPMessageFromFrame:(STOMPFrame *)frame 212 | client:(STOMPClient *)client { 213 | return [[STOMPMessage alloc] initWithClient:client headers:frame.headers body:frame.body]; 214 | } 215 | 216 | @end 217 | 218 | #pragma mark STOMP Subscription 219 | 220 | @interface STOMPSubscription() 221 | 222 | @property (nonatomic, retain) STOMPClient *client; 223 | 224 | - (id)initWithClient:(STOMPClient *)theClient 225 | identifier:(NSString *)theIdentifier; 226 | 227 | @end 228 | 229 | @implementation STOMPSubscription 230 | 231 | @synthesize client; 232 | @synthesize identifier; 233 | 234 | - (id)initWithClient:(STOMPClient *)theClient 235 | identifier:(NSString *)theIdentifier { 236 | if(self = [super init]) { 237 | self.client = theClient; 238 | identifier = [theIdentifier copy]; 239 | } 240 | return self; 241 | } 242 | 243 | - (void)unsubscribe { 244 | [self.client sendFrameWithCommand:kCommandUnsubscribe 245 | headers:@{kHeaderID: self.identifier} 246 | body:nil]; 247 | } 248 | 249 | - (NSString *)description { 250 | return [NSString stringWithFormat:@"", identifier]; 251 | } 252 | 253 | @end 254 | 255 | #pragma mark STOMP Transaction 256 | 257 | @interface STOMPTransaction() 258 | 259 | @property (nonatomic, retain) STOMPClient *client; 260 | 261 | - (id)initWithClient:(STOMPClient *)theClient 262 | identifier:(NSString *)theIdentifier; 263 | 264 | @end 265 | 266 | @implementation STOMPTransaction 267 | 268 | @synthesize identifier; 269 | 270 | - (id)initWithClient:(STOMPClient *)theClient 271 | identifier:(NSString *)theIdentifier { 272 | if(self = [super init]) { 273 | self.client = theClient; 274 | identifier = [theIdentifier copy]; 275 | } 276 | return self; 277 | } 278 | 279 | - (void)commit { 280 | [self.client sendFrameWithCommand:kCommandCommit 281 | headers:@{kHeaderTransaction: self.identifier} 282 | body:nil]; 283 | } 284 | 285 | - (void)abort { 286 | [self.client sendFrameWithCommand:kCommandAbort 287 | headers:@{kHeaderTransaction: self.identifier} 288 | body:nil]; 289 | } 290 | 291 | - (NSString *)description { 292 | return [NSString stringWithFormat:@"", identifier]; 293 | } 294 | 295 | @end 296 | 297 | #pragma mark STOMP Client Implementation 298 | 299 | @implementation STOMPClient 300 | 301 | @synthesize socket, host, port; 302 | @synthesize connectionCompletionHandler, disconnectedHandler, receiptHandler, errorHandler; 303 | @synthesize subscriptions; 304 | @synthesize pinger, ponger; 305 | 306 | int idGenerator; 307 | CFAbsoluteTime serverActivity; 308 | 309 | #pragma mark - 310 | #pragma mark Public API 311 | 312 | - (id)initWithHost:(NSString *)aHost 313 | port:(NSUInteger)aPort { 314 | if(self = [super init]) { 315 | self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self 316 | delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; 317 | self.host = aHost; 318 | self.port = aPort; 319 | idGenerator = 0; 320 | self.connected = NO; 321 | self.subscriptions = [[NSMutableDictionary alloc] init]; 322 | self.clientHeartBeat = @"5000,10000"; 323 | } 324 | return self; 325 | } 326 | 327 | - (void)connectWithLogin:(NSString *)login 328 | passcode:(NSString *)passcode 329 | completionHandler:(void (^)(STOMPFrame *connectedFrame, NSError *error))completionHandler { 330 | [self connectWithHeaders:@{kHeaderLogin: login, kHeaderPasscode: passcode} 331 | completionHandler:completionHandler]; 332 | } 333 | 334 | - (void)connectWithHeaders:(NSDictionary *)headers 335 | completionHandler:(void (^)(STOMPFrame *connectedFrame, NSError *error))completionHandler { 336 | self.connectionCompletionHandler = completionHandler; 337 | 338 | NSError *err; 339 | if(![self.socket connectToHost:host onPort:port error:&err]) { 340 | if (self.connectionCompletionHandler) { 341 | self.connectionCompletionHandler(nil, err); 342 | } 343 | } 344 | 345 | NSMutableDictionary *connectHeaders = [[NSMutableDictionary alloc] initWithDictionary:headers]; 346 | connectHeaders[kHeaderAcceptVersion] = kVersion1_2; 347 | if (!connectHeaders[kHeaderHost]) { 348 | connectHeaders[kHeaderHost] = host; 349 | } 350 | if (!connectHeaders[kHeaderHeartBeat]) { 351 | connectHeaders[kHeaderHeartBeat] = self.clientHeartBeat; 352 | } else { 353 | self.clientHeartBeat = connectHeaders[kHeaderHeartBeat]; 354 | } 355 | 356 | [self sendFrameWithCommand:kCommandConnect 357 | headers:connectHeaders 358 | body: nil]; 359 | } 360 | 361 | - (void)sendTo:(NSString *)destination 362 | body:(NSString *)body { 363 | [self sendTo:destination 364 | headers:nil 365 | body:body]; 366 | } 367 | 368 | - (void)sendTo:(NSString *)destination 369 | headers:(NSDictionary *)headers 370 | body:(NSString *)body { 371 | NSMutableDictionary *msgHeaders = [NSMutableDictionary dictionaryWithDictionary:headers]; 372 | msgHeaders[kHeaderDestination] = destination; 373 | if (body) { 374 | msgHeaders[kHeaderContentLength] = [NSNumber numberWithLong:[body length]]; 375 | } 376 | [self sendFrameWithCommand:kCommandSend 377 | headers:msgHeaders 378 | body:body]; 379 | } 380 | 381 | - (STOMPSubscription *)subscribeTo:(NSString *)destination 382 | messageHandler:(STOMPMessageHandler)handler { 383 | return [self subscribeTo:destination 384 | headers:nil 385 | messageHandler:handler]; 386 | } 387 | 388 | - (STOMPSubscription *)subscribeTo:(NSString *)destination 389 | headers:(NSDictionary *)headers 390 | messageHandler:(STOMPMessageHandler)handler { 391 | NSMutableDictionary *subHeaders = [[NSMutableDictionary alloc] initWithDictionary:headers]; 392 | subHeaders[kHeaderDestination] = destination; 393 | NSString *identifier = subHeaders[kHeaderID]; 394 | if (!identifier) { 395 | identifier = [NSString stringWithFormat:@"sub-%d", idGenerator++]; 396 | subHeaders[kHeaderID] = identifier; 397 | } 398 | self.subscriptions[identifier] = handler; 399 | [self sendFrameWithCommand:kCommandSubscribe 400 | headers:subHeaders 401 | body:nil]; 402 | return [[STOMPSubscription alloc] initWithClient:self identifier:identifier]; 403 | } 404 | 405 | - (STOMPTransaction *)begin { 406 | NSString *identifier = [NSString stringWithFormat:@"tx-%d", idGenerator++]; 407 | return [self begin:identifier]; 408 | } 409 | 410 | - (STOMPTransaction *)begin:(NSString *)identifier { 411 | [self sendFrameWithCommand:kCommandBegin 412 | headers:@{kHeaderTransaction: identifier} 413 | body:nil]; 414 | return [[STOMPTransaction alloc] initWithClient:self identifier:identifier]; 415 | } 416 | 417 | - (void)disconnect { 418 | [self disconnect: nil]; 419 | } 420 | 421 | - (void)disconnect:(void (^)(NSError *error))completionHandler { 422 | self.disconnectedHandler = completionHandler; 423 | [self sendFrameWithCommand:kCommandDisconnect 424 | headers:nil 425 | body:nil]; 426 | [self.subscriptions removeAllObjects]; 427 | [self.pinger invalidate]; 428 | [self.ponger invalidate]; 429 | [self.socket disconnectAfterReadingAndWriting]; 430 | } 431 | 432 | 433 | #pragma mark - 434 | #pragma mark Private Methods 435 | 436 | - (void)sendFrameWithCommand:(NSString *)command 437 | headers:(NSDictionary *)headers 438 | body:(NSString *)body { 439 | if ([self.socket isDisconnected]) { 440 | return; 441 | } 442 | STOMPFrame *frame = [[STOMPFrame alloc] initWithCommand:command headers:headers body:body]; 443 | LogDebug(@">>> %@", frame); 444 | NSData *data = [frame toData]; 445 | [self.socket writeData:data withTimeout:kDefaultTimeout tag:123]; 446 | } 447 | 448 | - (void)sendPing:(NSTimer *)timer { 449 | if ([self.socket isDisconnected]) { 450 | return; 451 | } 452 | [self.socket writeData:[GCDAsyncSocket LFData] withTimeout:kDefaultTimeout tag:123]; 453 | LogDebug(@">>> PING"); 454 | } 455 | 456 | - (void)checkPong:(NSTimer *)timer { 457 | NSDictionary *dict = timer.userInfo; 458 | NSInteger ttl = [dict[@"ttl"] intValue]; 459 | 460 | CFAbsoluteTime delta = CFAbsoluteTimeGetCurrent() - serverActivity; 461 | if (delta > (ttl * 2)) { 462 | LogDebug(@"did not receive server activity for the last %f seconds", delta); 463 | [self disconnect:errorHandler]; 464 | } 465 | } 466 | 467 | - (void)setupHeartBeatWithClient:(NSString *)clientValues 468 | server:(NSString *)serverValues { 469 | NSInteger cx, cy, sx, sy; 470 | 471 | NSScanner *scanner = [NSScanner scannerWithString:clientValues]; 472 | scanner.charactersToBeSkipped = [NSCharacterSet characterSetWithCharactersInString:@", "]; 473 | [scanner scanInteger:&cx]; 474 | [scanner scanInteger:&cy]; 475 | 476 | scanner = [NSScanner scannerWithString:serverValues]; 477 | scanner.charactersToBeSkipped = [NSCharacterSet characterSetWithCharactersInString:@", "]; 478 | [scanner scanInteger:&sx]; 479 | [scanner scanInteger:&sy]; 480 | 481 | NSInteger pingTTL = ceil(MAX(cx, sy) / 1000); 482 | NSInteger pongTTL = ceil(MAX(sx, cy) / 1000); 483 | 484 | LogDebug(@"send heart-beat every %ld seconds", pingTTL); 485 | LogDebug(@"expect to receive heart-beats every %ld seconds", pongTTL); 486 | 487 | dispatch_async(dispatch_get_main_queue(), ^{ 488 | if (pingTTL > 0) { 489 | self.pinger = [NSTimer scheduledTimerWithTimeInterval: pingTTL 490 | target: self 491 | selector: @selector(sendPing:) 492 | userInfo: nil 493 | repeats: YES]; 494 | } 495 | if (pongTTL > 0) { 496 | self.ponger = [NSTimer scheduledTimerWithTimeInterval: pongTTL 497 | target: self 498 | selector: @selector(checkPong:) 499 | userInfo: @{@"ttl": [NSNumber numberWithInteger:pongTTL]} 500 | repeats: YES]; 501 | } 502 | }); 503 | 504 | } 505 | 506 | - (void)receivedFrame:(STOMPFrame *)frame { 507 | // CONNECTED 508 | if([kCommandConnected isEqual:frame.command]) { 509 | self.connected = YES; 510 | [self setupHeartBeatWithClient:self.clientHeartBeat server:frame.headers[kHeaderHeartBeat]]; 511 | if (self.connectionCompletionHandler) { 512 | self.connectionCompletionHandler(frame, nil); 513 | } 514 | // MESSAGE 515 | } else if([kCommandMessage isEqual:frame.command]) { 516 | STOMPMessageHandler handler = self.subscriptions[frame.headers[kHeaderSubscription]]; 517 | if (handler) { 518 | STOMPMessage *message = [STOMPMessage STOMPMessageFromFrame:frame 519 | client:self]; 520 | handler(message); 521 | } else { 522 | //TODO default handler 523 | } 524 | // RECEIPT 525 | } else if([kCommandReceipt isEqual:frame.command]) { 526 | if (self.receiptHandler) { 527 | self.receiptHandler(frame); 528 | } 529 | // ERROR 530 | } else if([kCommandError isEqual:frame.command]) { 531 | NSError *error = [[NSError alloc] initWithDomain:@"StompKit" code:1 userInfo:@{@"frame": frame}]; 532 | // ERROR coming after the CONNECT frame 533 | if (!self.connected && self.connectionCompletionHandler) { 534 | self.connectionCompletionHandler(frame, error); 535 | } else if (self.errorHandler) { 536 | self.errorHandler(error); 537 | } else { 538 | LogDebug(@"Unhandled ERROR frame: %@", frame); 539 | } 540 | } else { 541 | NSError *error = [[NSError alloc] initWithDomain:@"StompKit" 542 | code:2 543 | userInfo:@{@"message": [NSString stringWithFormat:@"Unknown frame %@", frame.command], 544 | @"frame": frame}]; 545 | if (self.errorHandler) { 546 | self.errorHandler(error); 547 | } 548 | } 549 | } 550 | 551 | - (void)readFrame { 552 | [[self socket] readDataToData:[GCDAsyncSocket ZeroData] withTimeout:-1 tag:0]; 553 | } 554 | 555 | #pragma mark - 556 | #pragma mark GCDAsyncSocketDelegate 557 | 558 | - (void)socket:(GCDAsyncSocket *)sock 559 | didReadData:(NSData *)data 560 | withTag:(long)tag { 561 | serverActivity = CFAbsoluteTimeGetCurrent(); 562 | STOMPFrame *frame = [STOMPFrame STOMPFrameFromData:data]; 563 | [self receivedFrame:frame]; 564 | [self readFrame]; 565 | } 566 | 567 | - (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag { 568 | LogDebug(@"<<< PONG"); 569 | serverActivity = CFAbsoluteTimeGetCurrent(); 570 | } 571 | 572 | - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { 573 | [self readFrame]; 574 | } 575 | 576 | - (void)socketDidDisconnect:(GCDAsyncSocket *)sock 577 | withError:(NSError *)err { 578 | LogDebug(@"socket did disconnect"); 579 | if (!self.connected && self.connectionCompletionHandler) { 580 | self.connectionCompletionHandler(nil, err); 581 | } else if (self.connected) { 582 | if (self.disconnectedHandler) { 583 | self.disconnectedHandler(err); 584 | } else if (self.errorHandler) { 585 | self.errorHandler(err); 586 | } 587 | } 588 | self.connected = NO; 589 | } 590 | 591 | @end 592 | -------------------------------------------------------------------------------- /StompKitTests/StompKitTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | jmesnil.net.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /StompKitTests/StompKitTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // StompKitTests.h 3 | // StompKit 4 | // 5 | // Created by Jeff Mesnil on 09/10/2013. 6 | // Copyright (c) 2013 Jeff Mesnil. All rights reserved. 7 | // 8 | 9 | #ifndef StompKit_StompKitTests_h 10 | #define StompKit_StompKitTests_h 11 | 12 | #define HOST @"localhost" 13 | #define PORT 61613 14 | #define LOGIN @"user" 15 | #define PASSCODE @"password" 16 | 17 | #define QUEUE_DEST @"/queue/myqueue" 18 | #define QUEUE_DEST_2 @"/queue/myqueue_2" 19 | 20 | #define secondsToNanoseconds(t) (t * 1000000000) // in nanoseconds 21 | #define gotSignal(semaphore, timeout) ((dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, secondsToNanoseconds(timeout)))) == 0l) 22 | 23 | #endif -------------------------------------------------------------------------------- /StompKitTests/StompKitTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // StompKitTests.m 3 | // StompKitTests 4 | // 5 | // Created by Jeff Mesnil on 09/10/2013. 6 | // Copyright (c) 2013 Jeff Mesnil. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "StompKit.h" 11 | #import "StompKitTests.h" 12 | 13 | // These integration tests expects that a STOMP broker is running 14 | // and listening on localhost:61613 15 | // The credentials are user / password 16 | // and the tests uses the destinations: "/queue/myqueue" "/queue/myqueue_2" 17 | // All these configuration variables are defined in StompKitTests.h 18 | 19 | @interface StompKitTests : XCTestCase 20 | 21 | @property (nonatomic, retain) STOMPClient *client; 22 | @end 23 | 24 | @implementation StompKitTests 25 | 26 | @synthesize client; 27 | 28 | - (void)setUp 29 | { 30 | [super setUp]; 31 | 32 | self.client = [[STOMPClient alloc] initWithHost:HOST 33 | port:PORT]; 34 | } 35 | 36 | - (void)tearDown 37 | { 38 | [self.client disconnect]; 39 | [super tearDown]; 40 | } 41 | 42 | - (void)testInvalidServerInfo { 43 | 44 | dispatch_semaphore_t errorReceived = dispatch_semaphore_create(0); 45 | 46 | STOMPClient *otherClient = [[STOMPClient alloc] initWithHost:@"invalid host" port:61613]; 47 | [otherClient connectWithLogin:LOGIN 48 | passcode:PASSCODE 49 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 50 | if (error) { 51 | NSLog(@"got error: %@", error); 52 | dispatch_semaphore_signal(errorReceived); 53 | } 54 | }]; 55 | XCTAssertTrue(gotSignal(errorReceived, 2)); 56 | } 57 | 58 | - (void)testConnect { 59 | dispatch_semaphore_t connected = dispatch_semaphore_create(0); 60 | 61 | [self.client connectWithLogin:LOGIN 62 | passcode:PASSCODE 63 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 64 | if (!error) { 65 | dispatch_semaphore_signal(connected); 66 | } 67 | }]; 68 | XCTAssertTrue(gotSignal(connected, 2), @"can not connect to %@:%d with credentials %@ / %@", HOST, PORT, LOGIN, PASSCODE); 69 | } 70 | 71 | - (void)testConnectWithError { 72 | dispatch_semaphore_t errorReceived = dispatch_semaphore_create(0); 73 | 74 | [self.client connectWithLogin:@"not a valid login" 75 | passcode:PASSCODE 76 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 77 | if (error) { 78 | NSLog(@"got error: %@", error); 79 | dispatch_semaphore_signal(errorReceived); 80 | } 81 | }]; 82 | XCTAssertTrue(gotSignal(errorReceived, 2)); 83 | } 84 | 85 | - (void)testDisconnect 86 | { 87 | dispatch_semaphore_t disconnected = dispatch_semaphore_create(0); 88 | 89 | [self.client connectWithLogin:LOGIN 90 | passcode:PASSCODE 91 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 92 | [self.client disconnect:^(NSError *error) { 93 | dispatch_semaphore_signal(disconnected); 94 | }]; 95 | }]; 96 | XCTAssertTrue(gotSignal(disconnected, 2)); 97 | } 98 | 99 | - (void)testSingleSubscription 100 | { 101 | dispatch_semaphore_t messageReceived = dispatch_semaphore_create(0); 102 | 103 | NSString *msg = @"testSingleSubscription"; 104 | __block NSString *reply; 105 | 106 | [self.client connectWithLogin:LOGIN 107 | passcode:PASSCODE 108 | completionHandler:^(STOMPFrame *_, NSError *error) { 109 | [self.client subscribeTo:QUEUE_DEST 110 | messageHandler:^(STOMPMessage *message) { 111 | reply = message.body; 112 | dispatch_semaphore_signal(messageReceived); 113 | }]; 114 | [self.client sendTo:QUEUE_DEST 115 | body:msg]; 116 | }]; 117 | 118 | XCTAssertTrue(gotSignal(messageReceived, 2), @"did not receive signal"); 119 | XCTAssert([msg isEqualToString:reply], @"did not receive expected message "); 120 | } 121 | 122 | - (void)testMultipleSubscription 123 | { 124 | dispatch_semaphore_t messageReceivedFromSub1 = dispatch_semaphore_create(0); 125 | dispatch_semaphore_t messageReceivedFromSub2 = dispatch_semaphore_create(0); 126 | 127 | STOMPMessageHandler handler1 = ^void (STOMPMessage *message) { 128 | dispatch_semaphore_signal(messageReceivedFromSub1); 129 | }; 130 | STOMPMessageHandler handler2 = ^void (STOMPMessage *message) { 131 | dispatch_semaphore_signal(messageReceivedFromSub2); 132 | }; 133 | [self.client connectWithLogin:LOGIN 134 | passcode:PASSCODE 135 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 136 | [self.client subscribeTo:QUEUE_DEST messageHandler:handler1]; 137 | [self.client subscribeTo:QUEUE_DEST_2 messageHandler:handler2]; 138 | 139 | [self.client sendTo:QUEUE_DEST body:@"testMultipleSubscription #1"]; 140 | [self.client sendTo:QUEUE_DEST_2 body:@"testMultipleSubscription #2"]; 141 | }]; 142 | 143 | XCTAssertTrue(gotSignal(messageReceivedFromSub1, 2), @"did not receive signal"); 144 | XCTAssertTrue(gotSignal(messageReceivedFromSub2, 2), @"did not receive signal"); 145 | } 146 | 147 | - (void)testUnsubscribe 148 | { 149 | dispatch_semaphore_t messageReceived = dispatch_semaphore_create(0); 150 | dispatch_semaphore_t subscribed = dispatch_semaphore_create(0); 151 | 152 | __block STOMPSubscription *subscription; 153 | 154 | [self.client connectWithLogin:LOGIN 155 | passcode:PASSCODE 156 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 157 | subscription = [self.client subscribeTo:QUEUE_DEST 158 | messageHandler:^(STOMPMessage *message) { 159 | dispatch_semaphore_signal(messageReceived); 160 | }]; 161 | dispatch_semaphore_signal(subscribed); 162 | }]; 163 | 164 | XCTAssertTrue(gotSignal(subscribed, 2)); 165 | 166 | [subscription unsubscribe]; 167 | 168 | [self.client sendTo:QUEUE_DEST body:@"testUnsubscribe"]; 169 | 170 | XCTAssertFalse(gotSignal(messageReceived, 2)); 171 | 172 | // resubscribe to consume the message and leave the queue empty: 173 | [self.client subscribeTo:QUEUE_DEST messageHandler:^(STOMPMessage *message) { 174 | dispatch_semaphore_signal(messageReceived); 175 | }]; 176 | 177 | XCTAssertTrue(gotSignal(messageReceived, 2)); 178 | } 179 | 180 | - (void)testAck { 181 | dispatch_semaphore_t receiptForAckReceived = dispatch_semaphore_create(0); 182 | 183 | NSString *receiptID = @"receipt-for-ack"; 184 | 185 | self.client.receiptHandler = ^(STOMPFrame *frame) { 186 | if ([frame.headers[kHeaderReceiptID] isEqualToString:receiptID]) { 187 | dispatch_semaphore_signal(receiptForAckReceived); 188 | } 189 | }; 190 | 191 | [self.client connectWithLogin:LOGIN 192 | passcode:PASSCODE 193 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 194 | [self.client subscribeTo:QUEUE_DEST 195 | headers: @{kHeaderAck:kAckClient} 196 | messageHandler:^(STOMPMessage *message) { 197 | [message ack:@{kHeaderReceipt: receiptID}]; 198 | }]; 199 | [self.client sendTo:QUEUE_DEST body:@"testAck"]; 200 | }]; 201 | 202 | XCTAssertTrue(gotSignal(receiptForAckReceived, 2)); 203 | } 204 | 205 | - (void)testNack { 206 | dispatch_semaphore_t receiptForNackReceived = dispatch_semaphore_create(0); 207 | 208 | NSString *receiptID = @"receipt-for-nack"; 209 | self.client.receiptHandler = ^(STOMPFrame *frame) { 210 | if ([frame.headers[kHeaderReceiptID] isEqualToString:receiptID]) { 211 | dispatch_semaphore_signal(receiptForNackReceived); 212 | } 213 | }; 214 | 215 | [self.client connectWithLogin:LOGIN 216 | passcode:PASSCODE 217 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 218 | [self.client subscribeTo:QUEUE_DEST 219 | headers: @{kHeaderAck: kAckClient} 220 | messageHandler:^(STOMPMessage *message) { 221 | [message nack:@{kHeaderReceipt: receiptID}]; 222 | }]; 223 | [self.client sendTo:QUEUE_DEST body:@"testNack"]; 224 | }]; 225 | 226 | XCTAssertTrue(gotSignal(receiptForNackReceived, 2)); 227 | } 228 | 229 | - (void)testCommitTransaction { 230 | dispatch_semaphore_t messageReceived = dispatch_semaphore_create(0); 231 | 232 | __block STOMPTransaction *transaction; 233 | 234 | [self.client connectWithLogin:LOGIN 235 | passcode:PASSCODE 236 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 237 | [self.client subscribeTo:QUEUE_DEST 238 | messageHandler:^(STOMPMessage *message) { 239 | dispatch_semaphore_signal(messageReceived); 240 | }]; 241 | transaction = [self.client begin]; 242 | [self.client sendTo:QUEUE_DEST 243 | headers:@{kHeaderTransaction: transaction.identifier} 244 | body:@"in a transaction"]; 245 | }]; 246 | 247 | XCTAssertFalse(gotSignal(messageReceived, 1)); 248 | 249 | [transaction commit]; 250 | 251 | XCTAssertTrue(gotSignal(messageReceived, 1)); 252 | } 253 | 254 | - (void)testAbortTransaction { 255 | dispatch_semaphore_t messageSent = dispatch_semaphore_create(0); 256 | dispatch_semaphore_t messageReceived = dispatch_semaphore_create(0); 257 | 258 | __block STOMPTransaction *transaction; 259 | 260 | [self.client connectWithLogin:LOGIN 261 | passcode:PASSCODE 262 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 263 | [self.client subscribeTo:QUEUE_DEST 264 | messageHandler:^(STOMPMessage *message) { 265 | dispatch_semaphore_signal(messageReceived); 266 | }]; 267 | transaction = [self.client begin]; 268 | [self.client sendTo:QUEUE_DEST 269 | headers:@{kHeaderTransaction: transaction.identifier} 270 | body:@"in a transaction"]; 271 | dispatch_semaphore_signal(messageSent); 272 | }]; 273 | 274 | XCTAssertTrue(gotSignal(messageSent, 2)); 275 | 276 | [transaction abort]; 277 | 278 | XCTAssertFalse(gotSignal(messageReceived, 2)); 279 | } 280 | 281 | - (void)testJSON 282 | { 283 | dispatch_semaphore_t messageReceived = dispatch_semaphore_create(0); 284 | 285 | NSDictionary *dict = @{@"foo": @"bar"}; 286 | __block NSString *receivedBody; 287 | 288 | [self.client connectWithLogin:LOGIN 289 | passcode:PASSCODE 290 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 291 | [self.client subscribeTo:QUEUE_DEST 292 | messageHandler:^(STOMPMessage *message) { 293 | receivedBody = message.body; 294 | dispatch_semaphore_signal(messageReceived); 295 | }]; 296 | NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil]; 297 | NSString *body =[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 298 | [self.client sendTo:QUEUE_DEST body:body]; 299 | }]; 300 | 301 | XCTAssertTrue(gotSignal(messageReceived, 2), @"did not receive signal"); 302 | 303 | NSDictionary *receivedDict = [NSJSONSerialization JSONObjectWithData:[receivedBody dataUsingEncoding:NSUTF8StringEncoding] 304 | options:NSJSONReadingMutableContainers 305 | error:nil]; 306 | XCTAssertTrue([receivedDict[@"foo"] isEqualToString:@"bar"]); 307 | } 308 | 309 | @end -------------------------------------------------------------------------------- /StompKitTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | --------------------------------------------------------------------------------