├── WebsocketStompKitTests ├── en.lproj │ └── InfoPlist.strings ├── WebsocketStompKitTests.h ├── WebsocketStompKitTests-Info.plist └── WebsocketStompKitTests.m ├── .xctool-args ├── .travis.yml ├── Podfile ├── .gitignore ├── WebsocketStompKit.podspec ├── WebsocketStompKit.xcodeproj ├── xcshareddata │ └── xcschemes │ │ └── StompKit.xcscheme └── project.pbxproj ├── README.md ├── WebsocketStompKit ├── WebsocketStompKit.h └── WebsocketStompKit.m └── LICENSE /WebsocketStompKitTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /.xctool-args: -------------------------------------------------------------------------------- 1 | [ 2 | "-workspace", "WebsocketStompKit.xcworkspace", 3 | "-scheme", "WebsocketStompKit", 4 | "-sdk", "iphonesimulator", 5 | "-arch", "i386" 6 | ] -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | xcodeproj 'WebsocketStompKit.xcodeproj' 2 | 3 | platform :ios, '6.0' 4 | 5 | pod 'jetfire', '0.1.2' 6 | 7 | target 'WebsocketStompKitTests', :exclusive => true do 8 | pod 'Kiwi', '2.2' 9 | end 10 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /WebsocketStompKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "WebsocketStompKit" 3 | s.version = "0.1.1" 4 | s.summary = "STOMP over Websocket Objective-C Client for iOS." 5 | s.homepage = "https://github.com/rguldener/WebsocketStompKit" 6 | s.license = 'Apache License, Version 2.0' 7 | s.author = "Jeff Mesnil & Robin Guldener" 8 | s.source = { :git => 'https://github.com/rguldener/WebsocketStompKit.git' } 9 | s.ios.deployment_target = '6.0' 10 | s.source_files = 'WebsocketStompKit/*.{h,m}' 11 | s.public_header_files = 'WebsocketStompKit/WebsocketStompKit.h' 12 | s.requires_arc = true 13 | s.dependency 'jetfire', '0.1.2' 14 | end 15 | -------------------------------------------------------------------------------- /WebsocketStompKitTests/WebsocketStompKitTests.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 URL @"http://localhost:61613" 13 | #define LOGIN @"user" 14 | #define PASSCODE @"password" 15 | 16 | #define QUEUE_DEST @"/queue/myqueue" 17 | #define QUEUE_DEST_2 @"/queue/myqueue_2" 18 | 19 | #define secondsToNanoseconds(t) (t * 1000000000) // in nanoseconds 20 | #define gotSignal(semaphore, timeout) ((dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, secondsToNanoseconds(timeout)))) == 0l) 21 | 22 | #endif -------------------------------------------------------------------------------- /WebsocketStompKitTests/WebsocketStompKitTests-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 | -------------------------------------------------------------------------------- /WebsocketStompKit.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 | 59 | 60 | 61 | 62 | 63 | 64 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WebsocketStompKit 2 | ======== 3 | 4 | Current version: 0.1.1 (version mirrored to [StompKit](https://github.com/mobile-web-messaging/StompKit)) 5 | 6 | WebsocketStompKit is STOMP over websocket for iOS. It is built on the great [StompKit](https://github.com/mobile-web-messaging/StompKit) and replaces its socket handling library with the very well done [Jetfire](https://github.com/acmacalister/jetfire) websocket library. 7 | 8 | ## Why would I want to run STOMP over websockets on iOS? 9 | 10 | Probably you already have a server that speaks STOMP over websockets. If you don't expect to have tens of thousands of clients at the same time, why rewrite everything from scratch? With WebsocketStompKit one server can handle both iOS and web clients. 11 | 12 | ## Installation 13 | 14 | Install with Cocoapods 15 | ``` 16 | ... 17 | pod 'WebsocketStompKit', :git => 'https://github.com/rguldener/WebsocketStompKit.git', :tag => '0.1.1' 18 | ... 19 | ``` 20 | Jetfire comes as a dependency so be prepared for that. 21 | 22 | ## Usage 23 | 24 | Import into your project/Objective-C headers bridge file (for Swift) 25 | 26 | ``` 27 | #import 28 | ``` 29 | 30 | Then instantiate the ```STOMPClient``` class 31 | 32 | ``` 33 | NSURL *websocketUrl = [NSURL urlWithString:@"ws://my-great-server.com/websocket"]; 34 | STOMPClient *client = [[STOMPClient alloc] initWithURL:websocketUrl websocketHeaders:nil useHeartbeat:NO]; 35 | ``` 36 | 37 | websocketHeaders accepts an NSDictionary of additional HTTP header entries that should be passed along with the initial websocket request. This is especially useful if you need to authenticate with cookies as by default Jetfire **will not pass cookies** along with your initial websocket-upgrade HTTP request. 38 | useHeartbeat allows you to deactivate the heartbeat component of STOMP (which is optional) as it is not supported by all STOMP brokers. 39 | 40 | Once you have your client object you can connect in the same way as with StompKit 41 | ``` 42 | // connect to the broker 43 | [client connectWithLogin:@"mylogin" 44 | passcode:@"mypassword" 45 | completionHandler:^(STOMPFrame *_, NSError *error) { 46 | if (err) { 47 | NSLog(@"%@", error); 48 | return; 49 | } 50 | 51 | // send a message 52 | [client sendTo:@"/queue/myqueue" body:@"Hello, iOS!"]; 53 | // and disconnect 54 | [client disconnect]; 55 | }]; 56 | ``` 57 | 58 | Note that the completion handler which you pass into the connect method will also be called when the websocket connection gets closed or if the connection creation does not succeed. 59 | 60 | The rest of the provided methods are the same as in [StompKit](https://github.com/mobile-web-messaging/StompKit), please refer to its Readme for basic usage information 61 | 62 | ## Differences to StompKit 63 | Easy: 64 | 65 | * Routes STOMP messages over websocket connection instead of a raw TCP socket 66 | * Allows deactivation of heartbeat functionality 67 | 68 | I plan to keep this in sync with StompKit moving forward and will mirror their versioning here. 69 | 70 | ## Authors 71 | 72 | * StompKit: [Jeff Mesnil](http://jmesnil.net/) 73 | * Jetfire: [Austin Cherry](http://austincherry.me) & [Dalton Cherry](http://daltoniam.com) 74 | * Mixing the two together: Robin Guldener 75 | 76 | -------------------------------------------------------------------------------- /WebsocketStompKit/WebsocketStompKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // WebsocketStompKit.h 3 | // WebsocketStompKit 4 | // 5 | // Created by Jeff Mesnil on 09/10/2013. 6 | // Modified by Robin Guldener on 17/03/2015 7 | // Copyright (c) 2013 Jeff Mesnil & Robin Guldener. All rights reserved. 8 | // 9 | 10 | #import 11 | 12 | #pragma mark Frame headers 13 | 14 | #define kHeaderAcceptVersion @"accept-version" 15 | #define kHeaderAck @"ack" 16 | #define kHeaderContentLength @"content-length" 17 | #define kHeaderDestination @"destination" 18 | #define kHeaderHeartBeat @"heart-beat" 19 | #define kHeaderHost @"host" 20 | #define kHeaderID @"id" 21 | #define kHeaderLogin @"login" 22 | #define kHeaderMessage @"message" 23 | #define kHeaderPasscode @"passcode" 24 | #define kHeaderReceipt @"receipt" 25 | #define kHeaderReceiptID @"receipt-id" 26 | #define kHeaderSession @"session" 27 | #define kHeaderSubscription @"subscription" 28 | #define kHeaderTransaction @"transaction" 29 | 30 | #pragma mark Ack Header Values 31 | 32 | #define kAckAuto @"auto" 33 | #define kAckClient @"client" 34 | #define kAckClientIndividual @"client-individual" 35 | 36 | @class STOMPFrame; 37 | @class STOMPMessage; 38 | 39 | typedef void (^STOMPFrameHandler)(STOMPFrame *frame); 40 | typedef void (^STOMPMessageHandler)(STOMPMessage *message); 41 | 42 | #pragma mark STOMP Frame 43 | 44 | @interface STOMPFrame : NSObject 45 | 46 | @property (nonatomic, copy, readonly) NSString *command; 47 | @property (nonatomic, copy, readonly) NSDictionary *headers; 48 | @property (nonatomic, copy, readonly) NSString *body; 49 | 50 | @end 51 | 52 | #pragma mark STOMP Message 53 | 54 | @interface STOMPMessage : STOMPFrame 55 | 56 | - (void)ack; 57 | - (void)ack:(NSDictionary *)theHeaders; 58 | - (void)nack; 59 | - (void)nack:(NSDictionary *)theHeaders; 60 | 61 | @end 62 | 63 | #pragma mark STOMP Subscription 64 | 65 | @interface STOMPSubscription : NSObject 66 | 67 | @property (nonatomic, copy, readonly) NSString *identifier; 68 | 69 | - (void)unsubscribe; 70 | 71 | @end 72 | 73 | #pragma mark STOMP Transaction 74 | 75 | @interface STOMPTransaction : NSObject 76 | 77 | @property (nonatomic, copy, readonly) NSString *identifier; 78 | 79 | - (void)commit; 80 | - (void)abort; 81 | 82 | @end 83 | 84 | @protocol STOMPClientDelegate 85 | 86 | @optional 87 | - (void) websocketDidDisconnect: (NSError *)error; 88 | @end 89 | 90 | #pragma mark STOMP Client 91 | 92 | @interface STOMPClient : NSObject 93 | 94 | @property (nonatomic, copy) STOMPFrameHandler receiptHandler; 95 | @property (nonatomic, copy) void (^errorHandler)(NSError *error); 96 | @property (nonatomic, assign) BOOL connected; 97 | @property (nonatomic, readonly) BOOL heartbeatActivated; 98 | @property (nonatomic, weak) NSObject *delegate; 99 | 100 | - (id)initWithURL:(NSURL *)theUrl webSocketHeaders:(NSDictionary *)headers useHeartbeat:(BOOL)heartbeat; 101 | 102 | - (void)connectWithLogin:(NSString *)login 103 | passcode:(NSString *)passcode 104 | completionHandler:(void (^)(STOMPFrame *connectedFrame, NSError *error))completionHandler; 105 | - (void)connectWithHeaders:(NSDictionary *)headers 106 | completionHandler:(void (^)(STOMPFrame *connectedFrame, NSError *error))completionHandler; 107 | 108 | - (void)sendTo:(NSString *)destination 109 | body:(NSString *)body; 110 | - (void)sendTo:(NSString *)destination 111 | headers:(NSDictionary *)headers 112 | body:(NSString *)body; 113 | 114 | - (STOMPSubscription *)subscribeTo:(NSString *)destination 115 | messageHandler:(STOMPMessageHandler)handler; 116 | - (STOMPSubscription *)subscribeTo:(NSString *)destination 117 | headers:(NSDictionary *)headers 118 | messageHandler:(STOMPMessageHandler)handler; 119 | 120 | - (STOMPTransaction *)begin; 121 | - (STOMPTransaction *)begin:(NSString *)identifier; 122 | 123 | - (void)disconnect; 124 | - (void)disconnect:(void (^)(NSError *error))completionHandler; 125 | 126 | @end -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /WebsocketStompKitTests/WebsocketStompKitTests.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] initWithURL:[NSURL URLWithString:URL]]; 33 | } 34 | 35 | - (void)tearDown 36 | { 37 | [self.client disconnect]; 38 | [super tearDown]; 39 | } 40 | 41 | - (void)testInvalidServerInfo { 42 | 43 | dispatch_semaphore_t errorReceived = dispatch_semaphore_create(0); 44 | 45 | STOMPClient *otherClient = [[STOMPClient alloc] initWithURL:[NSURL URLWithString:@"invalid url"]]; 46 | [otherClient connectWithLogin:LOGIN 47 | passcode:PASSCODE 48 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 49 | if (error) { 50 | NSLog(@"got error: %@", error); 51 | dispatch_semaphore_signal(errorReceived); 52 | } 53 | }]; 54 | XCTAssertTrue(gotSignal(errorReceived, 2)); 55 | } 56 | 57 | - (void)testConnect { 58 | dispatch_semaphore_t connected = dispatch_semaphore_create(0); 59 | 60 | [self.client connectWithLogin:LOGIN 61 | passcode:PASSCODE 62 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 63 | if (!error) { 64 | dispatch_semaphore_signal(connected); 65 | } 66 | }]; 67 | XCTAssertTrue(gotSignal(connected, 2), @"can not connect to %@ with credentials %@ / %@", URL, LOGIN, PASSCODE); 68 | } 69 | 70 | - (void)testConnectWithError { 71 | dispatch_semaphore_t errorReceived = dispatch_semaphore_create(0); 72 | 73 | [self.client connectWithLogin:@"not a valid login" 74 | passcode:PASSCODE 75 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 76 | if (error) { 77 | NSLog(@"got error: %@", error); 78 | dispatch_semaphore_signal(errorReceived); 79 | } 80 | }]; 81 | XCTAssertTrue(gotSignal(errorReceived, 2)); 82 | } 83 | 84 | - (void)testDisconnect 85 | { 86 | dispatch_semaphore_t disconnected = dispatch_semaphore_create(0); 87 | 88 | [self.client connectWithLogin:LOGIN 89 | passcode:PASSCODE 90 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 91 | [self.client disconnect:^(NSError *error) { 92 | dispatch_semaphore_signal(disconnected); 93 | }]; 94 | }]; 95 | XCTAssertTrue(gotSignal(disconnected, 2)); 96 | } 97 | 98 | - (void)testSingleSubscription 99 | { 100 | dispatch_semaphore_t messageReceived = dispatch_semaphore_create(0); 101 | 102 | NSString *msg = @"testSingleSubscription"; 103 | __block NSString *reply; 104 | 105 | [self.client connectWithLogin:LOGIN 106 | passcode:PASSCODE 107 | completionHandler:^(STOMPFrame *_, NSError *error) { 108 | [self.client subscribeTo:QUEUE_DEST 109 | messageHandler:^(STOMPMessage *message) { 110 | reply = message.body; 111 | dispatch_semaphore_signal(messageReceived); 112 | }]; 113 | [self.client sendTo:QUEUE_DEST 114 | body:msg]; 115 | }]; 116 | 117 | XCTAssertTrue(gotSignal(messageReceived, 2), @"did not receive signal"); 118 | XCTAssert([msg isEqualToString:reply], @"did not receive expected message "); 119 | } 120 | 121 | - (void)testMultipleSubscription 122 | { 123 | dispatch_semaphore_t messageReceivedFromSub1 = dispatch_semaphore_create(0); 124 | dispatch_semaphore_t messageReceivedFromSub2 = dispatch_semaphore_create(0); 125 | 126 | STOMPMessageHandler handler1 = ^void (STOMPMessage *message) { 127 | dispatch_semaphore_signal(messageReceivedFromSub1); 128 | }; 129 | STOMPMessageHandler handler2 = ^void (STOMPMessage *message) { 130 | dispatch_semaphore_signal(messageReceivedFromSub2); 131 | }; 132 | [self.client connectWithLogin:LOGIN 133 | passcode:PASSCODE 134 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 135 | [self.client subscribeTo:QUEUE_DEST messageHandler:handler1]; 136 | [self.client subscribeTo:QUEUE_DEST_2 messageHandler:handler2]; 137 | 138 | [self.client sendTo:QUEUE_DEST body:@"testMultipleSubscription #1"]; 139 | [self.client sendTo:QUEUE_DEST_2 body:@"testMultipleSubscription #2"]; 140 | }]; 141 | 142 | XCTAssertTrue(gotSignal(messageReceivedFromSub1, 2), @"did not receive signal"); 143 | XCTAssertTrue(gotSignal(messageReceivedFromSub2, 2), @"did not receive signal"); 144 | } 145 | 146 | - (void)testUnsubscribe 147 | { 148 | dispatch_semaphore_t messageReceived = dispatch_semaphore_create(0); 149 | dispatch_semaphore_t subscribed = dispatch_semaphore_create(0); 150 | 151 | __block STOMPSubscription *subscription; 152 | 153 | [self.client connectWithLogin:LOGIN 154 | passcode:PASSCODE 155 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 156 | subscription = [self.client subscribeTo:QUEUE_DEST 157 | messageHandler:^(STOMPMessage *message) { 158 | dispatch_semaphore_signal(messageReceived); 159 | }]; 160 | dispatch_semaphore_signal(subscribed); 161 | }]; 162 | 163 | XCTAssertTrue(gotSignal(subscribed, 2)); 164 | 165 | [subscription unsubscribe]; 166 | 167 | [self.client sendTo:QUEUE_DEST body:@"testUnsubscribe"]; 168 | 169 | XCTAssertFalse(gotSignal(messageReceived, 2)); 170 | 171 | // resubscribe to consume the message and leave the queue empty: 172 | [self.client subscribeTo:QUEUE_DEST messageHandler:^(STOMPMessage *message) { 173 | dispatch_semaphore_signal(messageReceived); 174 | }]; 175 | 176 | XCTAssertTrue(gotSignal(messageReceived, 2)); 177 | } 178 | 179 | - (void)testAck { 180 | dispatch_semaphore_t receiptForAckReceived = dispatch_semaphore_create(0); 181 | 182 | NSString *receiptID = @"receipt-for-ack"; 183 | 184 | self.client.receiptHandler = ^(STOMPFrame *frame) { 185 | if ([frame.headers[kHeaderReceiptID] isEqualToString:receiptID]) { 186 | dispatch_semaphore_signal(receiptForAckReceived); 187 | } 188 | }; 189 | 190 | [self.client connectWithLogin:LOGIN 191 | passcode:PASSCODE 192 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 193 | [self.client subscribeTo:QUEUE_DEST 194 | headers: @{kHeaderAck:kAckClient} 195 | messageHandler:^(STOMPMessage *message) { 196 | [message ack:@{kHeaderReceipt: receiptID}]; 197 | }]; 198 | [self.client sendTo:QUEUE_DEST body:@"testAck"]; 199 | }]; 200 | 201 | XCTAssertTrue(gotSignal(receiptForAckReceived, 2)); 202 | } 203 | 204 | - (void)testNack { 205 | dispatch_semaphore_t receiptForNackReceived = dispatch_semaphore_create(0); 206 | 207 | NSString *receiptID = @"receipt-for-nack"; 208 | self.client.receiptHandler = ^(STOMPFrame *frame) { 209 | if ([frame.headers[kHeaderReceiptID] isEqualToString:receiptID]) { 210 | dispatch_semaphore_signal(receiptForNackReceived); 211 | } 212 | }; 213 | 214 | [self.client connectWithLogin:LOGIN 215 | passcode:PASSCODE 216 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 217 | [self.client subscribeTo:QUEUE_DEST 218 | headers: @{kHeaderAck: kAckClient} 219 | messageHandler:^(STOMPMessage *message) { 220 | [message nack:@{kHeaderReceipt: receiptID}]; 221 | }]; 222 | [self.client sendTo:QUEUE_DEST body:@"testNack"]; 223 | }]; 224 | 225 | XCTAssertTrue(gotSignal(receiptForNackReceived, 2)); 226 | } 227 | 228 | - (void)testCommitTransaction { 229 | dispatch_semaphore_t messageReceived = dispatch_semaphore_create(0); 230 | 231 | __block STOMPTransaction *transaction; 232 | 233 | [self.client connectWithLogin:LOGIN 234 | passcode:PASSCODE 235 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 236 | [self.client subscribeTo:QUEUE_DEST 237 | messageHandler:^(STOMPMessage *message) { 238 | dispatch_semaphore_signal(messageReceived); 239 | }]; 240 | transaction = [self.client begin]; 241 | [self.client sendTo:QUEUE_DEST 242 | headers:@{kHeaderTransaction: transaction.identifier} 243 | body:@"in a transaction"]; 244 | }]; 245 | 246 | XCTAssertFalse(gotSignal(messageReceived, 1)); 247 | 248 | [transaction commit]; 249 | 250 | XCTAssertTrue(gotSignal(messageReceived, 1)); 251 | } 252 | 253 | - (void)testAbortTransaction { 254 | dispatch_semaphore_t messageSent = dispatch_semaphore_create(0); 255 | dispatch_semaphore_t messageReceived = dispatch_semaphore_create(0); 256 | 257 | __block STOMPTransaction *transaction; 258 | 259 | [self.client connectWithLogin:LOGIN 260 | passcode:PASSCODE 261 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 262 | [self.client subscribeTo:QUEUE_DEST 263 | messageHandler:^(STOMPMessage *message) { 264 | dispatch_semaphore_signal(messageReceived); 265 | }]; 266 | transaction = [self.client begin]; 267 | [self.client sendTo:QUEUE_DEST 268 | headers:@{kHeaderTransaction: transaction.identifier} 269 | body:@"in a transaction"]; 270 | dispatch_semaphore_signal(messageSent); 271 | }]; 272 | 273 | XCTAssertTrue(gotSignal(messageSent, 2)); 274 | 275 | [transaction abort]; 276 | 277 | XCTAssertFalse(gotSignal(messageReceived, 2)); 278 | } 279 | 280 | - (void)testJSON 281 | { 282 | dispatch_semaphore_t messageReceived = dispatch_semaphore_create(0); 283 | 284 | NSDictionary *dict = @{@"foo": @"bar"}; 285 | __block NSString *receivedBody; 286 | 287 | [self.client connectWithLogin:LOGIN 288 | passcode:PASSCODE 289 | completionHandler:^(STOMPFrame *connectedFrame, NSError *error) { 290 | [self.client subscribeTo:QUEUE_DEST 291 | messageHandler:^(STOMPMessage *message) { 292 | receivedBody = message.body; 293 | dispatch_semaphore_signal(messageReceived); 294 | }]; 295 | NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil]; 296 | NSString *body =[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 297 | [self.client sendTo:QUEUE_DEST body:body]; 298 | }]; 299 | 300 | XCTAssertTrue(gotSignal(messageReceived, 2), @"did not receive signal"); 301 | 302 | NSDictionary *receivedDict = [NSJSONSerialization JSONObjectWithData:[receivedBody dataUsingEncoding:NSUTF8StringEncoding] 303 | options:NSJSONReadingMutableContainers 304 | error:nil]; 305 | XCTAssertTrue([receivedDict[@"foo"] isEqualToString:@"bar"]); 306 | } 307 | 308 | @end -------------------------------------------------------------------------------- /WebsocketStompKit/WebsocketStompKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebsocketStompKit.m 3 | // WebsocketStompKit 4 | // 5 | // Created by Jeff Mesnil on 09/10/2013. 6 | // Modified by Robin Guldener on 17/03/2015 7 | // Copyright (c) 2013 Jeff Mesnil & Robin Guldener. All rights reserved. 8 | // 9 | 10 | #import "WebsocketStompKit.h" 11 | #import 12 | 13 | #define kDefaultTimeout 5 14 | #define kVersion1_2 @"1.2" 15 | #define kNoHeartBeat @"0,0" 16 | 17 | #define WSProtocols @[]//@[@"v10.stomp", @"v11.stomp"] 18 | 19 | #pragma mark Logging macros 20 | 21 | #ifdef DEBUG // set to 1 to enable logs 22 | 23 | #define LogDebug(frmt, ...) NSLog(frmt, ##__VA_ARGS__); 24 | 25 | #else 26 | 27 | #define LogDebug(frmt, ...) {} 28 | 29 | #endif 30 | 31 | #pragma mark Frame commands 32 | 33 | #define kCommandAbort @"ABORT" 34 | #define kCommandAck @"ACK" 35 | #define kCommandBegin @"BEGIN" 36 | #define kCommandCommit @"COMMIT" 37 | #define kCommandConnect @"CONNECT" 38 | #define kCommandConnected @"CONNECTED" 39 | #define kCommandDisconnect @"DISCONNECT" 40 | #define kCommandError @"ERROR" 41 | #define kCommandMessage @"MESSAGE" 42 | #define kCommandNack @"NACK" 43 | #define kCommandReceipt @"RECEIPT" 44 | #define kCommandSend @"SEND" 45 | #define kCommandSubscribe @"SUBSCRIBE" 46 | #define kCommandUnsubscribe @"UNSUBSCRIBE" 47 | 48 | #pragma mark Control characters 49 | 50 | #define kLineFeed @"\x0A" 51 | #define kNullChar @"\x00" 52 | #define kHeaderSeparator @":" 53 | 54 | #pragma mark - 55 | #pragma mark STOMP Client private interface 56 | 57 | @interface STOMPClient() 58 | 59 | @property (nonatomic, retain) JFRWebSocket *socket; 60 | @property (nonatomic, copy) NSURL *url; 61 | @property (nonatomic, copy) NSString *host; 62 | @property (nonatomic) NSString *clientHeartBeat; 63 | @property (nonatomic, weak) NSTimer *pinger; 64 | @property (nonatomic, weak) NSTimer *ponger; 65 | @property (nonatomic, assign) BOOL heartbeat; 66 | 67 | @property (nonatomic, copy) void (^disconnectedHandler)(NSError *error); 68 | @property (nonatomic, copy) void (^connectionCompletionHandler)(STOMPFrame *connectedFrame, NSError *error); 69 | @property (nonatomic, copy) NSDictionary *connectFrameHeaders; 70 | @property (nonatomic, retain) NSMutableDictionary *subscriptions; 71 | 72 | - (void) sendFrameWithCommand:(NSString *)command 73 | headers:(NSDictionary *)headers 74 | body:(NSString *)body; 75 | 76 | @end 77 | 78 | #pragma mark STOMP Frame 79 | 80 | @interface STOMPFrame() 81 | 82 | - (id)initWithCommand:(NSString *)theCommand 83 | headers:(NSDictionary *)theHeaders 84 | body:(NSString *)theBody; 85 | 86 | - (NSData *)toData; 87 | 88 | @end 89 | 90 | @implementation STOMPFrame 91 | 92 | @synthesize command, headers, body; 93 | 94 | - (id)initWithCommand:(NSString *)theCommand 95 | headers:(NSDictionary *)theHeaders 96 | body:(NSString *)theBody { 97 | if(self = [super init]) { 98 | command = theCommand; 99 | headers = theHeaders; 100 | body = theBody; 101 | } 102 | return self; 103 | } 104 | 105 | - (NSString *)toString { 106 | NSMutableString *frame = [NSMutableString stringWithString: [self.command stringByAppendingString:kLineFeed]]; 107 | for (id key in self.headers) { 108 | [frame appendString:[NSString stringWithFormat:@"%@%@%@%@", key, kHeaderSeparator, self.headers[key], kLineFeed]]; 109 | } 110 | [frame appendString:kLineFeed]; 111 | if (self.body) { 112 | [frame appendString:self.body]; 113 | } 114 | [frame appendString:kNullChar]; 115 | return frame; 116 | } 117 | 118 | - (NSData *)toData { 119 | return [[self toString] dataUsingEncoding:NSUTF8StringEncoding]; 120 | } 121 | 122 | + (STOMPFrame *) STOMPFrameFromData:(NSData *)data { 123 | NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length])]; 124 | NSString *msg = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding]; 125 | LogDebug(@"<<< %@", msg); 126 | NSMutableArray *contents = (NSMutableArray *)[[msg componentsSeparatedByString:kLineFeed] mutableCopy]; 127 | while ([contents count] > 0 && [contents[0] isEqual:@""]) { 128 | [contents removeObjectAtIndex:0]; 129 | } 130 | NSString *command = [[contents objectAtIndex:0] copy]; 131 | NSMutableDictionary *headers = [[NSMutableDictionary alloc] init]; 132 | NSMutableString *body = [[NSMutableString alloc] init]; 133 | BOOL hasHeaders = NO; 134 | [contents removeObjectAtIndex:0]; 135 | for(NSString *line in contents) { 136 | if(hasHeaders) { 137 | [body appendString:line]; 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 stringByTrimmingCharactersInSet:[NSCharacterSet controlCharacterSet]]]; 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, url, host, heartbeat; 302 | @synthesize connectFrameHeaders; 303 | @synthesize connectionCompletionHandler, disconnectedHandler, receiptHandler, errorHandler; 304 | @synthesize subscriptions; 305 | @synthesize pinger, ponger; 306 | @synthesize delegate; 307 | 308 | int idGenerator; 309 | CFAbsoluteTime serverActivity; 310 | 311 | #pragma mark - 312 | #pragma mark Public API 313 | 314 | - (id)initWithURL:(NSURL *)theUrl webSocketHeaders:(NSDictionary *)headers useHeartbeat:(BOOL)heart { 315 | if(self = [super init]) { 316 | self.socket = [[JFRWebSocket alloc] initWithURL:theUrl protocols:WSProtocols]; 317 | if (headers) { 318 | for (NSString *key in headers.allKeys) { 319 | [self.socket addHeader:[headers objectForKey:key] forKey:key]; 320 | } 321 | } 322 | self.socket.delegate = self; 323 | 324 | self.heartbeat = heart; 325 | 326 | self.url = theUrl; 327 | self.host = theUrl.host; 328 | idGenerator = 0; 329 | self.connected = NO; 330 | self.subscriptions = [[NSMutableDictionary alloc] init]; 331 | self.clientHeartBeat = @"5000,10000"; 332 | } 333 | return self; 334 | } 335 | 336 | - (BOOL) heartbeatActivated { 337 | return heartbeat; 338 | } 339 | 340 | - (void)connectWithLogin:(NSString *)login 341 | passcode:(NSString *)passcode 342 | completionHandler:(void (^)(STOMPFrame *connectedFrame, NSError *error))completionHandler { 343 | [self connectWithHeaders:@{kHeaderLogin: login, kHeaderPasscode: passcode} 344 | completionHandler:completionHandler]; 345 | } 346 | 347 | - (void)connectWithHeaders:(NSDictionary *)headers 348 | completionHandler:(void (^)(STOMPFrame *connectedFrame, NSError *error))completionHandler { 349 | self.connectFrameHeaders = headers; 350 | self.connectionCompletionHandler = completionHandler; 351 | [self.socket connect]; 352 | } 353 | 354 | - (void)sendTo:(NSString *)destination 355 | body:(NSString *)body { 356 | [self sendTo:destination 357 | headers:nil 358 | body:body]; 359 | } 360 | 361 | - (void)sendTo:(NSString *)destination 362 | headers:(NSDictionary *)headers 363 | body:(NSString *)body { 364 | NSMutableDictionary *msgHeaders = [NSMutableDictionary dictionaryWithDictionary:headers]; 365 | msgHeaders[kHeaderDestination] = destination; 366 | if (body) { 367 | msgHeaders[kHeaderContentLength] = [NSNumber numberWithLong:[body length]]; 368 | } 369 | [self sendFrameWithCommand:kCommandSend 370 | headers:msgHeaders 371 | body:body]; 372 | } 373 | 374 | - (STOMPSubscription *)subscribeTo:(NSString *)destination 375 | messageHandler:(STOMPMessageHandler)handler { 376 | return [self subscribeTo:destination 377 | headers:nil 378 | messageHandler:handler]; 379 | } 380 | 381 | - (STOMPSubscription *)subscribeTo:(NSString *)destination 382 | headers:(NSDictionary *)headers 383 | messageHandler:(STOMPMessageHandler)handler { 384 | NSMutableDictionary *subHeaders = [[NSMutableDictionary alloc] initWithDictionary:headers]; 385 | subHeaders[kHeaderDestination] = destination; 386 | NSString *identifier = subHeaders[kHeaderID]; 387 | if (!identifier) { 388 | identifier = [NSString stringWithFormat:@"sub-%d", idGenerator++]; 389 | subHeaders[kHeaderID] = identifier; 390 | } 391 | self.subscriptions[identifier] = handler; 392 | [self sendFrameWithCommand:kCommandSubscribe 393 | headers:subHeaders 394 | body:nil]; 395 | return [[STOMPSubscription alloc] initWithClient:self identifier:identifier]; 396 | } 397 | 398 | - (STOMPTransaction *)begin { 399 | NSString *identifier = [NSString stringWithFormat:@"tx-%d", idGenerator++]; 400 | return [self begin:identifier]; 401 | } 402 | 403 | - (STOMPTransaction *)begin:(NSString *)identifier { 404 | [self sendFrameWithCommand:kCommandBegin 405 | headers:@{kHeaderTransaction: identifier} 406 | body:nil]; 407 | return [[STOMPTransaction alloc] initWithClient:self identifier:identifier]; 408 | } 409 | 410 | - (void)disconnect { 411 | [self disconnect: nil]; 412 | } 413 | 414 | - (void)disconnect:(void (^)(NSError *error))completionHandler { 415 | self.disconnectedHandler = completionHandler; 416 | [self sendFrameWithCommand:kCommandDisconnect 417 | headers:nil 418 | body:nil]; 419 | [self.subscriptions removeAllObjects]; 420 | [self.pinger invalidate]; 421 | [self.ponger invalidate]; 422 | [self.socket disconnect]; 423 | } 424 | 425 | 426 | #pragma mark - 427 | #pragma mark Private Methods 428 | 429 | - (void)sendFrameWithCommand:(NSString *)command 430 | headers:(NSDictionary *)headers 431 | body:(NSString *)body { 432 | if (![self.socket isConnected]) { 433 | return; 434 | } 435 | STOMPFrame *frame = [[STOMPFrame alloc] initWithCommand:command headers:headers body:body]; 436 | LogDebug(@">>> %@", frame); 437 | [self.socket writeString:[frame toString]]; 438 | } 439 | 440 | - (void)sendPing:(NSTimer *)timer { 441 | if (![self.socket isConnected]) { 442 | return; 443 | } 444 | [self.socket writeData:[NSData dataWithBytes:"\x0A" length:1]]; 445 | LogDebug(@">>> PING"); 446 | } 447 | 448 | - (void)checkPong:(NSTimer *)timer { 449 | NSDictionary *dict = timer.userInfo; 450 | NSInteger ttl = [dict[@"ttl"] intValue]; 451 | 452 | CFAbsoluteTime delta = CFAbsoluteTimeGetCurrent() - serverActivity; 453 | if (delta > (ttl * 2)) { 454 | LogDebug(@"did not receive server activity for the last %f seconds", delta); 455 | [self disconnect:errorHandler]; 456 | } 457 | } 458 | 459 | - (void)setupHeartBeatWithClient:(NSString *)clientValues 460 | server:(NSString *)serverValues { 461 | if (!heartbeat) { 462 | return; 463 | } 464 | 465 | NSInteger cx, cy, sx, sy; 466 | 467 | NSScanner *scanner = [NSScanner scannerWithString:clientValues]; 468 | scanner.charactersToBeSkipped = [NSCharacterSet characterSetWithCharactersInString:@", "]; 469 | [scanner scanInteger:&cx]; 470 | [scanner scanInteger:&cy]; 471 | 472 | scanner = [NSScanner scannerWithString:serverValues]; 473 | scanner.charactersToBeSkipped = [NSCharacterSet characterSetWithCharactersInString:@", "]; 474 | [scanner scanInteger:&sx]; 475 | [scanner scanInteger:&sy]; 476 | 477 | NSInteger pingTTL = ceil(MAX(cx, sy) / 1000); 478 | NSInteger pongTTL = ceil(MAX(sx, cy) / 1000); 479 | 480 | LogDebug(@"send heart-beat every %ld seconds", pingTTL); 481 | LogDebug(@"expect to receive heart-beats every %ld seconds", pongTTL); 482 | 483 | __weak typeof(self) weakSelf = self; 484 | dispatch_async(dispatch_get_main_queue(), ^{ 485 | if (pingTTL > 0) { 486 | weakSelf.pinger = [NSTimer scheduledTimerWithTimeInterval: pingTTL 487 | target: weakSelf 488 | selector: @selector(sendPing:) 489 | userInfo: nil 490 | repeats: YES]; 491 | } 492 | if (pongTTL > 0) { 493 | weakSelf.ponger = [NSTimer scheduledTimerWithTimeInterval: pongTTL 494 | target: weakSelf 495 | selector: @selector(checkPong:) 496 | userInfo: @{@"ttl": [NSNumber numberWithInteger:pongTTL]} 497 | repeats: YES]; 498 | } 499 | }); 500 | 501 | } 502 | 503 | - (void)receivedFrame:(STOMPFrame *)frame { 504 | // CONNECTED 505 | if([kCommandConnected isEqual:frame.command]) { 506 | self.connected = YES; 507 | [self setupHeartBeatWithClient:self.clientHeartBeat server:frame.headers[kHeaderHeartBeat]]; 508 | if (self.connectionCompletionHandler) { 509 | self.connectionCompletionHandler(frame, nil); 510 | } 511 | // MESSAGE 512 | } else if([kCommandMessage isEqual:frame.command]) { 513 | STOMPMessageHandler handler = self.subscriptions[frame.headers[kHeaderSubscription]]; 514 | if (handler) { 515 | STOMPMessage *message = [STOMPMessage STOMPMessageFromFrame:frame 516 | client:self]; 517 | handler(message); 518 | } else { 519 | //TODO default handler 520 | } 521 | // RECEIPT 522 | } else if([kCommandReceipt isEqual:frame.command]) { 523 | if (self.receiptHandler) { 524 | self.receiptHandler(frame); 525 | } 526 | // ERROR 527 | } else if([kCommandError isEqual:frame.command]) { 528 | NSError *error = [[NSError alloc] initWithDomain:@"StompKit" code:1 userInfo:@{@"frame": frame}]; 529 | // ERROR coming after the CONNECT frame 530 | if (!self.connected && self.connectionCompletionHandler) { 531 | self.connectionCompletionHandler(frame, error); 532 | } else if (self.errorHandler) { 533 | self.errorHandler(error); 534 | } else { 535 | LogDebug(@"Unhandled ERROR frame: %@", frame); 536 | } 537 | } else { 538 | NSError *error = [[NSError alloc] initWithDomain:@"StompKit" 539 | code:2 540 | userInfo:@{@"message": [NSString stringWithFormat:@"Unknown frame %@", frame.command], 541 | @"frame": frame}]; 542 | if (self.errorHandler) { 543 | self.errorHandler(error); 544 | } 545 | } 546 | } 547 | 548 | #pragma mark - 549 | #pragma mark JetfireDelegate 550 | 551 | - (void) websocketDidConnect:(JFRWebSocket*) socket { 552 | 553 | // Websocket has connected, send the STOMP connection frame 554 | NSMutableDictionary *connectHeaders = [[NSMutableDictionary alloc] initWithDictionary:connectFrameHeaders]; 555 | connectHeaders[kHeaderAcceptVersion] = kVersion1_2; 556 | if (!connectHeaders[kHeaderHost]) { 557 | connectHeaders[kHeaderHost] = host; 558 | } 559 | if (!connectHeaders[kHeaderHeartBeat]) { 560 | connectHeaders[kHeaderHeartBeat] = self.clientHeartBeat; 561 | } else { 562 | self.clientHeartBeat = connectHeaders[kHeaderHeartBeat]; 563 | } 564 | 565 | [self sendFrameWithCommand:kCommandConnect 566 | headers:connectHeaders 567 | body: nil]; 568 | 569 | } 570 | 571 | // BINARY FRAMES! 572 | // Should never be used for STOMP as STOMP is a text based protocol. 573 | // However, STOMPKit can handle binary data so no harm in leaving this here 574 | - (void)websocket:(JFRWebSocket*)socket didReceiveData:(NSData*)data { 575 | serverActivity = CFAbsoluteTimeGetCurrent(); 576 | STOMPFrame *frame = [STOMPFrame STOMPFrameFromData:data]; 577 | [self receivedFrame:frame]; 578 | } 579 | 580 | // TEXT FRAMES! 581 | // This is where all the goodness should arrive 582 | - (void)websocket:(JFRWebSocket*)socket didReceiveMessage:(NSString *)string { 583 | serverActivity = CFAbsoluteTimeGetCurrent(); 584 | STOMPFrame *frame = [STOMPFrame STOMPFrameFromData:[string dataUsingEncoding:NSUTF8StringEncoding]]; 585 | [self receivedFrame:frame]; 586 | } 587 | 588 | - (void)websocketDidDisconnect:(JFRWebSocket*)socket error:(NSError*)error { 589 | LogDebug(@"socket did disconnect, error: %@", error); 590 | if (!self.connected && self.connectionCompletionHandler) { 591 | self.connectionCompletionHandler(nil, error); 592 | } else if (self.connected) { 593 | if (self.disconnectedHandler) { 594 | self.disconnectedHandler(error); 595 | } else if (self.errorHandler) { 596 | self.errorHandler(error); 597 | } 598 | } 599 | self.connected = NO; 600 | 601 | if (self.delegate != nil && [self.delegate respondsToSelector:@selector(websocketDidDisconnect:)]) { 602 | [self.delegate websocketDidDisconnect:error]; 603 | } 604 | } 605 | 606 | @end 607 | -------------------------------------------------------------------------------- /WebsocketStompKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 005859CE1AB96F30006185FB /* WebsocketStompKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 005859CD1AB96F30006185FB /* WebsocketStompKit.m */; }; 11 | 005859D11AB96F53006185FB /* WebsocketStompKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 005859D01AB96F53006185FB /* WebsocketStompKitTests.m */; }; 12 | 6C24B5EB8E0343E483E53CA2 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37A4F2569A574CA6A0704DA6 /* libPods.a */; }; 13 | 932BA57C18056C4B00A03257 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 932BA57B18056C4B00A03257 /* Foundation.framework */; }; 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 /* libWebsocketStompKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 932BA57818056C4B00A03257 /* libWebsocketStompKit.a */; }; 18 | 932BA59618056C4C00A03257 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 932BA59418056C4C00A03257 /* InfoPlist.strings */; }; 19 | D1313E2FF21A4E15A007CA43 /* libPods-StompKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 591868DBFE214F55AB478DA7 /* libPods-StompKitTests.a */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 932BA58E18056C4C00A03257 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 932BA57018056C4B00A03257 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 932BA57718056C4B00A03257; 28 | remoteInfo = StompKit; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXCopyFilesBuildPhase section */ 33 | 932BA57618056C4B00A03257 /* CopyFiles */ = { 34 | isa = PBXCopyFilesBuildPhase; 35 | buildActionMask = 2147483647; 36 | dstPath = "include/$(PRODUCT_NAME)"; 37 | dstSubfolderSpec = 16; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXCopyFilesBuildPhase section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 005859CC1AB96F30006185FB /* WebsocketStompKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebsocketStompKit.h; path = WebsocketStompKit/WebsocketStompKit.h; sourceTree = SOURCE_ROOT; }; 46 | 005859CD1AB96F30006185FB /* WebsocketStompKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = WebsocketStompKit.m; path = WebsocketStompKit/WebsocketStompKit.m; sourceTree = SOURCE_ROOT; }; 47 | 005859CF1AB96F53006185FB /* WebsocketStompKitTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebsocketStompKitTests.h; path = WebsocketStompKitTests/WebsocketStompKitTests.h; sourceTree = SOURCE_ROOT; }; 48 | 005859D01AB96F53006185FB /* WebsocketStompKitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = WebsocketStompKitTests.m; path = WebsocketStompKitTests/WebsocketStompKitTests.m; sourceTree = SOURCE_ROOT; }; 49 | 37A4F2569A574CA6A0704DA6 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 566F44FAC018466ACDB20B75 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; 51 | 591868DBFE214F55AB478DA7 /* libPods-StompKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-StompKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 9320F3901816698700FF599F /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = ""; }; 53 | 932BA57818056C4B00A03257 /* libWebsocketStompKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libWebsocketStompKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 932BA57B18056C4B00A03257 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 55 | 932BA58818056C4C00A03257 /* WebsocketStompKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WebsocketStompKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 932BA58918056C4C00A03257 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 57 | 932BA58C18056C4C00A03257 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 58 | 932BA59318056C4C00A03257 /* WebsocketStompKitTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "WebsocketStompKitTests-Info.plist"; sourceTree = ""; }; 59 | 932BA59518056C4C00A03257 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 60 | 938827ED18056CA2009A1164 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 61 | 938827EE18056CA2009A1164 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = ""; }; 62 | 9D76DD3AA2A0BA27F8BC7D9A /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 63 | E13BC1EA3039EC423342920B /* Pods-StompKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StompKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-StompKitTests/Pods-StompKitTests.debug.xcconfig"; sourceTree = ""; }; 64 | F2106E46785E4C239A551BE3 /* Pods-StompKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StompKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-StompKitTests/Pods-StompKitTests.release.xcconfig"; sourceTree = ""; }; 65 | /* End PBXFileReference section */ 66 | 67 | /* Begin PBXFrameworksBuildPhase section */ 68 | 932BA57518056C4B00A03257 /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | 932BA57C18056C4B00A03257 /* Foundation.framework in Frameworks */, 73 | 6C24B5EB8E0343E483E53CA2 /* libPods.a in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | 932BA58518056C4C00A03257 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | 932BA58A18056C4C00A03257 /* XCTest.framework in Frameworks */, 82 | 932BA58D18056C4C00A03257 /* UIKit.framework in Frameworks */, 83 | 932BA59018056C4C00A03257 /* libWebsocketStompKit.a in Frameworks */, 84 | 932BA58B18056C4C00A03257 /* Foundation.framework in Frameworks */, 85 | D1313E2FF21A4E15A007CA43 /* libPods-StompKitTests.a in Frameworks */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | /* End PBXFrameworksBuildPhase section */ 90 | 91 | /* Begin PBXGroup section */ 92 | 932BA56F18056C4B00A03257 = { 93 | isa = PBXGroup; 94 | children = ( 95 | 9320F3901816698700FF599F /* Podfile */, 96 | 938827ED18056CA2009A1164 /* LICENSE */, 97 | 938827EE18056CA2009A1164 /* README.md */, 98 | 932BA57D18056C4B00A03257 /* WebsocketStompKit */, 99 | 932BA59118056C4C00A03257 /* StompKitTests */, 100 | 932BA57A18056C4B00A03257 /* Frameworks */, 101 | 932BA57918056C4B00A03257 /* Products */, 102 | C220D1A04164CBCD2C98650C /* Pods */, 103 | ); 104 | sourceTree = ""; 105 | }; 106 | 932BA57918056C4B00A03257 /* Products */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 932BA57818056C4B00A03257 /* libWebsocketStompKit.a */, 110 | 932BA58818056C4C00A03257 /* WebsocketStompKitTests.xctest */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | 932BA57A18056C4B00A03257 /* Frameworks */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 932BA57B18056C4B00A03257 /* Foundation.framework */, 119 | 932BA58918056C4C00A03257 /* XCTest.framework */, 120 | 932BA58C18056C4C00A03257 /* UIKit.framework */, 121 | 37A4F2569A574CA6A0704DA6 /* libPods.a */, 122 | 591868DBFE214F55AB478DA7 /* libPods-StompKitTests.a */, 123 | ); 124 | name = Frameworks; 125 | sourceTree = ""; 126 | }; 127 | 932BA57D18056C4B00A03257 /* WebsocketStompKit */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 005859CC1AB96F30006185FB /* WebsocketStompKit.h */, 131 | 005859CD1AB96F30006185FB /* WebsocketStompKit.m */, 132 | ); 133 | name = WebsocketStompKit; 134 | path = StompKit; 135 | sourceTree = ""; 136 | }; 137 | 932BA59118056C4C00A03257 /* StompKitTests */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 005859CF1AB96F53006185FB /* WebsocketStompKitTests.h */, 141 | 005859D01AB96F53006185FB /* WebsocketStompKitTests.m */, 142 | 932BA59218056C4C00A03257 /* Supporting Files */, 143 | ); 144 | path = StompKitTests; 145 | sourceTree = ""; 146 | }; 147 | 932BA59218056C4C00A03257 /* Supporting Files */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 932BA59318056C4C00A03257 /* WebsocketStompKitTests-Info.plist */, 151 | 932BA59418056C4C00A03257 /* InfoPlist.strings */, 152 | ); 153 | name = "Supporting Files"; 154 | sourceTree = ""; 155 | }; 156 | C220D1A04164CBCD2C98650C /* Pods */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 9D76DD3AA2A0BA27F8BC7D9A /* Pods.debug.xcconfig */, 160 | 566F44FAC018466ACDB20B75 /* Pods.release.xcconfig */, 161 | E13BC1EA3039EC423342920B /* Pods-StompKitTests.debug.xcconfig */, 162 | F2106E46785E4C239A551BE3 /* Pods-StompKitTests.release.xcconfig */, 163 | ); 164 | name = Pods; 165 | sourceTree = ""; 166 | }; 167 | /* End PBXGroup section */ 168 | 169 | /* Begin PBXNativeTarget section */ 170 | 932BA57718056C4B00A03257 /* WebsocketStompKit */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = 932BA59B18056C4C00A03257 /* Build configuration list for PBXNativeTarget "WebsocketStompKit" */; 173 | buildPhases = ( 174 | F14EDA9ACA514E238C2E405D /* Check Pods Manifest.lock */, 175 | 932BA57418056C4B00A03257 /* Sources */, 176 | 932BA57518056C4B00A03257 /* Frameworks */, 177 | 932BA57618056C4B00A03257 /* CopyFiles */, 178 | F35F0CAAF0E740C2A96A5AC7 /* Copy Pods Resources */, 179 | ); 180 | buildRules = ( 181 | ); 182 | dependencies = ( 183 | ); 184 | name = WebsocketStompKit; 185 | productName = StompKit; 186 | productReference = 932BA57818056C4B00A03257 /* libWebsocketStompKit.a */; 187 | productType = "com.apple.product-type.library.static"; 188 | }; 189 | 932BA58718056C4C00A03257 /* WebsocketStompKitTests */ = { 190 | isa = PBXNativeTarget; 191 | buildConfigurationList = 932BA59E18056C4C00A03257 /* Build configuration list for PBXNativeTarget "WebsocketStompKitTests" */; 192 | buildPhases = ( 193 | 125CC95EDE78443AA2A8A7CA /* Check Pods Manifest.lock */, 194 | 932BA58418056C4C00A03257 /* Sources */, 195 | 932BA58518056C4C00A03257 /* Frameworks */, 196 | 932BA58618056C4C00A03257 /* Resources */, 197 | D78F70268CF740B1A5B2133D /* Copy Pods Resources */, 198 | ); 199 | buildRules = ( 200 | ); 201 | dependencies = ( 202 | 932BA58F18056C4C00A03257 /* PBXTargetDependency */, 203 | ); 204 | name = WebsocketStompKitTests; 205 | productName = StompKitTests; 206 | productReference = 932BA58818056C4C00A03257 /* WebsocketStompKitTests.xctest */; 207 | productType = "com.apple.product-type.bundle.unit-test"; 208 | }; 209 | /* End PBXNativeTarget section */ 210 | 211 | /* Begin PBXProject section */ 212 | 932BA57018056C4B00A03257 /* Project object */ = { 213 | isa = PBXProject; 214 | attributes = { 215 | LastUpgradeCheck = 0500; 216 | ORGANIZATIONNAME = "Jeff Mesnil"; 217 | }; 218 | buildConfigurationList = 932BA57318056C4B00A03257 /* Build configuration list for PBXProject "WebsocketStompKit" */; 219 | compatibilityVersion = "Xcode 3.2"; 220 | developmentRegion = English; 221 | hasScannedForEncodings = 0; 222 | knownRegions = ( 223 | en, 224 | ); 225 | mainGroup = 932BA56F18056C4B00A03257; 226 | productRefGroup = 932BA57918056C4B00A03257 /* Products */; 227 | projectDirPath = ""; 228 | projectRoot = ""; 229 | targets = ( 230 | 932BA57718056C4B00A03257 /* WebsocketStompKit */, 231 | 932BA58718056C4C00A03257 /* WebsocketStompKitTests */, 232 | ); 233 | }; 234 | /* End PBXProject section */ 235 | 236 | /* Begin PBXResourcesBuildPhase section */ 237 | 932BA58618056C4C00A03257 /* Resources */ = { 238 | isa = PBXResourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | 932BA59618056C4C00A03257 /* InfoPlist.strings in Resources */, 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | /* End PBXResourcesBuildPhase section */ 246 | 247 | /* Begin PBXShellScriptBuildPhase section */ 248 | 125CC95EDE78443AA2A8A7CA /* Check Pods Manifest.lock */ = { 249 | isa = PBXShellScriptBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | ); 253 | inputPaths = ( 254 | ); 255 | name = "Check Pods Manifest.lock"; 256 | outputPaths = ( 257 | ); 258 | runOnlyForDeploymentPostprocessing = 0; 259 | shellPath = /bin/sh; 260 | 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"; 261 | showEnvVarsInLog = 0; 262 | }; 263 | D78F70268CF740B1A5B2133D /* Copy Pods Resources */ = { 264 | isa = PBXShellScriptBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | ); 268 | inputPaths = ( 269 | ); 270 | name = "Copy Pods Resources"; 271 | outputPaths = ( 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | shellPath = /bin/sh; 275 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-StompKitTests/Pods-StompKitTests-resources.sh\"\n"; 276 | showEnvVarsInLog = 0; 277 | }; 278 | F14EDA9ACA514E238C2E405D /* Check Pods Manifest.lock */ = { 279 | isa = PBXShellScriptBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | ); 283 | inputPaths = ( 284 | ); 285 | name = "Check Pods Manifest.lock"; 286 | outputPaths = ( 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | shellPath = /bin/sh; 290 | 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"; 291 | showEnvVarsInLog = 0; 292 | }; 293 | F35F0CAAF0E740C2A96A5AC7 /* Copy Pods Resources */ = { 294 | isa = PBXShellScriptBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | ); 298 | inputPaths = ( 299 | ); 300 | name = "Copy Pods Resources"; 301 | outputPaths = ( 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | shellPath = /bin/sh; 305 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; 306 | showEnvVarsInLog = 0; 307 | }; 308 | /* End PBXShellScriptBuildPhase section */ 309 | 310 | /* Begin PBXSourcesBuildPhase section */ 311 | 932BA57418056C4B00A03257 /* Sources */ = { 312 | isa = PBXSourcesBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | 005859D11AB96F53006185FB /* WebsocketStompKitTests.m in Sources */, 316 | 005859CE1AB96F30006185FB /* WebsocketStompKit.m in Sources */, 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | 932BA58418056C4C00A03257 /* Sources */ = { 321 | isa = PBXSourcesBuildPhase; 322 | buildActionMask = 2147483647; 323 | files = ( 324 | ); 325 | runOnlyForDeploymentPostprocessing = 0; 326 | }; 327 | /* End PBXSourcesBuildPhase section */ 328 | 329 | /* Begin PBXTargetDependency section */ 330 | 932BA58F18056C4C00A03257 /* PBXTargetDependency */ = { 331 | isa = PBXTargetDependency; 332 | target = 932BA57718056C4B00A03257 /* WebsocketStompKit */; 333 | targetProxy = 932BA58E18056C4C00A03257 /* PBXContainerItemProxy */; 334 | }; 335 | /* End PBXTargetDependency section */ 336 | 337 | /* Begin PBXVariantGroup section */ 338 | 932BA59418056C4C00A03257 /* InfoPlist.strings */ = { 339 | isa = PBXVariantGroup; 340 | children = ( 341 | 932BA59518056C4C00A03257 /* en */, 342 | ); 343 | name = InfoPlist.strings; 344 | sourceTree = ""; 345 | }; 346 | /* End PBXVariantGroup section */ 347 | 348 | /* Begin XCBuildConfiguration section */ 349 | 932BA59918056C4C00A03257 /* Debug */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ALWAYS_SEARCH_USER_PATHS = NO; 353 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 354 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 355 | CLANG_CXX_LIBRARY = "libc++"; 356 | CLANG_ENABLE_MODULES = YES; 357 | CLANG_ENABLE_OBJC_ARC = YES; 358 | CLANG_WARN_BOOL_CONVERSION = YES; 359 | CLANG_WARN_CONSTANT_CONVERSION = YES; 360 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 361 | CLANG_WARN_EMPTY_BODY = YES; 362 | CLANG_WARN_ENUM_CONVERSION = YES; 363 | CLANG_WARN_INT_CONVERSION = YES; 364 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 365 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 366 | COPY_PHASE_STRIP = NO; 367 | GCC_C_LANGUAGE_STANDARD = gnu99; 368 | GCC_DYNAMIC_NO_PIC = NO; 369 | GCC_OPTIMIZATION_LEVEL = 0; 370 | GCC_PREPROCESSOR_DEFINITIONS = ( 371 | "DEBUG=1", 372 | "$(inherited)", 373 | ); 374 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 375 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 376 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 377 | GCC_WARN_UNDECLARED_SELECTOR = YES; 378 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 379 | GCC_WARN_UNUSED_FUNCTION = YES; 380 | GCC_WARN_UNUSED_VARIABLE = YES; 381 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 382 | ONLY_ACTIVE_ARCH = YES; 383 | SDKROOT = iphoneos; 384 | }; 385 | name = Debug; 386 | }; 387 | 932BA59A18056C4C00A03257 /* Release */ = { 388 | isa = XCBuildConfiguration; 389 | buildSettings = { 390 | ALWAYS_SEARCH_USER_PATHS = NO; 391 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 392 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 393 | CLANG_CXX_LIBRARY = "libc++"; 394 | CLANG_ENABLE_MODULES = YES; 395 | CLANG_ENABLE_OBJC_ARC = YES; 396 | CLANG_WARN_BOOL_CONVERSION = YES; 397 | CLANG_WARN_CONSTANT_CONVERSION = YES; 398 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 399 | CLANG_WARN_EMPTY_BODY = YES; 400 | CLANG_WARN_ENUM_CONVERSION = YES; 401 | CLANG_WARN_INT_CONVERSION = YES; 402 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 403 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 404 | COPY_PHASE_STRIP = YES; 405 | ENABLE_NS_ASSERTIONS = NO; 406 | GCC_C_LANGUAGE_STANDARD = gnu99; 407 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 408 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 409 | GCC_WARN_UNDECLARED_SELECTOR = YES; 410 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 411 | GCC_WARN_UNUSED_FUNCTION = YES; 412 | GCC_WARN_UNUSED_VARIABLE = YES; 413 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 414 | SDKROOT = iphoneos; 415 | VALIDATE_PRODUCT = YES; 416 | }; 417 | name = Release; 418 | }; 419 | 932BA59C18056C4C00A03257 /* Debug */ = { 420 | isa = XCBuildConfiguration; 421 | baseConfigurationReference = 9D76DD3AA2A0BA27F8BC7D9A /* Pods.debug.xcconfig */; 422 | buildSettings = { 423 | DSTROOT = /tmp/StompKit.dst; 424 | OTHER_LDFLAGS = "-ObjC"; 425 | PRODUCT_NAME = WebsocketStompKit; 426 | SKIP_INSTALL = YES; 427 | }; 428 | name = Debug; 429 | }; 430 | 932BA59D18056C4C00A03257 /* Release */ = { 431 | isa = XCBuildConfiguration; 432 | baseConfigurationReference = 566F44FAC018466ACDB20B75 /* Pods.release.xcconfig */; 433 | buildSettings = { 434 | DSTROOT = /tmp/StompKit.dst; 435 | OTHER_LDFLAGS = "-ObjC"; 436 | PRODUCT_NAME = WebsocketStompKit; 437 | SKIP_INSTALL = YES; 438 | }; 439 | name = Release; 440 | }; 441 | 932BA59F18056C4C00A03257 /* Debug */ = { 442 | isa = XCBuildConfiguration; 443 | baseConfigurationReference = E13BC1EA3039EC423342920B /* Pods-StompKitTests.debug.xcconfig */; 444 | buildSettings = { 445 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 446 | FRAMEWORK_SEARCH_PATHS = ( 447 | "$(SDKROOT)/Developer/Library/Frameworks", 448 | "$(inherited)", 449 | "$(DEVELOPER_FRAMEWORKS_DIR)", 450 | ); 451 | GCC_PREPROCESSOR_DEFINITIONS = ( 452 | "DEBUG=1", 453 | "$(inherited)", 454 | ); 455 | INFOPLIST_FILE = "StompKitTests/WebsocketStompKitTests-Info.plist"; 456 | PRODUCT_NAME = WebsocketStompKitTests; 457 | WRAPPER_EXTENSION = xctest; 458 | }; 459 | name = Debug; 460 | }; 461 | 932BA5A018056C4C00A03257 /* Release */ = { 462 | isa = XCBuildConfiguration; 463 | baseConfigurationReference = F2106E46785E4C239A551BE3 /* Pods-StompKitTests.release.xcconfig */; 464 | buildSettings = { 465 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 466 | FRAMEWORK_SEARCH_PATHS = ( 467 | "$(SDKROOT)/Developer/Library/Frameworks", 468 | "$(inherited)", 469 | "$(DEVELOPER_FRAMEWORKS_DIR)", 470 | ); 471 | INFOPLIST_FILE = "StompKitTests/WebsocketStompKitTests-Info.plist"; 472 | PRODUCT_NAME = WebsocketStompKitTests; 473 | WRAPPER_EXTENSION = xctest; 474 | }; 475 | name = Release; 476 | }; 477 | /* End XCBuildConfiguration section */ 478 | 479 | /* Begin XCConfigurationList section */ 480 | 932BA57318056C4B00A03257 /* Build configuration list for PBXProject "WebsocketStompKit" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | 932BA59918056C4C00A03257 /* Debug */, 484 | 932BA59A18056C4C00A03257 /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | 932BA59B18056C4C00A03257 /* Build configuration list for PBXNativeTarget "WebsocketStompKit" */ = { 490 | isa = XCConfigurationList; 491 | buildConfigurations = ( 492 | 932BA59C18056C4C00A03257 /* Debug */, 493 | 932BA59D18056C4C00A03257 /* Release */, 494 | ); 495 | defaultConfigurationIsVisible = 0; 496 | defaultConfigurationName = Release; 497 | }; 498 | 932BA59E18056C4C00A03257 /* Build configuration list for PBXNativeTarget "WebsocketStompKitTests" */ = { 499 | isa = XCConfigurationList; 500 | buildConfigurations = ( 501 | 932BA59F18056C4C00A03257 /* Debug */, 502 | 932BA5A018056C4C00A03257 /* Release */, 503 | ); 504 | defaultConfigurationIsVisible = 0; 505 | defaultConfigurationName = Release; 506 | }; 507 | /* End XCConfigurationList section */ 508 | }; 509 | rootObject = 932BA57018056C4B00A03257 /* Project object */; 510 | } 511 | --------------------------------------------------------------------------------