├── 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 |
--------------------------------------------------------------------------------