Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c91a8e08c4 | |||
| 6a8a48dfd9 | |||
| 5b46a5c6de | |||
| a854649e09 | |||
| 36de866909 | |||
| 9371147fd1 | |||
| 60288f8454 | |||
| 5b4a2e5e94 | |||
| 2d1b534f4d | |||
| 26d42ca585 | |||
| 900b107dc0 | |||
| 65738cad39 | |||
| ff5768048c | |||
| 9b6770afe1 | |||
| b4c0e1e72b | |||
| 5bab81d099 | |||
| 3622125a62 | |||
| 4850bfbb38 | |||
| 9dd461a972 | |||
| 0469f7ae05 | |||
| fc18d96ff4 | |||
| bd5d2e80f5 | |||
| bdbeb1be5f | |||
| d0868e486c | |||
| 766aee9700 | |||
| 8c4e208eda | |||
| 9325a4ac64 | |||
| bee2611e60 | |||
| e638a42547 | |||
| a8c6176b04 | |||
| b5d1acba22 | |||
| 7474d4fba4 | |||
| 9570efd717 | |||
| 671c168e56 | |||
| dd354b9629 | |||
| a8cd4e598d | |||
| 2458c41eb6 | |||
| a4215b3f06 | |||
| 0078f3d9fe | |||
| 57be4eecd9 | |||
| 504ab4d111 | |||
| 4f83e4ae03 | |||
| fee9e1ba80 | |||
| d4af68951f | |||
| 49ca3dd04a | |||
| 8ce6167bf2 | |||
| 97b9f54d60 | |||
| 4bf0730f90 | |||
| 1d88d5284d | |||
| f1fc60312a | |||
| 911d456b2d | |||
| 6bf99f4906 | |||
| 95e1e2c90d | |||
| 7e623e8ece | |||
| d92a31d302 | |||
| f6b9ff9905 | |||
| db1795aa38 | |||
| 4671c607ff | |||
| db26eef28a | |||
| 03ed94c746 | |||
| a47eef9bed | |||
| 60fc2d62b9 | |||
| bb1e33479e | |||
| 11beb0ad7f | |||
| 3d464c937a | |||
| 7151d861c9 | |||
| a1c4d59993 | |||
| 9740ddabde | |||
| f8878beb51 | |||
| 228a115d8e | |||
| 5c05f0e87d | |||
| c16532a797 | |||
| d6bcfbce6f | |||
| 721db6ded0 | |||
| 76704ba1a7 | |||
| dc6c225b96 | |||
| db675a8d47 | |||
| 924d76b72c | |||
| b07f7ed111 | |||
| bae2002df4 | |||
| f2c61faff2 | |||
| ae428376bf | |||
| d8a2c0b1e3 | |||
| d1cdf93a78 | |||
| 1cdc1d9470 | |||
| 5a3b3356bf | |||
| 990fd20a2c | |||
| 92cf70b7c3 | |||
| 4054812b59 | |||
| f06194265c | |||
| ef4e5fac48 |
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>--ignore</key>
|
||||
<array>
|
||||
<string>Mac</string>
|
||||
<string>Touch</string>
|
||||
</array>
|
||||
<key>--project-company</key>
|
||||
<string>Noodlewerk</string>
|
||||
<key>--project-name</key>
|
||||
<string>NWPusher</string>
|
||||
<key>--company-id</key>
|
||||
<string>com.noodlewerk</string>
|
||||
<key>--warn-missing-arg</key>
|
||||
<false/>
|
||||
<key>--repeat-first-par</key>
|
||||
<false/>
|
||||
<key>--output</key>
|
||||
<string>.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,155 @@
|
||||
Change Log
|
||||
==========
|
||||
|
||||
### master (unreleased)
|
||||
|
||||
* add certificate type to description
|
||||
|
||||
### 0.7.5 (2017-04-25)
|
||||
|
||||
* Add support for new certificate type (passes)
|
||||
* Fix rich text (#49)
|
||||
* Fix app name (#47)
|
||||
* Update docs pushing to macOS
|
||||
|
||||
### 0.7.4 (2017-01-16)
|
||||
|
||||
* Add support for handshake error (internal error)
|
||||
* Add detection of p12 without password
|
||||
|
||||
### 0.7.3 (2016-09-19)
|
||||
|
||||
* Add support for Watch Kit certificates (DanielFontes)
|
||||
* Add support for handshake error (certificate unknown)
|
||||
|
||||
### 0.7.2 (2016-07-19)
|
||||
|
||||
* Added support for Carthage, thanks to @zats
|
||||
|
||||
### 0.7.1 (2016-04-27)
|
||||
|
||||
* Remove Mac enum from Touch target
|
||||
|
||||
### 0.7.0 (2016-01-07)
|
||||
|
||||
* Add support for simplified certificates (pull request by 666tos)
|
||||
|
||||
### 0.6.4 (2015-12-20)
|
||||
|
||||
* Add support for new certificate types (web, simplified, voip)
|
||||
* Add support for handshake errors (dark wake and closed abort)
|
||||
|
||||
### 0.6.3 (2015-01-22)
|
||||
|
||||
* Add certificate expiration date to listing
|
||||
* Add expiration and revocation error message
|
||||
|
||||
### 0.6.2 (2015-01-15)
|
||||
|
||||
* Add underlying error reason code
|
||||
|
||||
### 0.6.1 (2015-01-14)
|
||||
|
||||
* Add SSL handshake error codes
|
||||
|
||||
### 0.6.0 (2014-10-30)
|
||||
|
||||
* Remove kNWSuccess
|
||||
* Remove deprecated
|
||||
|
||||
### 0.5.4 (2014-10-28)
|
||||
|
||||
* Deprecate fetching
|
||||
* Document all framework stuff
|
||||
|
||||
### 0.5.3 (2014-10-22)
|
||||
|
||||
* Set NWHub defaults to autoConnect:YES
|
||||
* Add a bunch of helpers to NWHub and NWPusher
|
||||
|
||||
### 0.5.2 (2014-10-19)
|
||||
|
||||
* Add convenient connect methods
|
||||
* Introduce Cocoa-style error handling (NSError)
|
||||
* Deprecate C-style error handling (NWError)
|
||||
* Cleanup readme examples
|
||||
* Add troubleshooting in readme
|
||||
|
||||
### 0.5.1 (2014-07-26)
|
||||
|
||||
* Add OpenSSL readme
|
||||
* Fix syntax
|
||||
|
||||
### 0.4.3 (2014-04-09)
|
||||
|
||||
* Add read feedback demo app
|
||||
* Add log view demo app
|
||||
|
||||
### 0.4.2 (2014-03-31)
|
||||
|
||||
* Intro new config format Mac app
|
||||
* Cleanup Mac demo
|
||||
* Add demo device token history
|
||||
|
||||
### 0.4.1 (2014-03-24)
|
||||
|
||||
* Fix leaks
|
||||
* Fix APN error reporting
|
||||
* Add p12 import to demo
|
||||
|
||||
### 0.4.0 (2014-03-23)
|
||||
|
||||
* Add detailed error reporting
|
||||
* Redo readme
|
||||
* Fix demo
|
||||
* Redesign API
|
||||
|
||||
### 0.3.5 (2014-03-21)
|
||||
|
||||
* Support multiple identities
|
||||
* Add inspect tool
|
||||
* Fix mem leak
|
||||
* Fix failed fetch
|
||||
|
||||
### 0.3.4 (2014-03-17)
|
||||
|
||||
* Tweak demo menu
|
||||
* Remove iosock, fix ssl connection
|
||||
* Add support Mac push certs
|
||||
* Merge pull request #7 from AriX/master
|
||||
|
||||
### 0.3.3 (2014-03-03)
|
||||
|
||||
* Add support of expiry and priority
|
||||
* Add support for Apple's third binary format
|
||||
* Fix ssl connect retry
|
||||
* Tweak readme, add links
|
||||
* Merge pull request #5 from ChristianKienle/master
|
||||
* Fix feedback fetching
|
||||
* Add helpers to hub and feedback
|
||||
* Add demo auto-increment payload counter
|
||||
|
||||
### 0.3.2 (2014-01-27)
|
||||
|
||||
* Cleanup project
|
||||
|
||||
### 0.3.1 (2014-01-27)
|
||||
|
||||
* Add Podspec
|
||||
* Auto sandbox detection
|
||||
* Add NWHub on top of NWPusher
|
||||
* Lots of demo fixes
|
||||
|
||||
### 0.3.0 (2014-01-15)
|
||||
|
||||
* Add support for new push format
|
||||
* Add troubleshooting in readme
|
||||
* Add reconnect to demo app
|
||||
|
||||
### 0.2.0 (2013-04-27)
|
||||
|
||||
* Add demo application
|
||||
|
||||
### 0.1.0 (2012-09-10)
|
||||
|
||||
* First release of the framework
|
||||
@@ -10,31 +10,144 @@
|
||||
|
||||
@class NWNotification, NWPusher;
|
||||
|
||||
|
||||
/** Allows callback on errors while pushing to and reading from server.
|
||||
|
||||
Check out `NWHub` for more details.
|
||||
*/
|
||||
@protocol NWHubDelegate <NSObject>
|
||||
- (void)notification:(NWNotification *)notification didFailWithResult:(NWError)result;
|
||||
/** The notification failed during or after pushing. */
|
||||
- (void)notification:(NWNotification *)notification didFailWithError:(NSError *)error;
|
||||
@end
|
||||
|
||||
|
||||
/** Helper on top of `NWPusher` that hides the details of pushing and reading.
|
||||
|
||||
This class provides a more convenient way of pushing notifications to the APNs. It deals with the trouble of assigning a unique identifier to every notification and the handling of error responses from the server. It hides the latency that comes with transmitting the pushes, allowing you to simply push your notifications and getting notified of errors through the delegate. If this feels over-abstracted, then definitely check out the `NWPusher` class, which will give you full control.
|
||||
|
||||
There are two set of methods for pushing notifications: the easy and the pros. The former will just do the pushing and reconnect if the connection breaks. This is your low-worry solution, provided that you call `readFailed` every so often (seconds) to handle error data from the server. The latter will give you a little more control and a little more responsibility.
|
||||
*/
|
||||
@interface NWHub : NSObject
|
||||
|
||||
/** @name Properties */
|
||||
|
||||
/** The pusher instance that does the actual work. */
|
||||
@property (nonatomic, strong) NWPusher *pusher;
|
||||
|
||||
/** Assign a delegate to get notified when something fails during or after pushing. */
|
||||
@property (nonatomic, weak) id<NWHubDelegate> delegate;
|
||||
|
||||
/** The type of notification serialization we'll be using. */
|
||||
@property (nonatomic, assign) NWNotificationType type;
|
||||
|
||||
/** The timespan we'll hold on to a notification after pushing, allowing the server to respond. */
|
||||
@property (nonatomic, assign) NSTimeInterval feedbackSpan;
|
||||
|
||||
/** The index incremented on every notification push, used as notification identifier. */
|
||||
@property (nonatomic, assign) NSUInteger index;
|
||||
|
||||
/** @name Initialization */
|
||||
|
||||
/** Create and return a hub object with a delegate object assigned. */
|
||||
- (instancetype)initWithDelegate:(id<NWHubDelegate>)delegate;
|
||||
|
||||
/** Create and return a hub object with a delegate and pusher object assigned. */
|
||||
- (instancetype)initWithPusher:(NWPusher *)pusher delegate:(id<NWHubDelegate>)delegate;
|
||||
|
||||
- (NWError)connectWithIdentity:(NWIdentityRef)identity;
|
||||
- (NWError)connectWithPKCS12Data:(NSData *)data password:(NSString *)password;
|
||||
- (NWError)reconnect;
|
||||
/** Create, connect and returns an instance with delegate and identity. */
|
||||
+ (instancetype)connectWithDelegate:(id<NWHubDelegate>)delegate identity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** Create, connect and returns an instance with delegate and identity. */
|
||||
+ (instancetype)connectWithDelegate:(id<NWHubDelegate>)delegate PKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** @name Connecting */
|
||||
|
||||
/** Connect the pusher using the identity to setup the SSL connection. */
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** Connect the pusher using the PKCS #12 data to setup the SSL connection. */
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** Reconnect with the server, to recover from a closed or defect connection. */
|
||||
- (BOOL)reconnectWithError:(NSError **)error;
|
||||
|
||||
/** Close the connection, allows reconnecting. */
|
||||
- (void)disconnect;
|
||||
|
||||
/** @name Pushing (easy) */
|
||||
|
||||
/** Push a JSON string payload to a device with token string.
|
||||
@see pushNotifications:
|
||||
*/
|
||||
- (NSUInteger)pushPayload:(NSString *)payload token:(NSString *)token;
|
||||
|
||||
/** Push a JSON string payload to multiple devices with token strings.
|
||||
@see pushNotifications:
|
||||
*/
|
||||
- (NSUInteger)pushPayload:(NSString *)payload tokens:(NSArray *)tokens;
|
||||
|
||||
/** Push multiple JSON string payloads to a device with token string.
|
||||
@see pushNotifications:
|
||||
*/
|
||||
- (NSUInteger)pushPayloads:(NSArray *)payloads token:(NSString *)token;
|
||||
- (NSUInteger)pushNotifications:(NSArray *)notifications autoReconnect:(BOOL)reconnect;
|
||||
- (NSUInteger)flushFailed;
|
||||
|
||||
/** Push multiple notifications, each representing a payload and a device token.
|
||||
|
||||
This will assign each notification a unique identifier if none was set yet. If pushing fails it will reconnect. This method can be used rather carelessly; any thing goes. However, this also means that a failed notification might break the connection temporarily, losing a notification here or there. If you are sending bulk and don't care too much about this, then you'll be fine. If not, consider using `pushNotification:autoReconnect:error:`.
|
||||
|
||||
Make sure to call `readFailed` on a regular basis to allow server error responses to be handled and the delegate to be called.
|
||||
|
||||
Returns the number of notifications that failed, preferably zero.
|
||||
|
||||
@see readFailed
|
||||
*/
|
||||
- (NSUInteger)pushNotifications:(NSArray *)notifications;
|
||||
|
||||
/** Read the response from the server to see if any pushes have failed.
|
||||
|
||||
Due to transmission latency it usually takes a couple of milliseconds for the server to respond to errors. This methods reads the server response and handles the errors. Make sure to call this regularly to catch up on malformed notifications.
|
||||
|
||||
@see pushNotifications:
|
||||
*/
|
||||
- (NSUInteger)readFailed;
|
||||
|
||||
/** @name Pushing (pros) */
|
||||
|
||||
/** Push a notification and reconnect if anything failed.
|
||||
|
||||
This will assign the notification a unique (incremental) identifier and feed it to the internal pusher. If this succeeds, the notification is stored for later lookup by `readFailed:autoReconnect:error:`. If it fails, the delegate will be invoked and it will reconnect if set to auto-reconnect.
|
||||
|
||||
@see readFailed:autoReconnect:error:
|
||||
*/
|
||||
- (BOOL)pushNotification:(NWNotification *)notification autoReconnect:(BOOL)reconnect error:(NSError **)error;
|
||||
|
||||
/** Read the response from the server and reconnect if anything failed.
|
||||
|
||||
If the APNs finds something wrong with a notification, it will write back the identifier and error code. As this involves transmission to and from the server, it takes just a little while to get this failure info. This method should therefore be invoked a little (say a second) after pushing to see if anything was wrong. On a slow connection this might take longer than the interval between push messages, in which case the reported notification was *not* the last one sent.
|
||||
|
||||
From the server we only get the notification identifier and the error message. This method translates this back into the original notification by keeping track of all notifications sent in the past 30 seconds. If somehow the original notification cannot be found, it will assign `NSNull`.
|
||||
|
||||
Usually, when a notification fails, the server will drop the connection. To prevent this from causing any more problems, the connection can be reestablished by setting it to reconnect automatically.
|
||||
|
||||
@see trimIdentifiers
|
||||
@see feedbackSpan
|
||||
*/
|
||||
- (BOOL)readFailed:(NWNotification **)notification autoReconnect:(BOOL)reconnect error:(NSError **)error;
|
||||
|
||||
/** Let go of old notification, after you read the failed notifications.
|
||||
|
||||
This class keeps track of all notifications sent so we can look them up later based on their identifier. This allows it to translate identifiers back into the original notification. To limit the amount of memory all older notifications should be trimmed from this lookup, which is done by this method. This is done based on the `feedbackSpan`, which defaults to 30 seconds.
|
||||
|
||||
Be careful not to call this function without first reading all failed notifications, using `readFailed:autoReconnect:error:`.
|
||||
|
||||
@see readFailed:autoReconnect:error:
|
||||
@see feedbackSpan
|
||||
*/
|
||||
- (BOOL)trimIdentifiers;
|
||||
|
||||
// deprecated
|
||||
|
||||
+ (instancetype)connectWithDelegate:(id<NWHubDelegate>)delegate identity:(NWIdentityRef)identity error:(NSError **)error __deprecated;
|
||||
+ (instancetype)connectWithDelegate:(id<NWHubDelegate>)delegate PKCS12Data:(NSData *)data password:(NSString *)password error:(NSError **)error __deprecated;
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity error:(NSError **)error __deprecated;
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError **)error __deprecated;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
|
||||
@implementation NWHub {
|
||||
NSUInteger _index;
|
||||
NSMutableDictionary *_notificationForIdentifier;
|
||||
}
|
||||
|
||||
@@ -43,19 +42,19 @@
|
||||
|
||||
#pragma mark - Connecting
|
||||
|
||||
- (NWError)connectWithIdentity:(NWIdentityRef)identity
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [_pusher connectWithIdentity:identity];
|
||||
return [_pusher connectWithIdentity:identity environment:environment error:error];
|
||||
}
|
||||
|
||||
- (NWError)connectWithPKCS12Data:(NSData *)data password:(NSString *)password
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [_pusher connectWithPKCS12Data:data password:password];
|
||||
return [_pusher connectWithPKCS12Data:data password:password environment:environment error:error];
|
||||
}
|
||||
|
||||
- (NWError)reconnect
|
||||
- (BOOL)reconnectWithError:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [_pusher reconnect];
|
||||
return [_pusher reconnectWithError:error];
|
||||
}
|
||||
|
||||
- (void)disconnect
|
||||
@@ -63,96 +62,169 @@
|
||||
[_pusher disconnect];
|
||||
}
|
||||
|
||||
+ (instancetype)connectWithDelegate:(id<NWHubDelegate>)delegate identity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NWHub *hub = [[NWHub alloc] initWithDelegate:delegate];
|
||||
return identity && [hub connectWithIdentity:identity environment:environment error:error] ? hub : nil;
|
||||
}
|
||||
|
||||
#pragma mark - Pushing
|
||||
+ (instancetype)connectWithDelegate:(id<NWHubDelegate>)delegate PKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NWHub *hub = [[NWHub alloc] initWithDelegate:delegate];
|
||||
return data && [hub connectWithPKCS12Data:data password:password environment:environment error:error] ? hub : nil;
|
||||
}
|
||||
|
||||
#pragma mark - Pushing without NSError
|
||||
|
||||
- (NSUInteger)pushPayload:(NSString *)payload token:(NSString *)token
|
||||
{
|
||||
NSUInteger identifier = _index++;
|
||||
NWNotification *notification = [[NWNotification alloc] initWithPayload:payload token:token identifier:identifier expiration:nil priority:0];
|
||||
return [self pushNotifications:@[notification] autoReconnect:NO];
|
||||
NWNotification *notification = [[NWNotification alloc] initWithPayload:payload token:token identifier:0 expiration:nil priority:0];
|
||||
return [self pushNotifications:@[notification]];
|
||||
}
|
||||
|
||||
- (NSUInteger)pushPayload:(NSString *)payload tokens:(NSArray *)tokens
|
||||
{
|
||||
NSMutableArray *notifications = @[].mutableCopy;
|
||||
for (NSString *token in tokens) {
|
||||
NSUInteger identifier = _index++;
|
||||
NWNotification *notification = [[NWNotification alloc] initWithPayload:payload token:token identifier:identifier expiration:nil priority:0];
|
||||
NWNotification *notification = [[NWNotification alloc] initWithPayload:payload token:token identifier:0 expiration:nil priority:0];
|
||||
[notifications addObject:notification];
|
||||
}
|
||||
return [self pushNotifications:notifications autoReconnect:NO];
|
||||
return [self pushNotifications:notifications];
|
||||
}
|
||||
|
||||
- (NSUInteger)pushPayloads:(NSArray *)payloads token:(NSString *)token
|
||||
{
|
||||
NSMutableArray *notifications = @[].mutableCopy;
|
||||
for (NSString *payload in payloads) {
|
||||
NSUInteger identifier = _index++;
|
||||
NWNotification *notification = [[NWNotification alloc] initWithPayload:payload token:token identifier:identifier expiration:nil priority:0];
|
||||
NWNotification *notification = [[NWNotification alloc] initWithPayload:payload token:token identifier:0 expiration:nil priority:0];
|
||||
[notifications addObject:notification];
|
||||
}
|
||||
return [self pushNotifications:notifications autoReconnect:NO];
|
||||
return [self pushNotifications:notifications];
|
||||
}
|
||||
|
||||
- (NSUInteger)pushNotifications:(NSArray *)notifications autoReconnect:(BOOL)reconnect
|
||||
- (NSUInteger)pushNotifications:(NSArray *)notifications
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
NSUInteger fails = 0;
|
||||
for (NWNotification *notification in notifications) {
|
||||
if (!notification.identifier) notification.identifier = _index++;
|
||||
BOOL failed = [self pushNotification:notification autoReconnect:reconnect];
|
||||
if (failed) count++;
|
||||
BOOL success = [self pushNotification:notification autoReconnect:YES error:nil];
|
||||
if (!success) {
|
||||
fails++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
return fails;
|
||||
}
|
||||
|
||||
- (BOOL)pushNotification:(NWNotification *)notification autoReconnect:(BOOL)reconnect
|
||||
#pragma mark - Pushing with NSError
|
||||
|
||||
- (BOOL)pushNotification:(NWNotification *)notification autoReconnect:(BOOL)reconnect error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NWError pushed = [_pusher pushNotification:notification type:_type];
|
||||
if (pushed != kNWSuccess) {
|
||||
[_delegate notification:notification didFailWithResult:pushed];
|
||||
}
|
||||
if (reconnect && pushed == kNWErrorWriteClosedGraceful) {
|
||||
[self reconnect];
|
||||
if (!notification.identifier) notification.identifier = _index++;
|
||||
NSError *e = nil;
|
||||
BOOL pushed = [_pusher pushNotification:notification type:_type error:&e];
|
||||
if (!pushed) {
|
||||
if (error) *error = e;
|
||||
if ([_delegate respondsToSelector:@selector(notification:didFailWithError:)]) {
|
||||
[_delegate notification:notification didFailWithError:e];
|
||||
}
|
||||
if (reconnect && e.code == kNWErrorWriteClosedGraceful) {
|
||||
[self reconnectWithError:error];
|
||||
}
|
||||
return pushed;
|
||||
}
|
||||
_notificationForIdentifier[@(notification.identifier)] = @[notification, NSDate.date];
|
||||
return pushed != kNWSuccess;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)fetchFailed
|
||||
- (BOOL)pushNotifications:(NSArray *)notifications autoReconnect:(BOOL)reconnect error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
for (NWNotification *notification in notifications) {
|
||||
BOOL success = [self pushNotification:notification autoReconnect:reconnect error:error];
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Reading failed
|
||||
|
||||
- (NSUInteger)readFailed
|
||||
{
|
||||
NSArray *failed = nil;
|
||||
[self readFailed:&failed max:1000 autoReconnect:YES error:nil];
|
||||
return failed.count;
|
||||
}
|
||||
|
||||
- (BOOL)readFailed:(NSArray **)notifications max:(NSUInteger)max autoReconnect:(BOOL)reconnect error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSMutableArray *n = @[].mutableCopy;
|
||||
for (NSUInteger i = 0; i < max; i++) {
|
||||
NWNotification *notification = nil;
|
||||
BOOL read = [self readFailed:¬ification autoReconnect:reconnect error:error];
|
||||
if (!read) {
|
||||
return read;
|
||||
}
|
||||
if (!notification) {
|
||||
break;
|
||||
}
|
||||
[n addObject:notification];
|
||||
}
|
||||
if (notifications) *notifications = n;
|
||||
[self trimIdentifiers];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)readFailed:(NWNotification **)notification autoReconnect:(BOOL)reconnect error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSUInteger identifier = 0;
|
||||
NWError apnError = kNWSuccess;
|
||||
NWError fetch = [_pusher fetchFailedIdentifier:&identifier apnError:&apnError];
|
||||
if (fetch != kNWSuccess) {
|
||||
return NO;
|
||||
NSError *apnError = nil;
|
||||
BOOL read = [_pusher readFailedIdentifier:&identifier apnError:&apnError error:error];
|
||||
if (!read) {
|
||||
return read;
|
||||
}
|
||||
if (identifier || apnError != kNWSuccess) {
|
||||
NWNotification *notification = _notificationForIdentifier[@(identifier)][0];
|
||||
[_delegate notification:notification didFailWithResult:apnError];
|
||||
return YES;
|
||||
if (apnError) {
|
||||
NWNotification *n = _notificationForIdentifier[@(identifier)][0];
|
||||
if (notification) *notification = n ?: (NWNotification *)NSNull.null;
|
||||
if ([_delegate respondsToSelector:@selector(notification:didFailWithError:)]) {
|
||||
[_delegate notification:n didFailWithError:apnError];
|
||||
}
|
||||
if (reconnect) {
|
||||
[self reconnectWithError:error];
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)collectGarbage
|
||||
- (BOOL)trimIdentifiers
|
||||
{
|
||||
NSDate *oldBefore = [NSDate dateWithTimeIntervalSinceNow:-_feedbackSpan];
|
||||
NSArray *old = [[_notificationForIdentifier keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
|
||||
return [oldBefore compare:obj[1]] == NSOrderedDescending;
|
||||
}] allObjects];
|
||||
[_notificationForIdentifier removeObjectsForKeys:old];
|
||||
return old.count;
|
||||
return !!old.count;
|
||||
}
|
||||
|
||||
- (NSUInteger)flushFailed
|
||||
#pragma mark - Deprecated
|
||||
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
for (BOOL failed = YES; failed; count++) {
|
||||
failed = [self fetchFailed];
|
||||
}
|
||||
[self collectGarbage];
|
||||
return count - 1;
|
||||
return [self connectWithIdentity:identity environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithPKCS12Data:data password:password environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
+ (instancetype)connectWithDelegate:(id<NWHubDelegate>)delegate identity:(NWIdentityRef)identity error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithDelegate:delegate identity:identity environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
+ (instancetype)connectWithDelegate:(id<NWHubDelegate>)delegate PKCS12Data:(NSData *)data password:(NSString *)password error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithDelegate:delegate identity:data environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,25 +8,66 @@
|
||||
#import "NWType.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
/** A single push message, containing the receiver device token, the payload, and delivery attributes.
|
||||
|
||||
This class represents a single push message, or *remote notification* as Apple calls it. It consists of device token, payload, and some optional attributes. The device token is a unique reference to a single installed app on a single Apple device. The payload is a JSON-formatted string that is delivered into the app. Among app-specific data, this payload contains information on how the device should handle and display this notification.
|
||||
|
||||
Then there is a number of additional attributes Apple has been adding over the years. The *identifier* is used in error data that we get back from the server. This allows us to associate the error with the notification. The *expiration* date tells the delivery system when it should stop trying to deliver the notification to the device. Priority indicates whether to conserve power on delivery.
|
||||
|
||||
There are different data formats into which a notification can be serialized. Older formats do not support all attributes. While this class supports all formats, it uses the latest format by default.
|
||||
|
||||
Read more about this in Apple's documentation under *Provider Communication with Apple Push Notification Service* and *The Notification Payload*.
|
||||
*/
|
||||
@interface NWNotification : NSObject
|
||||
|
||||
/** @name Properties */
|
||||
|
||||
/** String representation of serialized JSON. */
|
||||
@property (nonatomic, strong) NSString *payload;
|
||||
|
||||
/** UTF-8 data representation of serialized JSON. */
|
||||
@property (nonatomic, strong) NSData *payloadData;
|
||||
|
||||
/** Hex string representation of the device token. */
|
||||
@property (nonatomic, strong) NSString *token;
|
||||
|
||||
/** Data representation of the device token. */
|
||||
@property (nonatomic, strong) NSData *tokenData;
|
||||
|
||||
/** Identifier used for correlating server response on error. */
|
||||
@property (nonatomic, assign) NSUInteger identifier;
|
||||
|
||||
/** The expiration date after which the server will not attempt to deliver. */
|
||||
@property (nonatomic, strong) NSDate *expiration;
|
||||
|
||||
/** Epoch seconds representation of expiration date. */
|
||||
@property (nonatomic, assign) NSUInteger expirationStamp;
|
||||
|
||||
/** Notification priority used by server for delivery optimization. */
|
||||
@property (nonatomic, assign) NSUInteger priority;
|
||||
|
||||
/** Indicates whether the expiration date should be serialized. */
|
||||
@property (nonatomic, assign) BOOL addExpiration;
|
||||
|
||||
/** @name Initialization */
|
||||
|
||||
/** Create and returns a notification object based on given attribute objects. */
|
||||
- (instancetype)initWithPayload:(NSString *)payload token:(NSString *)token identifier:(NSUInteger)identifier expiration:(NSDate *)date priority:(NSUInteger)priority;
|
||||
|
||||
/** Create and returns a notification object based on given raw attributes. */
|
||||
- (instancetype)initWithPayloadData:(NSData *)payload tokenData:(NSData *)token identifier:(NSUInteger)identifier expirationStamp:(NSUInteger)expirationStamp addExpiration:(BOOL)addExpiration priority:(NSUInteger)priority;
|
||||
|
||||
/** @name Serialization */
|
||||
|
||||
/** Serialize this notification using provided format. */
|
||||
- (NSData *)dataWithType:(NWNotificationType)type;
|
||||
|
||||
/** @name Helpers */
|
||||
|
||||
/** Converts a hex string into binary data. */
|
||||
+ (NSData *)dataFromHex:(NSString *)hex;
|
||||
|
||||
/** Converts binary data into a hex string. */
|
||||
+ (NSString *)hexFromData:(NSData *)data;
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,9 +13,6 @@ static NSUInteger const NWPayloadMaxSize = 256;
|
||||
|
||||
@implementation NWNotification
|
||||
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)initWithPayload:(NSString *)payload token:(NSString *)token identifier:(NSUInteger)identifier expiration:(NSDate *)date priority:(NSUInteger)priority
|
||||
{
|
||||
self = [super init];
|
||||
@@ -43,6 +40,8 @@ static NSUInteger const NWPayloadMaxSize = 256;
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Accessors
|
||||
|
||||
- (NSString *)payload
|
||||
{
|
||||
return _payloadData ? [[NSString alloc] initWithData:_payloadData encoding:NSUTF8StringEncoding] : nil;
|
||||
|
||||
@@ -8,15 +8,57 @@
|
||||
#import "NWType.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class NWSSLConnection;
|
||||
|
||||
/** Reads tokens and dates from the APNs feedback service.
|
||||
|
||||
The feedback service is a separate server that provides a list of all device tokens that it tried to deliver a notification to, but was unable to. This usually indicates that this device no longer has the app installed. This way, the feedback service provides reliable way of finding out who uninstalled the app, which can be fed back into your database.
|
||||
|
||||
Apple recommends reading from the service once a day. After a device token has been read, it will not be returned again until the next failed delivery. In practice: connect once a day, read all device tokens, and update your own database accordingly.
|
||||
|
||||
Read more in Apple's documentation under *The Feedback Service*.
|
||||
*/
|
||||
@interface NWPushFeedback : NSObject
|
||||
|
||||
- (NWError)connectWithIdentity:(NWIdentityRef)identity;
|
||||
- (NWError)connectWithPKCS12Data:(NSData *)data password:(NSString *)password;
|
||||
/** @name Properties */
|
||||
|
||||
@property (nonatomic, strong) NWSSLConnection *connection;
|
||||
|
||||
/** @name Initialization */
|
||||
|
||||
/** Setup connection with feedback service based on identity. */
|
||||
+ (instancetype)connectWithIdentity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** Setup connection with feedback service based on PKCS #12 data. */
|
||||
+ (instancetype)connectWithPKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** @name Connecting */
|
||||
|
||||
/** Connect with feedback service based on identity. */
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** Connect with feedback service based on PKCS #12 data. */
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** Disconnect from feedback service. The server will automatically drop the connection after all feedback data has been read. */
|
||||
- (void)disconnect;
|
||||
|
||||
- (NWError)readTokenData:(NSData **)token date:(NSDate **)date;
|
||||
- (NWError)readToken:(NSString **)token date:(NSDate **)date;
|
||||
- (NWError)readTokenDatePairs:(NSArray **)pairs max:(NSUInteger)max;
|
||||
/** @name Reading */
|
||||
|
||||
/** Read a single token-date pair, where token is data. */
|
||||
- (BOOL)readTokenData:(NSData **)token date:(NSDate **)date error:(NSError **)error;
|
||||
|
||||
/** Read a single token-date pair, where token is hex string. */
|
||||
- (BOOL)readToken:(NSString **)token date:(NSDate **)date error:(NSError **)error;
|
||||
|
||||
/** Read all (or max) token-date pairs, where token is hex string. */
|
||||
- (NSArray *)readTokenDatePairsWithMax:(NSUInteger)max error:(NSError **)error;
|
||||
|
||||
// deprecated
|
||||
|
||||
+ (instancetype)connectWithIdentity:(NWIdentityRef)identity error:(NSError **)error __deprecated;
|
||||
+ (instancetype)connectWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError **)error __deprecated;
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity error:(NSError **)error __deprecated;
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError **)error __deprecated;
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,33 +16,31 @@ static NSString * const NWPushHost = @"feedback.push.apple.com";
|
||||
static NSUInteger const NWPushPort = 2196;
|
||||
static NSUInteger const NWTokenMaxSize = 32;
|
||||
|
||||
@implementation NWPushFeedback {
|
||||
NWSSLConnection *_connection;
|
||||
}
|
||||
@implementation NWPushFeedback
|
||||
|
||||
#pragma mark - Connecting
|
||||
|
||||
#pragma mark - Apple SSL
|
||||
|
||||
- (NWError)connectWithIdentity:(NWIdentityRef)identity
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
if (_connection) [_connection disconnect]; _connection = nil;
|
||||
NSString *host = [NWSecTools isSandboxIdentity:identity] ? NWSandboxPushHost : NWPushHost;
|
||||
if (environment == NWEnvironmentAuto) environment = [NWSecTools environmentForIdentity:identity];
|
||||
NSString *host = (environment == NWEnvironmentSandbox) ? NWSandboxPushHost : NWPushHost;
|
||||
NWSSLConnection *connection = [[NWSSLConnection alloc] initWithHost:host port:NWPushPort identity:identity];
|
||||
NWError result = [connection connect];
|
||||
if (result == kNWSuccess) {
|
||||
_connection = connection;
|
||||
BOOL connected = [connection connectWithError:error];
|
||||
if (!connected) {
|
||||
return connected;
|
||||
}
|
||||
return result;
|
||||
_connection = connection;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NWError)connectWithPKCS12Data:(NSData *)data password:(NSString *)password
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NWIdentityRef identity = nil;
|
||||
NWError result = [NWSecTools identityWithPKCS12Data:data password:password identity:&identity];
|
||||
if (result != kNWSuccess) {
|
||||
return result;
|
||||
NWIdentityRef identity = [NWSecTools identityWithPKCS12Data:data password:password error:error];
|
||||
if (!identity) {
|
||||
return NO;
|
||||
}
|
||||
return [self connectWithIdentity:identity];
|
||||
return [self connectWithIdentity:identity environment:environment error:error];
|
||||
}
|
||||
|
||||
- (void)disconnect
|
||||
@@ -50,21 +48,33 @@ static NSUInteger const NWTokenMaxSize = 32;
|
||||
[_connection disconnect]; _connection = nil;
|
||||
}
|
||||
|
||||
+ (instancetype)connectWithIdentity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NWPushFeedback *feedback = [[NWPushFeedback alloc] init];
|
||||
return identity && [feedback connectWithIdentity:identity environment:environment error:error] ? feedback : nil;
|
||||
}
|
||||
|
||||
#pragma mark - Apple push
|
||||
+ (instancetype)connectWithPKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NWPushFeedback *feedback = [[NWPushFeedback alloc] init];
|
||||
return data && [feedback connectWithPKCS12Data:data password:password environment:environment error:error] ? feedback : nil;
|
||||
}
|
||||
|
||||
- (NWError)readTokenData:(NSData **)token date:(NSDate **)date
|
||||
|
||||
#pragma mark - Reading feedback
|
||||
|
||||
- (BOOL)readTokenData:(NSData **)token date:(NSDate **)date error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
*token = nil;
|
||||
*date = nil;
|
||||
NSMutableData *data = [NSMutableData dataWithLength:sizeof(uint32_t) + sizeof(uint16_t) + NWTokenMaxSize];
|
||||
NSUInteger length = 0;
|
||||
NWError read = [_connection read:data length:&length];
|
||||
if (read != kNWSuccess || length == 0) {
|
||||
BOOL read = [_connection read:data length:&length error:error];
|
||||
if (!read || length == 0) {
|
||||
return read;
|
||||
}
|
||||
if (length != data.length) {
|
||||
return kNWErrorFeedbackLength;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorFeedbackLength reason:length error:error];
|
||||
}
|
||||
uint32_t time = 0;
|
||||
[data getBytes:&time range:NSMakeRange(0, 4)];
|
||||
@@ -73,43 +83,66 @@ static NSUInteger const NWTokenMaxSize = 32;
|
||||
[data getBytes:&l range:NSMakeRange(4, 2)];
|
||||
NSUInteger tokenLength = htons(l);
|
||||
if (tokenLength != NWTokenMaxSize) {
|
||||
return kNWErrorFeedbackTokenLength;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorFeedbackTokenLength reason:tokenLength error:error];
|
||||
}
|
||||
*token = [data subdataWithRange:NSMakeRange(6, length - 6)];
|
||||
return kNWSuccess;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NWError)readToken:(NSString **)token date:(NSDate **)date;
|
||||
- (BOOL)readToken:(NSString **)token date:(NSDate **)date error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
*token = nil;
|
||||
NSData *data = nil;
|
||||
NWError read = [self readTokenData:&data date:date];
|
||||
if (read != kNWSuccess) {
|
||||
BOOL read = [self readTokenData:&data date:date error:error];
|
||||
if (!read) {
|
||||
return read;
|
||||
}
|
||||
if (data) *token = [NWNotification hexFromData:data];
|
||||
return read;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NWError)readTokenDatePairs:(NSArray **)pairs max:(NSUInteger)max
|
||||
- (NSArray *)readTokenDatePairsWithMax:(NSUInteger)max error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSMutableArray *all = @[].mutableCopy;
|
||||
*pairs = all;
|
||||
NSMutableArray *pairs = @[].mutableCopy;
|
||||
for (NSUInteger i = 0; i < max; i++) {
|
||||
NSString *token = nil;
|
||||
NSDate *date = nil;
|
||||
NWError read = [self readToken:&token date:&date];
|
||||
if (read == kNWErrorReadClosedGraceful) {
|
||||
NSError *e = nil;
|
||||
BOOL read = [self readToken:&token date:&date error:&e];
|
||||
if (!read && e.code == kNWErrorReadClosedGraceful) {
|
||||
break;
|
||||
}
|
||||
if (read != kNWSuccess) {
|
||||
return read;
|
||||
if (!read) {
|
||||
if (error) *error = e;
|
||||
return nil;
|
||||
}
|
||||
if (token && date) {
|
||||
[all addObject:@[token, date]];
|
||||
[pairs addObject:@[token, date]];
|
||||
}
|
||||
}
|
||||
return kNWSuccess;
|
||||
return pairs;
|
||||
}
|
||||
|
||||
#pragma mark - Deprecated
|
||||
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithIdentity:identity environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithPKCS12Data:data password:password environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
+ (instancetype)connectWithIdentity:(NWIdentityRef)identity error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithIdentity:identity environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
+ (instancetype)connectWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithPKCS12Data:data password:password environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,18 +10,68 @@
|
||||
|
||||
@class NWNotification, NWSSLConnection;
|
||||
|
||||
|
||||
/** Serializes notification objects and pushes them to the APNs.
|
||||
|
||||
This is the heart of the framework. As the (inconvenient) name suggest, it's also one of the first classes that was added to the framework. This class provides a straightforward interface to the APNs, including connecting, pushing to and reading from the server.
|
||||
|
||||
Connecting is done based on an identity or PKCS #12 data. The identity is an instance of `SecIdentityRef` and contains a certificate and private key. The PKCS #12 data can be deserialized into such an identity. One can reconnect or disconnect at any time, and should if the connection has been dropped by the server. The latter can happen quite easily, for example when there is something wrong with the device token or payload of the notification.
|
||||
|
||||
Notifications are pushed one at a time. It is serialized and sent over the wire. If the server then concludes there is something wrong with that notification, it will write back error data. If you send out multiple notifications in a row, these errors might not match up. Therefore every error contains the identifier of the erroneous notification.
|
||||
|
||||
Make sure to read this error data from the server, so you can lookup the notification that caused it and prevent the issue in the future. As mentioned earlier, the server easily drops the connection if there is something out of the ordinary. NB: if you read right after pushing, it is very unlikely that data about that push already got back from the server.
|
||||
|
||||
Make sure to read Apple's documentation on *Apple Push Notification Service* and *Provider Communication*.
|
||||
*/
|
||||
@interface NWPusher : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NWSSLConnection *connection;
|
||||
/** @name Properties */
|
||||
|
||||
- (NWError)connectWithIdentity:(NWIdentityRef)identity;
|
||||
- (NWError)connectWithPKCS12Data:(NSData *)data password:(NSString *)password;
|
||||
- (NWError)reconnect;
|
||||
/** The SSL connection through which all notifications are pushed. */
|
||||
@property (nonatomic, strong) NWSSLConnection *connection;
|
||||
|
||||
/** @name Initialization */
|
||||
|
||||
/** Creates, connects and returns a pusher object based on the provided identity. */
|
||||
+ (instancetype)connectWithIdentity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** Creates, connects and returns a pusher object based on the PKCS #12 data. */
|
||||
+ (instancetype)connectWithPKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** @name Connecting */
|
||||
|
||||
/** Connect with the APNs using the identity. */
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** Connect with the APNs using the identity from PKCS #12 data. */
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError **)error;
|
||||
|
||||
/** Reconnect using the same identity, disconnects if necessary. */
|
||||
- (BOOL)reconnectWithError:(NSError **)error;
|
||||
|
||||
/** Disconnect from the server, allows reconnect. */
|
||||
- (void)disconnect;
|
||||
|
||||
- (NWError)pushPayload:(NSString *)payload token:(NSString *)token identifier:(NSUInteger)identifier;
|
||||
- (NWError)pushNotification:(NWNotification *)notification type:(NWNotificationType)type;
|
||||
- (NWError)fetchFailedIdentifier:(NSUInteger *)identifier apnError:(NWError *)apnError;
|
||||
/** @name Pushing */
|
||||
|
||||
/** Push a JSON string payload to a device with token string, assign identifier. */
|
||||
- (BOOL)pushPayload:(NSString *)payload token:(NSString *)token identifier:(NSUInteger)identifier error:(NSError **)error;
|
||||
|
||||
/** Push a notification using push type for serialization. */
|
||||
- (BOOL)pushNotification:(NWNotification *)notification type:(NWNotificationType)type error:(NSError **)error;
|
||||
|
||||
/** @name Reading */
|
||||
|
||||
/** Read back from the server the notification identifiers of failed pushes. */
|
||||
- (BOOL)readFailedIdentifier:(NSUInteger *)identifier apnError:(NSError **)apnError error:(NSError **)error;
|
||||
|
||||
/** Read back multiple notification identifiers of, up to max, failed pushes. */
|
||||
- (NSArray *)readFailedIdentifierErrorPairsWithMax:(NSUInteger)max error:(NSError **)error;
|
||||
|
||||
// deprecated
|
||||
|
||||
+ (instancetype)connectWithIdentity:(NWIdentityRef)identity error:(NSError **)error __deprecated;
|
||||
+ (instancetype)connectWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError **)error __deprecated;
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity error:(NSError **)error __deprecated;
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError **)error __deprecated;
|
||||
|
||||
@end
|
||||
|
||||
@@ -15,41 +15,39 @@ static NSString * const NWSandboxPushHost = @"gateway.sandbox.push.apple.com";
|
||||
static NSString * const NWPushHost = @"gateway.push.apple.com";
|
||||
static NSUInteger const NWPushPort = 2195;
|
||||
|
||||
@implementation NWPusher {
|
||||
NSUInteger _index;
|
||||
}
|
||||
@implementation NWPusher
|
||||
|
||||
#pragma mark - Connecting
|
||||
|
||||
#pragma mark - Apple SSL
|
||||
|
||||
- (NWError)connectWithIdentity:(NWIdentityRef)identity
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
if (_connection) [_connection disconnect]; _connection = nil;
|
||||
NSString *host = [NWSecTools isSandboxIdentity:identity] ? NWSandboxPushHost : NWPushHost;
|
||||
if (environment == NWEnvironmentAuto) environment = [NWSecTools environmentForIdentity:identity];
|
||||
NSString *host = (environment == NWEnvironmentSandbox) ? NWSandboxPushHost : NWPushHost;
|
||||
NWSSLConnection *connection = [[NWSSLConnection alloc] initWithHost:host port:NWPushPort identity:identity];
|
||||
NWError result = [connection connect];
|
||||
if (result == kNWSuccess) {
|
||||
_connection = connection;
|
||||
BOOL connected = [connection connectWithError:error];
|
||||
if (!connected) {
|
||||
return connected;
|
||||
}
|
||||
return result;
|
||||
_connection = connection;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NWError)connectWithPKCS12Data:(NSData *)data password:(NSString *)password
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NWIdentityRef identity = nil;
|
||||
NWError result = [NWSecTools identityWithPKCS12Data:data password:password identity:&identity];
|
||||
if (result != kNWSuccess) {
|
||||
return result;
|
||||
NWIdentityRef identity = [NWSecTools identityWithPKCS12Data:data password:password error:error];
|
||||
if (!identity) {
|
||||
return NO;
|
||||
}
|
||||
return [self connectWithIdentity:identity];
|
||||
return [self connectWithIdentity:identity environment:environment error:error];
|
||||
}
|
||||
|
||||
- (NWError)reconnect
|
||||
- (BOOL)reconnectWithError:(NSError *__autoreleasing *)error
|
||||
{
|
||||
if (!_connection) {
|
||||
return kNWErrorPushNotConnected;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorPushNotConnected error:error];
|
||||
}
|
||||
return [_connection connect];
|
||||
return [_connection connectWithError:error];
|
||||
}
|
||||
|
||||
- (void)disconnect
|
||||
@@ -57,42 +55,54 @@ static NSUInteger const NWPushPort = 2195;
|
||||
[_connection disconnect]; _connection = nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Apple push
|
||||
|
||||
- (NWError)pushPayload:(NSString *)payload token:(NSString *)token identifier:(NSUInteger)identifier
|
||||
+ (instancetype)connectWithIdentity:(NWIdentityRef)identity environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self pushNotification:[[NWNotification alloc] initWithPayload:payload token:token identifier:identifier expiration:nil priority:0] type:kNWNotificationType2];
|
||||
NWPusher *pusher = [[NWPusher alloc] init];
|
||||
return identity && [pusher connectWithIdentity:identity environment:environment error:error] ? pusher : nil;
|
||||
}
|
||||
|
||||
- (NWError)pushNotification:(NWNotification *)notification type:(NWNotificationType)type
|
||||
+ (instancetype)connectWithPKCS12Data:(NSData *)data password:(NSString *)password environment:(NWEnvironment)environment error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NWPusher *pusher = [[NWPusher alloc] init];
|
||||
return data && [pusher connectWithPKCS12Data:data password:password environment:environment error:error] ? pusher : nil;
|
||||
}
|
||||
|
||||
#pragma mark - Pushing payload
|
||||
|
||||
- (BOOL)pushPayload:(NSString *)payload token:(NSString *)token identifier:(NSUInteger)identifier error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self pushNotification:[[NWNotification alloc] initWithPayload:payload token:token identifier:identifier expiration:nil priority:0] type:kNWNotificationType2 error:error];
|
||||
}
|
||||
|
||||
- (BOOL)pushNotification:(NWNotification *)notification type:(NWNotificationType)type error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSUInteger length = 0;
|
||||
NSData *data = [notification dataWithType:type];
|
||||
NWError result = [_connection write:data length:&length];
|
||||
if (result != kNWSuccess) {
|
||||
return result;
|
||||
BOOL written = [_connection write:data length:&length error:error];
|
||||
if (!written) {
|
||||
return written;
|
||||
}
|
||||
if (length != data.length) {
|
||||
return kNWErrorPushWriteFail;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorPushWriteFail reason:length error:error];
|
||||
}
|
||||
return kNWSuccess;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NWError)fetchFailedIdentifier:(NSUInteger *)identifier apnError:(NWError *)apnError
|
||||
#pragma mark - Reading failed
|
||||
|
||||
- (BOOL)readFailedIdentifier:(NSUInteger *)identifier apnError:(NSError *__autoreleasing *)apnError error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
*apnError = kNWSuccess;
|
||||
*identifier = 0;
|
||||
NSMutableData *data = [NSMutableData dataWithLength:sizeof(uint8_t) * 2 + sizeof(uint32_t)];
|
||||
NSUInteger length = 0;
|
||||
NWError read = [_connection read:data length:&length];
|
||||
if (!length || read != kNWSuccess) {
|
||||
BOOL read = [_connection read:data length:&length error:error];
|
||||
if (!length || !read) {
|
||||
return read;
|
||||
}
|
||||
uint8_t command = 0;
|
||||
[data getBytes:&command range:NSMakeRange(0, 1)];
|
||||
if (command != 8) {
|
||||
return kNWErrorPushResponseCommand;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorPushResponseCommand reason:command error:error];
|
||||
}
|
||||
uint8_t status = 0;
|
||||
[data getBytes:&status range:NSMakeRange(1, 1)];
|
||||
@@ -100,17 +110,58 @@ static NSUInteger const NWPushPort = 2195;
|
||||
[data getBytes:&ID range:NSMakeRange(2, 4)];
|
||||
*identifier = htonl(ID);
|
||||
switch (status) {
|
||||
case 1: *apnError = kNWErrorAPNProcessing; break;
|
||||
case 2: *apnError = kNWErrorAPNMissingDeviceToken; break;
|
||||
case 3: *apnError = kNWErrorAPNMissingTopic; break;
|
||||
case 4: *apnError = kNWErrorAPNMissingPayload; break;
|
||||
case 5: *apnError = kNWErrorAPNInvalidTokenSize; break;
|
||||
case 6: *apnError = kNWErrorAPNInvalidTopicSize; break;
|
||||
case 7: *apnError = kNWErrorAPNInvalidPayloadSize; break;
|
||||
case 8: *apnError = kNWErrorAPNInvalidTokenContent; break;
|
||||
case 10: *apnError = kNWErrorAPNShutdown; break;
|
||||
case 1: [NWErrorUtil noWithErrorCode:kNWErrorAPNProcessing error:apnError]; break;
|
||||
case 2: [NWErrorUtil noWithErrorCode:kNWErrorAPNMissingDeviceToken error:apnError]; break;
|
||||
case 3: [NWErrorUtil noWithErrorCode:kNWErrorAPNMissingTopic error:apnError]; break;
|
||||
case 4: [NWErrorUtil noWithErrorCode:kNWErrorAPNMissingPayload error:apnError]; break;
|
||||
case 5: [NWErrorUtil noWithErrorCode:kNWErrorAPNInvalidTokenSize error:apnError]; break;
|
||||
case 6: [NWErrorUtil noWithErrorCode:kNWErrorAPNInvalidTopicSize error:apnError]; break;
|
||||
case 7: [NWErrorUtil noWithErrorCode:kNWErrorAPNInvalidPayloadSize error:apnError]; break;
|
||||
case 8: [NWErrorUtil noWithErrorCode:kNWErrorAPNInvalidTokenContent error:apnError]; break;
|
||||
case 10: [NWErrorUtil noWithErrorCode:kNWErrorAPNShutdown error:apnError]; break;
|
||||
default: [NWErrorUtil noWithErrorCode:kNWErrorAPNUnknownErrorCode reason:status error:apnError]; break;
|
||||
}
|
||||
return kNWSuccess;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSArray *)readFailedIdentifierErrorPairsWithMax:(NSUInteger)max error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSMutableArray *pairs = @[].mutableCopy;
|
||||
for (NSUInteger i = 0; i < max; i++) {
|
||||
NSUInteger identifier = 0;
|
||||
NSError *apnError = nil;
|
||||
BOOL read = [self readFailedIdentifier:&identifier apnError:&apnError error:error];
|
||||
if (!read) {
|
||||
return nil;
|
||||
}
|
||||
if (!apnError) {
|
||||
break;
|
||||
}
|
||||
[pairs addObject:@[@(identifier), apnError]];
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
#pragma mark - Deprecated
|
||||
|
||||
- (BOOL)connectWithIdentity:(NWIdentityRef)identity error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithIdentity:identity environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
- (BOOL)connectWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithPKCS12Data:data password:password environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
+ (instancetype)connectWithIdentity:(NWIdentityRef)identity error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithIdentity:identity environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
+ (instancetype)connectWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self connectWithPKCS12Data:data password:password environment:NWEnvironmentAuto error:error];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,18 +9,49 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
/** An SSL (TLS) connection to the APNs.
|
||||
|
||||
This class is basically an Objective-C wrapper around `SSLContextRef` and `SSLConnectionRef`, which are part of the native Secure Transport framework. This class provides a generic interface for SSL (TLS) connections, independent of NWPusher.
|
||||
|
||||
A SSL connection is set up using the host name, host port and an identity. The host name will be resolved using DNS. The identity is an instance of `SecIdentityRef` and contains both a certificate and a private key. See the *Secure Transport Reference* for more info on that.
|
||||
|
||||
Read more about provider communication in Apple's documentation under *Apple Push Notification Service*.
|
||||
|
||||
Methods return `NO` if an error occurred.
|
||||
*/
|
||||
@interface NWSSLConnection : NSObject
|
||||
|
||||
/** @name Properties */
|
||||
|
||||
/** The host name, which will be resolved using DNS. */
|
||||
@property (nonatomic, strong) NSString *host;
|
||||
|
||||
/** The host TCP port number. */
|
||||
@property (nonatomic, assign) NSUInteger port;
|
||||
|
||||
/** Identity containing a certificate-key pair for setting up the TLS connection. */
|
||||
@property (nonatomic, strong) NWIdentityRef identity;
|
||||
|
||||
/** @name Initialization */
|
||||
|
||||
/** Initialize a connection parameters host name, port, and identity. */
|
||||
- (instancetype)initWithHost:(NSString *)host port:(NSUInteger)port identity:(NWIdentityRef)identity;
|
||||
|
||||
- (NWError)connect;
|
||||
/** @name Connecting */
|
||||
|
||||
/** Connect socket, TLS and perform handshake.
|
||||
Can also be used when already connected, which will then first disconnect. */
|
||||
- (BOOL)connectWithError:(NSError **)error;
|
||||
|
||||
/** Drop connection if connected. */
|
||||
- (void)disconnect;
|
||||
|
||||
- (NWError)read:(NSMutableData *)data length:(NSUInteger *)length;
|
||||
- (NWError)write:(NSData *)data length:(NSUInteger *)length;
|
||||
/** @name I/O */
|
||||
|
||||
/** Read length number of bytes into mutable data object. */
|
||||
- (BOOL)read:(NSMutableData *)data length:(NSUInteger *)length error:(NSError **)error;
|
||||
|
||||
/** Write length number of bytes from data object. */
|
||||
- (BOOL)write:(NSData *)data length:(NSUInteger *)length error:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
//
|
||||
|
||||
#import "NWSSLConnection.h"
|
||||
#import "NWSecTools.h"
|
||||
#include <netdb.h>
|
||||
|
||||
#define NWSSL_HANDSHAKE_TRY_COUNT 1 << 26
|
||||
|
||||
OSStatus NWSSLRead(SSLConnectionRef connection, void *data, size_t *length);
|
||||
OSStatus NWSSLWrite(SSLConnectionRef connection, const void *data, size_t *length);
|
||||
@@ -41,38 +41,40 @@ OSStatus NWSSLWrite(SSLConnectionRef connection, const void *data, size_t *lengt
|
||||
[self disconnect];
|
||||
}
|
||||
|
||||
- (NWError)connect
|
||||
#pragma mark - Connecting
|
||||
|
||||
- (BOOL)connectWithError:(NSError *__autoreleasing *)error
|
||||
{
|
||||
[self disconnect];
|
||||
NWError socket = [self connectSocket];
|
||||
if (socket != kNWSuccess) {
|
||||
BOOL socket = [self connectSocketWithError:error];
|
||||
if (!socket) {
|
||||
[self disconnect];
|
||||
return socket;
|
||||
}
|
||||
NWError ssl = [self connectSSL];
|
||||
if (ssl != kNWSuccess) {
|
||||
BOOL ssl = [self connectSSLWithError:error];
|
||||
if (!ssl) {
|
||||
[self disconnect];
|
||||
return ssl;
|
||||
}
|
||||
NWError handshake = [self handshakeSSL];
|
||||
if (handshake != kNWSuccess) {
|
||||
BOOL handshake = [self handshakeSSLWithError:error];
|
||||
if (!handshake) {
|
||||
[self disconnect];
|
||||
return handshake;
|
||||
}
|
||||
return kNWSuccess;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NWError)connectSocket
|
||||
- (BOOL)connectSocketWithError:(NSError *__autoreleasing *)error
|
||||
{
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock < 0) {
|
||||
return kNWErrorSocketCreate;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSocketCreate reason:sock error:error];
|
||||
}
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_in));
|
||||
struct hostent *entr = gethostbyname(_host.UTF8String);
|
||||
if (!entr) {
|
||||
return kNWErrorSocketResolveHostName;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSocketResolveHostName error:error];
|
||||
}
|
||||
struct in_addr host;
|
||||
memcpy(&host, entr->h_addr, sizeof(struct in_addr));
|
||||
@@ -81,91 +83,73 @@ OSStatus NWSSLWrite(SSLConnectionRef connection, const void *data, size_t *lengt
|
||||
addr.sin_family = AF_INET;
|
||||
int conn = connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
|
||||
if (conn < 0) {
|
||||
return kNWErrorSocketConnect;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSocketConnect reason:conn error:error];
|
||||
}
|
||||
int cntl = fcntl(sock, F_SETFL, O_NONBLOCK);
|
||||
if (cntl < 0) {
|
||||
return kNWErrorSocketFileControl;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSocketFileControl reason:cntl error:error];
|
||||
}
|
||||
int set = 1, sopt = setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
|
||||
if (sopt < 0) {
|
||||
return kNWErrorSocketOptions;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSocketOptions reason:sopt error:error];
|
||||
}
|
||||
_socket = sock;
|
||||
return kNWSuccess;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NWError)connectSSL
|
||||
- (BOOL)connectSSLWithError:(NSError *__autoreleasing *)error
|
||||
{
|
||||
SSLContextRef context = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
|
||||
if (!context) {
|
||||
return kNWErrorSSLContext;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSSLContext error:error];
|
||||
}
|
||||
OSStatus setio = SSLSetIOFuncs(context, NWSSLRead, NWSSLWrite);
|
||||
if (setio != errSecSuccess) {
|
||||
return kNWErrorSSLIOFuncs;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSSLIOFuncs reason:setio error:error];
|
||||
}
|
||||
OSStatus setconn = SSLSetConnection(context, (SSLConnectionRef)(NSInteger)_socket);
|
||||
if (setconn != errSecSuccess) {
|
||||
return kNWErrorSSLConnection;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSSLConnection reason:setconn error:error];
|
||||
}
|
||||
OSStatus setpeer = SSLSetPeerDomainName(context, _host.UTF8String, strlen(_host.UTF8String));
|
||||
if (setpeer != errSecSuccess) {
|
||||
return kNWErrorSSLPeerDomainName;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSSLPeerDomainName reason:setpeer error:error];
|
||||
}
|
||||
OSStatus setcert = SSLSetCertificate(context, (__bridge CFArrayRef)@[_identity]);
|
||||
if (setcert != errSecSuccess) {
|
||||
return kNWErrorSSLCertificate;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSSLCertificate reason:setcert error:error];
|
||||
}
|
||||
_context = context;
|
||||
return kNWSuccess;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NWError)handshakeSSL
|
||||
- (BOOL)handshakeSSLWithError:(NSError *__autoreleasing *)error
|
||||
{
|
||||
OSStatus status = errSSLWouldBlock;
|
||||
for (NSUInteger i = 0; i < 1 << 26 && status == errSSLWouldBlock; i++) {
|
||||
for (NSUInteger i = 0; i < NWSSL_HANDSHAKE_TRY_COUNT && status == errSSLWouldBlock; i++) {
|
||||
status = SSLHandshake(_context);
|
||||
}
|
||||
switch (status) {
|
||||
case errSecSuccess: return kNWSuccess;
|
||||
case errSSLWouldBlock: return kNWErrorSSLHandshakeTimeout;
|
||||
case errSecIO: return kNWErrorSSLDroppedByServer;
|
||||
case errSecAuthFailed: return kNWErrorSSLAuthFailed;
|
||||
case errSecSuccess: return YES;
|
||||
case errSSLWouldBlock: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakeTimeout error:error];
|
||||
case errSecIO: return [NWErrorUtil noWithErrorCode:kNWErrorSSLDroppedByServer error:error];
|
||||
case errSecAuthFailed: return [NWErrorUtil noWithErrorCode:kNWErrorSSLAuthFailed error:error];
|
||||
case errSSLUnknownRootCert: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakeUnknownRootCert error:error];
|
||||
case errSSLNoRootCert: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakeNoRootCert error:error];
|
||||
case errSSLCertExpired: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakeCertExpired error:error];
|
||||
case errSSLXCertChainInvalid: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakeXCertChainInvalid error:error];
|
||||
case errSSLClientCertRequested: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakeClientCertRequested error:error];
|
||||
case errSSLServerAuthCompleted: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakeServerAuthCompleted error:error];
|
||||
case errSSLPeerCertExpired: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakePeerCertExpired error:error];
|
||||
case errSSLPeerCertRevoked: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakePeerCertRevoked error:error];
|
||||
case errSSLPeerCertUnknown: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakePeerCertUnknown error:error];
|
||||
case errSSLInternal: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakeInternalError error:error];
|
||||
#if !TARGET_OS_IPHONE
|
||||
case errSecInDarkWake: return [NWErrorUtil noWithErrorCode:kNWErrorSSLInDarkWake error:error];
|
||||
#endif
|
||||
case errSSLClosedAbort: return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakeClosedAbort error:error];
|
||||
}
|
||||
return kNWErrorSSLHandshakeFail;
|
||||
}
|
||||
|
||||
- (NWError)read:(NSMutableData *)data length:(NSUInteger *)length
|
||||
{
|
||||
*length = 0;
|
||||
size_t processed = 0;
|
||||
OSStatus status = SSLRead(_context, data.mutableBytes, data.length, &processed);
|
||||
*length = processed;
|
||||
switch (status) {
|
||||
case errSecSuccess: return kNWSuccess;
|
||||
case errSSLWouldBlock: return kNWSuccess;
|
||||
case errSecIO: return kNWErrorReadDroppedByServer;
|
||||
case errSSLClosedAbort: return kNWErrorReadClosedAbort;
|
||||
case errSSLClosedGraceful: return kNWErrorReadClosedGraceful;
|
||||
}
|
||||
return kNWErrorReadFail;
|
||||
}
|
||||
|
||||
- (NWError)write:(NSData *)data length:(NSUInteger *)length
|
||||
{
|
||||
*length = 0;
|
||||
size_t processed = 0;
|
||||
OSStatus status = SSLWrite(_context, data.bytes, data.length, &processed);
|
||||
*length = processed;
|
||||
switch (status) {
|
||||
case errSecSuccess: return kNWSuccess;
|
||||
case errSSLWouldBlock: return kNWSuccess;
|
||||
case errSecIO: return kNWErrorWriteDroppedByServer;
|
||||
case errSSLClosedAbort: return kNWErrorWriteClosedAbort;
|
||||
case errSSLClosedGraceful: return kNWErrorWriteClosedGraceful;
|
||||
}
|
||||
return kNWErrorWriteFail;
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorSSLHandshakeFail reason:status error:error];
|
||||
}
|
||||
|
||||
- (void)disconnect
|
||||
@@ -175,6 +159,40 @@ OSStatus NWSSLWrite(SSLConnectionRef connection, const void *data, size_t *lengt
|
||||
if (_context) CFRelease(_context); _context = NULL;
|
||||
}
|
||||
|
||||
#pragma mark - Read Write
|
||||
|
||||
- (BOOL)read:(NSMutableData *)data length:(NSUInteger *)length error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
*length = 0;
|
||||
size_t processed = 0;
|
||||
OSStatus status = SSLRead(_context, data.mutableBytes, data.length, &processed);
|
||||
*length = processed;
|
||||
switch (status) {
|
||||
case errSecSuccess: return YES;
|
||||
case errSSLWouldBlock: return YES;
|
||||
case errSecIO: return [NWErrorUtil noWithErrorCode:kNWErrorReadDroppedByServer error:error];
|
||||
case errSSLClosedAbort: return [NWErrorUtil noWithErrorCode:kNWErrorReadClosedAbort error:error];
|
||||
case errSSLClosedGraceful: return [NWErrorUtil noWithErrorCode:kNWErrorReadClosedGraceful error:error];
|
||||
}
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorReadFail reason:status error:error];
|
||||
}
|
||||
|
||||
- (BOOL)write:(NSData *)data length:(NSUInteger *)length error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
*length = 0;
|
||||
size_t processed = 0;
|
||||
OSStatus status = SSLWrite(_context, data.bytes, data.length, &processed);
|
||||
*length = processed;
|
||||
switch (status) {
|
||||
case errSecSuccess: return YES;
|
||||
case errSSLWouldBlock: return YES;
|
||||
case errSecIO: return [NWErrorUtil noWithErrorCode:kNWErrorWriteDroppedByServer error:error];
|
||||
case errSSLClosedAbort: return [NWErrorUtil noWithErrorCode:kNWErrorWriteClosedAbort error:error];
|
||||
case errSSLClosedGraceful: return [NWErrorUtil noWithErrorCode:kNWErrorWriteClosedGraceful error:error];
|
||||
}
|
||||
return [NWErrorUtil noWithErrorCode:kNWErrorWriteFail reason:status error:error];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
OSStatus NWSSLRead(SSLConnectionRef connection, void *data, size_t *length) {
|
||||
|
||||
@@ -8,25 +8,72 @@
|
||||
#import "NWType.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/** A collection of tools for reading, converting and inspecting Keychain objects and PKCS #12 files.
|
||||
|
||||
This is practically the glue that connects this framework to the Security framework and allows interacting with the OS Keychain and PKCS #12 files. It is mostly an Objective-C around the Security framework, including the benefits of ARC. `NWIdentityRef`, `NWCertificateRef` and `NWKeyRef` represent respectively `SecIdentityRef`, `SecCertificateRef`, `SecKeyRef`. It uses Cocoa-style error handling, so methods return `nil` or `NO` if an error occurred.
|
||||
*/
|
||||
@interface NWSecTools : NSObject
|
||||
|
||||
+ (NWError)identityWithPKCS12Data:(NSData *)pkcs12 password:(NSString *)password identity:(NWIdentityRef *)identity;
|
||||
+ (NWError)identitiesWithPKCS12Data:(NSData *)pkcs12 password:(NSString *)password identities:(NSArray **)identities;
|
||||
+ (NWError)keychainCertificates:(NSArray **)certificates;
|
||||
+ (NWError)certificateWithIdentity:(NWIdentityRef)identity certificate:(NWCertificateRef *)certificate;
|
||||
+ (NWError)keyWithIdentity:(NWIdentityRef)identity key:(NWKeyRef *)key;
|
||||
/** @name Initialization */
|
||||
|
||||
/** Read an identity from a PKCS #12 file (.p12) that contains a single certificate-key pair. */
|
||||
+ (NWIdentityRef)identityWithPKCS12Data:(NSData *)pkcs12 password:(NSString *)password error:(NSError **)error;
|
||||
|
||||
/** Read all identities from a PKCS #12 file (.p12). */
|
||||
+ (NSArray *)identitiesWithPKCS12Data:(NSData *)pkcs12 password:(NSString *)password error:(NSError **)error;
|
||||
|
||||
/** List all push certificates present in the OS Keychain. */
|
||||
+ (NSArray *)keychainCertificatesWithError:(NSError **)error;
|
||||
|
||||
/** @name Sec Wrappers */
|
||||
|
||||
/** Returns the certificate contained by the identity. */
|
||||
+ (NWCertificateRef)certificateWithIdentity:(NWIdentityRef)identity error:(NSError **)error;
|
||||
|
||||
/** Returns the key contained by the identity. */
|
||||
+ (NWKeyRef)keyWithIdentity:(NWIdentityRef)identity error:(NSError **)error;
|
||||
|
||||
/** Reads an X.509 certificate from a DER file. */
|
||||
+ (NWCertificateRef)certificateWithData:(NSData *)data;
|
||||
|
||||
#if !TARGET_OS_IPHONE
|
||||
/** Searches the OS Keychain for an identity (the key) that matches the certificate. (OS X only) */
|
||||
+ (NWIdentityRef)keychainIdentityWithCertificate:(NWCertificateRef)certificate error:(NSError **)error;
|
||||
#endif
|
||||
|
||||
/** @name Inspection */
|
||||
|
||||
/** Extracts the type and summary string. */
|
||||
+ (NWCertType)typeWithCertificate:(NWCertificateRef)certificate summary:(NSString **)summary;
|
||||
|
||||
/** Extracts the summary string. */
|
||||
+ (NSString *)summaryWithCertificate:(NWCertificateRef)certificate;
|
||||
+ (BOOL)isSandboxIdentity:(NWIdentityRef)identity;
|
||||
+ (BOOL)isSandboxCertificate:(NWCertificateRef)certificate;
|
||||
|
||||
/** Tells what environment options can be used with this identity (Development(sandbox)/Production server or both). */
|
||||
+ (NWEnvironmentOptions)environmentOptionsForIdentity:(NWIdentityRef)identity;
|
||||
|
||||
/** Tells what environment options can be used with this certificate (Development(sandbox)/Production server or both). */
|
||||
+ (NWEnvironmentOptions)environmentOptionsForCertificate:(NWCertificateRef)certificate;
|
||||
|
||||
/** Tells if the certificate can be used for connecting with APNs. */
|
||||
+ (BOOL)isPushCertificate:(NWCertificateRef)certificate;
|
||||
|
||||
/** Composes a dictionary describing the characteristics of the identity. */
|
||||
+ (NSDictionary *)inspectIdentity:(NWIdentityRef)identity;
|
||||
|
||||
#if !TARGET_OS_IPHONE
|
||||
+ (NWError)keychainIdentityWithCertificate:(NWCertificateRef)certificate identity:(NWIdentityRef *)identity;
|
||||
/** Extracts the expiration date. */
|
||||
+ (NSDate *)expirationWithCertificate:(NWCertificateRef)certificate;
|
||||
|
||||
/** Extracts given properties of certificate, see `SecCertificateOIDs.h`, use `nil` to get all. */
|
||||
+ (NSDictionary *)valuesWithCertificate:(NWCertificateRef)certificate keys:(NSArray *)keys error:(NSError **)error;
|
||||
#endif
|
||||
|
||||
// deprecated
|
||||
|
||||
+ (BOOL)isSandboxIdentity:(NWIdentityRef)identity __deprecated;
|
||||
+ (BOOL)isSandboxCertificate:(NWCertificateRef)certificate __deprecated;
|
||||
+ (NWEnvironment)environmentForIdentity:(NWIdentityRef)identity;
|
||||
+ (NWEnvironment)environmentForCertificate:(NWCertificateRef)certificate;
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,77 +7,59 @@
|
||||
|
||||
#import "NWSecTools.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, NWCertType) {
|
||||
kNWCertTypeNone = 0,
|
||||
kNWCertTypeIOSDevelopment = 1,
|
||||
kNWCertTypeIOSProduction = 2,
|
||||
kNWCertTypeMacDevelopment = 3,
|
||||
kNWCertTypeMacProduction = 4,
|
||||
kNWCertTypeUnknown = 5,
|
||||
};
|
||||
|
||||
|
||||
@implementation NWSecTools
|
||||
|
||||
+ (NWError)identityWithPKCS12Data:(NSData *)pkcs12 password:(NSString *)password identity:(NWIdentityRef *)identity
|
||||
#pragma mark - Initialization
|
||||
|
||||
+ (NWIdentityRef)identityWithPKCS12Data:(NSData *)pkcs12 password:(NSString *)password error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
*identity = nil;
|
||||
NSArray *identities = nil;
|
||||
NWError result = [self identitiesWithPKCS12Data:pkcs12 password:password identities:&identities];
|
||||
if (result != kNWSuccess) {
|
||||
return result;
|
||||
NSArray *identities = [self identitiesWithPKCS12Data:pkcs12 password:password error:error];
|
||||
if (!identities) {
|
||||
return nil;
|
||||
}
|
||||
if (identities.count == 0) {
|
||||
return kNWErrorPKCS12NoItems;
|
||||
return [NWErrorUtil nilWithErrorCode:kNWErrorPKCS12NoItems error:error];
|
||||
}
|
||||
if (identities.count > 1) {
|
||||
return kNWErrorPKCS12MutlipleItems;
|
||||
return [NWErrorUtil nilWithErrorCode:kNWErrorPKCS12MultipleItems reason:identities.count error:error];
|
||||
}
|
||||
*identity = identities.lastObject;
|
||||
return kNWSuccess;
|
||||
return identities.lastObject;
|
||||
}
|
||||
|
||||
+ (NWError)identitiesWithPKCS12Data:(NSData *)pkcs12 password:(NSString *)password identities:(NSArray **)identities
|
||||
+ (NSArray *)identitiesWithPKCS12Data:(NSData *)pkcs12 password:(NSString *)password error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
*identities = nil;
|
||||
NSArray *dicts = nil;
|
||||
if (!pkcs12.length) {
|
||||
return kNWErrorPKCS12EmptyData;
|
||||
return [NWErrorUtil nilWithErrorCode:kNWErrorPKCS12EmptyData error:error];
|
||||
}
|
||||
NWError result = [self allIdentitiesWithPKCS12Data:pkcs12 password:password dicts:&dicts];
|
||||
if (result != kNWSuccess) {
|
||||
return result;
|
||||
NSArray *dicts = [self allIdentitiesWithPKCS12Data:pkcs12 password:password error:error];
|
||||
if (!dicts) {
|
||||
return nil;
|
||||
}
|
||||
NSMutableArray *ids = @[].mutableCopy;
|
||||
for (NSDictionary *dict in dicts) {
|
||||
NWIdentityRef identity = dict[(__bridge id)kSecImportItemIdentity];
|
||||
if (identity) {
|
||||
NWCertificateRef certificate = nil;
|
||||
NWError certres = [self certificateWithIdentity:identity certificate:&certificate];
|
||||
if (certres != kNWSuccess) {
|
||||
return certres;
|
||||
NWCertificateRef certificate = [self certificateWithIdentity:identity error:error];
|
||||
if (!certificate) {
|
||||
return nil;
|
||||
}
|
||||
if ([self isPushCertificate:certificate]) {
|
||||
NWKeyRef key = nil;
|
||||
NWError keyres = [self keyWithIdentity:identity key:&key];
|
||||
if (keyres != kNWSuccess) {
|
||||
return keyres;
|
||||
NWKeyRef key = [self keyWithIdentity:identity error:error];
|
||||
if (!key) {
|
||||
return nil;
|
||||
}
|
||||
[ids addObject:identity];
|
||||
}
|
||||
}
|
||||
}
|
||||
*identities = ids;
|
||||
return kNWSuccess;
|
||||
return ids;
|
||||
}
|
||||
|
||||
+ (NWError)keychainCertificates:(NSArray **)certificates
|
||||
+ (NSArray *)keychainCertificatesWithError:(NSError *__autoreleasing *)error
|
||||
{
|
||||
*certificates = nil;
|
||||
NSArray *candidates = nil;
|
||||
NWError result = [self allKeychainCertificates:&candidates];
|
||||
if (result != kNWSuccess) {
|
||||
return result;
|
||||
NSArray *candidates = [self allKeychainCertificatesWithError:error];
|
||||
if (!candidates) {
|
||||
return nil;
|
||||
}
|
||||
NSMutableArray *certs = [[NSMutableArray alloc] init];
|
||||
for (id certificate in candidates) {
|
||||
@@ -85,11 +67,10 @@ typedef NS_ENUM(NSInteger, NWCertType) {
|
||||
[certs addObject:certificate];
|
||||
}
|
||||
}
|
||||
*certificates = certs;
|
||||
return kNWSuccess;
|
||||
return certs;
|
||||
}
|
||||
|
||||
#pragma mark - Certificate types
|
||||
#pragma mark - Inspection
|
||||
|
||||
+ (NWCertType)typeWithCertificate:(NWCertificateRef)certificate summary:(NSString **)summary
|
||||
{
|
||||
@@ -113,26 +94,33 @@ typedef NS_ENUM(NSInteger, NWCertType) {
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (BOOL)isSandboxIdentity:(NWIdentityRef)identity
|
||||
+ (NWEnvironmentOptions)environmentOptionsForIdentity:(NWIdentityRef)identity
|
||||
{
|
||||
NWCertificateRef certificate = nil;
|
||||
[self certificateWithIdentity:identity certificate:&certificate];
|
||||
return [self isSandboxCertificate:certificate];
|
||||
NWCertificateRef certificate = [self certificateWithIdentity:identity error:nil];
|
||||
return [self environmentOptionsForCertificate:certificate];
|
||||
}
|
||||
|
||||
+ (BOOL)isSandboxCertificate:(NWCertificateRef)certificate
|
||||
+ (NWEnvironmentOptions)environmentOptionsForCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
switch ([self typeWithCertificate:certificate summary:nil]) {
|
||||
case kNWCertTypeIOSDevelopment:
|
||||
case kNWCertTypeMacDevelopment:
|
||||
return YES;
|
||||
return NWEnvironmentOptionSandbox;
|
||||
|
||||
case kNWCertTypeIOSProduction:
|
||||
case kNWCertTypeMacProduction:
|
||||
return NWEnvironmentOptionProduction;
|
||||
case kNWCertTypeSimplified:
|
||||
case kNWCertTypeWebProduction:
|
||||
case kNWCertTypeVoIPServices:
|
||||
case kNWCertTypeWatchKitServices:
|
||||
case kNWCertTypePasses:
|
||||
return NWEnvironmentOptionAny;
|
||||
case kNWCertTypeNone:
|
||||
case kNWCertTypeUnknown:
|
||||
break;
|
||||
}
|
||||
return NO;
|
||||
return NWEnvironmentOptionNone;
|
||||
}
|
||||
|
||||
+ (BOOL)isPushCertificate:(NWCertificateRef)certificate
|
||||
@@ -142,6 +130,11 @@ typedef NS_ENUM(NSInteger, NWCertType) {
|
||||
case kNWCertTypeMacDevelopment:
|
||||
case kNWCertTypeIOSProduction:
|
||||
case kNWCertTypeMacProduction:
|
||||
case kNWCertTypeSimplified:
|
||||
case kNWCertTypeWebProduction:
|
||||
case kNWCertTypeVoIPServices:
|
||||
case kNWCertTypeWatchKitServices:
|
||||
case kNWCertTypePasses:
|
||||
return YES;
|
||||
case kNWCertTypeNone:
|
||||
case kNWCertTypeUnknown:
|
||||
@@ -157,6 +150,11 @@ typedef NS_ENUM(NSInteger, NWCertType) {
|
||||
case kNWCertTypeIOSProduction: return @"Apple Production IOS Push Services: ";
|
||||
case kNWCertTypeMacDevelopment: return @"Apple Development Mac Push Services: ";
|
||||
case kNWCertTypeMacProduction: return @"Apple Production Mac Push Services: ";
|
||||
case kNWCertTypeSimplified: return @"Apple Push Services: ";
|
||||
case kNWCertTypeWebProduction: return @"Website Push ID: ";
|
||||
case kNWCertTypeVoIPServices: return @"VoIP Services: ";
|
||||
case kNWCertTypeWatchKitServices: return @"WatchKit Services: ";
|
||||
case kNWCertTypePasses: return @"Pass Type ID: ";
|
||||
case kNWCertTypeNone:
|
||||
case kNWCertTypeUnknown:
|
||||
break;
|
||||
@@ -164,100 +162,6 @@ typedef NS_ENUM(NSInteger, NWCertType) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Sec wrappers
|
||||
|
||||
+ (NWCertificateRef)certificateWithData:(NSData *)data
|
||||
{
|
||||
return data ? CFBridgingRelease(SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)data)) : nil;
|
||||
}
|
||||
|
||||
+ (NSString *)plainSummaryWithCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
return certificate ? CFBridgingRelease(SecCertificateCopySubjectSummary((__bridge SecCertificateRef)certificate)) : nil;
|
||||
}
|
||||
|
||||
+ (NSData *)derDataWithCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
return certificate ? CFBridgingRelease(SecCertificateCopyData((__bridge SecCertificateRef)certificate)) : nil;
|
||||
}
|
||||
|
||||
+ (NWError)certificateWithIdentity:(NWIdentityRef)identity certificate:(NWCertificateRef *)certificate
|
||||
{
|
||||
*certificate = nil;
|
||||
SecCertificateRef cert = NULL;
|
||||
OSStatus status = identity ? SecIdentityCopyCertificate((__bridge SecIdentityRef)identity, &cert) : errSecParam;
|
||||
*certificate = CFBridgingRelease(cert);
|
||||
if (status != errSecSuccess || !cert) {
|
||||
return kNWErrorIdentityCopyCertificate;
|
||||
}
|
||||
return kNWSuccess;
|
||||
}
|
||||
|
||||
+ (NWError)keyWithIdentity:(NWIdentityRef)identity key:(NWKeyRef *)key
|
||||
{
|
||||
*key = nil;
|
||||
SecKeyRef k = NULL;
|
||||
OSStatus status = identity ? SecIdentityCopyPrivateKey((__bridge SecIdentityRef)identity, &k) : errSecParam;
|
||||
*key = CFBridgingRelease(k);
|
||||
if (status != errSecSuccess || !k) {
|
||||
return kNWErrorIdentityCopyPrivateKey;
|
||||
}
|
||||
return kNWSuccess;
|
||||
}
|
||||
|
||||
+ (NWError)allIdentitiesWithPKCS12Data:(NSData *)data password:(NSString *)password dicts:(NSArray **)dicts
|
||||
{
|
||||
*dicts = nil;
|
||||
NSDictionary *options = @{(__bridge id)kSecImportExportPassphrase: password};
|
||||
CFArrayRef items = NULL;
|
||||
OSStatus status = data ? SecPKCS12Import((__bridge CFDataRef)data, (__bridge CFDictionaryRef)options, &items) : errSecParam;
|
||||
*dicts = CFBridgingRelease(items);
|
||||
if (status != errSecSuccess || !items) {
|
||||
switch (status) {
|
||||
case errSecDecode: return kNWErrorPKCS12Decode;
|
||||
case errSecAuthFailed: return kNWErrorPKCS12AuthFailed;
|
||||
#if !TARGET_OS_IPHONE
|
||||
case errSecPkcs12VerifyFailure: return kNWErrorPKCS12Password;
|
||||
#endif
|
||||
}
|
||||
return kNWErrorPKCS12Import;
|
||||
}
|
||||
return kNWSuccess;
|
||||
}
|
||||
|
||||
+ (NWError)allKeychainCertificates:(NSArray **)certificates
|
||||
{
|
||||
*certificates = nil;
|
||||
NSDictionary *options = @{(__bridge id)kSecClass: (__bridge id)kSecClassCertificate,
|
||||
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll};
|
||||
CFArrayRef certs = NULL;
|
||||
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)options, (CFTypeRef *)&certs);
|
||||
*certificates = CFBridgingRelease(certs);
|
||||
if (status != errSecSuccess || !certs) {
|
||||
return kNWErrorKeychainCopyMatching;
|
||||
}
|
||||
return kNWSuccess;
|
||||
}
|
||||
|
||||
#if !TARGET_OS_IPHONE
|
||||
+ (NWError)keychainIdentityWithCertificate:(NWCertificateRef)certificate identity:(NWIdentityRef *)identity
|
||||
{
|
||||
*identity = nil;
|
||||
SecIdentityRef ident = NULL;
|
||||
OSStatus status = certificate ? SecIdentityCreateWithCertificate(NULL, (__bridge SecCertificateRef)certificate, &ident) : errSecParam;
|
||||
*identity = CFBridgingRelease(ident);
|
||||
if (status != errSecSuccess || !ident) {
|
||||
switch (status) {
|
||||
case errSecItemNotFound: return kNWErrorKeychainItemNotFound;
|
||||
}
|
||||
return kNWErrorKeychainCreateIdentity;
|
||||
}
|
||||
return kNWSuccess;
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma mark - Debug
|
||||
|
||||
+ (NSDictionary *)inspectIdentity:(NWIdentityRef)identity
|
||||
{
|
||||
if (!identity) return nil;
|
||||
@@ -282,4 +186,150 @@ typedef NS_ENUM(NSInteger, NWCertType) {
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma mark - Sec wrappers
|
||||
|
||||
+ (NWCertificateRef)certificateWithData:(NSData *)data
|
||||
{
|
||||
return data ? CFBridgingRelease(SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)data)) : nil;
|
||||
}
|
||||
|
||||
+ (NSString *)plainSummaryWithCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
return certificate ? CFBridgingRelease(SecCertificateCopySubjectSummary((__bridge SecCertificateRef)certificate)) : nil;
|
||||
}
|
||||
|
||||
+ (NSData *)derDataWithCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
return certificate ? CFBridgingRelease(SecCertificateCopyData((__bridge SecCertificateRef)certificate)) : nil;
|
||||
}
|
||||
|
||||
+ (NWCertificateRef)certificateWithIdentity:(NWIdentityRef)identity error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
SecCertificateRef cert = NULL;
|
||||
OSStatus status = identity ? SecIdentityCopyCertificate((__bridge SecIdentityRef)identity, &cert) : errSecParam;
|
||||
NWCertificateRef certificate = CFBridgingRelease(cert);
|
||||
if (status != errSecSuccess || !cert) {
|
||||
return [NWErrorUtil nilWithErrorCode:kNWErrorIdentityCopyCertificate reason:status error:error];
|
||||
}
|
||||
return certificate;
|
||||
}
|
||||
|
||||
+ (NWKeyRef)keyWithIdentity:(NWIdentityRef)identity error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
SecKeyRef k = NULL;
|
||||
OSStatus status = identity ? SecIdentityCopyPrivateKey((__bridge SecIdentityRef)identity, &k) : errSecParam;
|
||||
NWKeyRef key = CFBridgingRelease(k);
|
||||
if (status != errSecSuccess || !k) {
|
||||
return [NWErrorUtil nilWithErrorCode:kNWErrorIdentityCopyPrivateKey reason:status error:error];
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
+ (NSArray *)allIdentitiesWithPKCS12Data:(NSData *)data password:(NSString *)password error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSDictionary *options = password ? @{(__bridge id)kSecImportExportPassphrase: password} : @{};
|
||||
CFArrayRef items = NULL;
|
||||
OSStatus status = data ? SecPKCS12Import((__bridge CFDataRef)data, (__bridge CFDictionaryRef)options, &items) : errSecParam;
|
||||
NSArray *dicts = CFBridgingRelease(items);
|
||||
if (status != errSecSuccess || !items) {
|
||||
switch (status) {
|
||||
case errSecDecode: return [NWErrorUtil nilWithErrorCode:kNWErrorPKCS12Decode error:error];
|
||||
case errSecAuthFailed: return [NWErrorUtil nilWithErrorCode:kNWErrorPKCS12AuthFailed error:error];
|
||||
#if !TARGET_OS_IPHONE
|
||||
case errSecPkcs12VerifyFailure: return [NWErrorUtil nilWithErrorCode:kNWErrorPKCS12Password error:error];
|
||||
case errSecPassphraseRequired: return [NWErrorUtil nilWithErrorCode:kNWErrorPKCS12PasswordRequired error:error];
|
||||
#endif
|
||||
}
|
||||
return [NWErrorUtil nilWithErrorCode:kNWErrorPKCS12Import reason:status error:error];
|
||||
}
|
||||
return dicts;
|
||||
}
|
||||
|
||||
+ (NSArray *)allKeychainCertificatesWithError:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSDictionary *options = @{(__bridge id)kSecClass: (__bridge id)kSecClassCertificate,
|
||||
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll};
|
||||
CFArrayRef certs = NULL;
|
||||
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)options, (CFTypeRef *)&certs);
|
||||
NSArray *certificates = CFBridgingRelease(certs);
|
||||
if (status != errSecSuccess || !certs) {
|
||||
return [NWErrorUtil nilWithErrorCode:kNWErrorKeychainCopyMatching reason:status error:error];
|
||||
}
|
||||
return certificates;
|
||||
}
|
||||
|
||||
#if !TARGET_OS_IPHONE
|
||||
+ (NWIdentityRef)keychainIdentityWithCertificate:(NWCertificateRef)certificate error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
SecIdentityRef ident = NULL;
|
||||
OSStatus status = certificate ? SecIdentityCreateWithCertificate(NULL, (__bridge SecCertificateRef)certificate, &ident) : errSecParam;
|
||||
NWIdentityRef identity = CFBridgingRelease(ident);
|
||||
if (status != errSecSuccess || !ident) {
|
||||
switch (status) {
|
||||
case errSecItemNotFound: return [NWErrorUtil nilWithErrorCode:kNWErrorKeychainItemNotFound error:error];
|
||||
}
|
||||
return [NWErrorUtil nilWithErrorCode:kNWErrorKeychainCreateIdentity reason:status error:error];
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
+ (NSDate *)expirationWithCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
return [self valueWithCertificate:certificate key:(__bridge id)kSecOIDInvalidityDate];
|
||||
}
|
||||
|
||||
+ (id)valueWithCertificate:(NWCertificateRef)certificate key:(id)key
|
||||
{
|
||||
return [self valuesWithCertificate:certificate keys:@[key] error:nil][key][(__bridge id)kSecPropertyKeyValue];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)valuesWithCertificate:(NWCertificateRef)certificate keys:(NSArray *)keys error:(NSError **)error
|
||||
{
|
||||
CFErrorRef e = NULL;
|
||||
NSDictionary *result = CFBridgingRelease(SecCertificateCopyValues((__bridge SecCertificateRef)certificate, (__bridge CFArrayRef)keys, &e));
|
||||
if (error) *error = CFBridgingRelease(e);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma mark - Deprecated
|
||||
|
||||
+ (BOOL)isSandboxIdentity:(NWIdentityRef)identity
|
||||
{
|
||||
return [self environmentForIdentity:identity] == NWEnvironmentSandbox;
|
||||
}
|
||||
|
||||
+ (BOOL)isSandboxCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
return [self environmentForCertificate:certificate] == NWEnvironmentSandbox;
|
||||
}
|
||||
|
||||
+ (NWEnvironment)environmentForIdentity:(NWIdentityRef)identity
|
||||
{
|
||||
NWCertificateRef certificate = [self certificateWithIdentity:identity error:nil];
|
||||
return [self environmentForCertificate:certificate];
|
||||
}
|
||||
|
||||
+ (NWEnvironment)environmentForCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
switch ([self typeWithCertificate:certificate summary:nil]) {
|
||||
case kNWCertTypeIOSDevelopment:
|
||||
case kNWCertTypeMacDevelopment:
|
||||
return NWEnvironmentSandbox;
|
||||
|
||||
case kNWCertTypeIOSProduction:
|
||||
case kNWCertTypeMacProduction:
|
||||
return NWEnvironmentProduction;
|
||||
case kNWCertTypeSimplified:
|
||||
case kNWCertTypeWebProduction:
|
||||
case kNWCertTypeVoIPServices:
|
||||
case kNWCertTypeWatchKitServices:
|
||||
case kNWCertTypePasses:
|
||||
case kNWCertTypeNone:
|
||||
case kNWCertTypeUnknown:
|
||||
break;
|
||||
}
|
||||
return NWEnvironmentNone;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,83 +7,230 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/** The current and past data formats supported by APNs. For more information see Apple documentation under 'Legacy Information'. */
|
||||
typedef NS_ENUM(NSInteger, NWNotificationType) {
|
||||
/** The 'Simple Notification Format'. The oldest format, simply concatenates the device token and payload. */
|
||||
kNWNotificationType0 = 0,
|
||||
/** The 'Enhanced Notification Format'. Similar to the previous format, but includes and identifier and expiration date. */
|
||||
kNWNotificationType1 = 1,
|
||||
/** The 'Binary Interface and Notification Format'. The latest, more extensible format that allows for attributes like priority. */
|
||||
kNWNotificationType2 = 2,
|
||||
};
|
||||
|
||||
typedef id NWIdentityRef; // SecIdentityRef
|
||||
typedef id NWCertificateRef; // SecCertificateRef
|
||||
typedef id NWKeyRef; // SecKeyRef
|
||||
/** Types of push certificates. */
|
||||
typedef NS_ENUM(NSInteger, NWCertType) {
|
||||
/** None. */
|
||||
kNWCertTypeNone = 0,
|
||||
/** iOS Development. */
|
||||
kNWCertTypeIOSDevelopment = 1,
|
||||
/** iOS Production. */
|
||||
kNWCertTypeIOSProduction = 2,
|
||||
/** OS X Development. */
|
||||
kNWCertTypeMacDevelopment = 3,
|
||||
/** OS X Production. */
|
||||
kNWCertTypeMacProduction = 4,
|
||||
/** Simplified Certificate Handling. */
|
||||
kNWCertTypeSimplified = 5,
|
||||
/** Web Push Production. */
|
||||
kNWCertTypeWebProduction = 6,
|
||||
/** VoIP Services. */
|
||||
kNWCertTypeVoIPServices = 7,
|
||||
/** WatchKit Services. */
|
||||
kNWCertTypeWatchKitServices = 8,
|
||||
/** Pass Type ID. */
|
||||
kNWCertTypePasses = 9,
|
||||
/** Unknown. */
|
||||
kNWCertTypeUnknown = 10,
|
||||
};
|
||||
|
||||
/** An ARC-friendly replacement of SecIdentityRef. */
|
||||
typedef id NWIdentityRef;
|
||||
|
||||
/** An ARC-friendly replacement of SecCertificateRef. */
|
||||
typedef id NWCertificateRef;
|
||||
|
||||
/** An ARC-friendly replacement of SecKeyRef. */
|
||||
typedef id NWKeyRef;
|
||||
|
||||
/** List all error codes. */
|
||||
typedef NS_ENUM(NSInteger, NWError) {
|
||||
kNWSuccess = 0,
|
||||
|
||||
/** No error, that's odd. */
|
||||
kNWErrorNone = 0,
|
||||
|
||||
/** APN processing error. */
|
||||
kNWErrorAPNProcessing = -1,
|
||||
/** APN missing device token. */
|
||||
kNWErrorAPNMissingDeviceToken = -2,
|
||||
/** APN missing topic. */
|
||||
kNWErrorAPNMissingTopic = -3,
|
||||
/** APN missing payload. */
|
||||
kNWErrorAPNMissingPayload = -4,
|
||||
/** APN invalid token size. */
|
||||
kNWErrorAPNInvalidTokenSize = -5,
|
||||
/** APN invalid topic size. */
|
||||
kNWErrorAPNInvalidTopicSize = -6,
|
||||
/** APN invalid payload size. */
|
||||
kNWErrorAPNInvalidPayloadSize = -7,
|
||||
/** APN invalid token. */
|
||||
kNWErrorAPNInvalidTokenContent = -8,
|
||||
/** APN unknown reason. */
|
||||
kNWErrorAPNUnknownReason = -9,
|
||||
/** APN shutdown. */
|
||||
kNWErrorAPNShutdown = -10,
|
||||
/** APN unknown error code. */
|
||||
kNWErrorAPNUnknownErrorCode = -11,
|
||||
|
||||
/** Push response command unknown. */
|
||||
kNWErrorPushResponseCommand = -107,
|
||||
/** Push reconnect requires connection. */
|
||||
kNWErrorPushNotConnected = -111,
|
||||
/** Push not fully sent. */
|
||||
kNWErrorPushWriteFail = -112,
|
||||
|
||||
/** Feedback data length unexpected. */
|
||||
kNWErrorFeedbackLength = -108,
|
||||
/** Feedback token length unexpected. */
|
||||
kNWErrorFeedbackTokenLength = -109,
|
||||
|
||||
/** Socket cannot be created. */
|
||||
kNWErrorSocketCreate = -222,
|
||||
/** Socket connecting failed. */
|
||||
kNWErrorSocketConnect = -201,
|
||||
/** Socket host cannot be resolved. */
|
||||
kNWErrorSocketResolveHostName = -219,
|
||||
/** Socket file control failed. */
|
||||
kNWErrorSocketFileControl = -220,
|
||||
/** Socket options cannot be set. */
|
||||
kNWErrorSocketOptions = -221,
|
||||
|
||||
/** SSL connection cannot be set. */
|
||||
kNWErrorSSLConnection = -204,
|
||||
/** SSL context cannot be created. */
|
||||
kNWErrorSSLContext = -202,
|
||||
/** SSL callbacks cannot be set. */
|
||||
kNWErrorSSLIOFuncs = -203,
|
||||
/** SSL peer domain name cannot be set. */
|
||||
kNWErrorSSLPeerDomainName = -205,
|
||||
/** SSL certificate cannot be set. */
|
||||
kNWErrorSSLCertificate = -206,
|
||||
/** SSL handshake dropped by server. */
|
||||
kNWErrorSSLDroppedByServer = -207,
|
||||
/** SSL handshake authentication failed. */
|
||||
kNWErrorSSLAuthFailed = -208,
|
||||
/** SSL handshake failed. */
|
||||
kNWErrorSSLHandshakeFail = -209,
|
||||
/** SSL handshake root not a known anchor. */
|
||||
kNWErrorSSLHandshakeUnknownRootCert = -223,
|
||||
/** SSL handshake chain not verifiable to root. */
|
||||
kNWErrorSSLHandshakeNoRootCert = -224,
|
||||
/** SSL handshake expired certificates. */
|
||||
kNWErrorSSLHandshakeCertExpired = -225,
|
||||
/** SSL handshake invalid certificate chain. */
|
||||
kNWErrorSSLHandshakeXCertChainInvalid = -226,
|
||||
/** SSL handshake expecting client cert. */
|
||||
kNWErrorSSLHandshakeClientCertRequested = -227,
|
||||
/** SSL handshake auth interrupted. */
|
||||
kNWErrorSSLHandshakeServerAuthCompleted = -228,
|
||||
/** SSL handshake certificate expired. */
|
||||
kNWErrorSSLHandshakePeerCertExpired = -229,
|
||||
/** SSL handshake certificate revoked. */
|
||||
kNWErrorSSLHandshakePeerCertRevoked = -230,
|
||||
/** SSL handshake certificate unknown. */
|
||||
kNWErrorSSLHandshakePeerCertUnknown = -233,
|
||||
/** SSL handshake internal error. */
|
||||
kNWErrorSSLHandshakeInternalError = -234,
|
||||
/** SSL handshake in dark wake. */
|
||||
kNWErrorSSLInDarkWake = -231,
|
||||
/** SSL handshake connection closed via error. */
|
||||
kNWErrorSSLHandshakeClosedAbort = -232,
|
||||
/** SSL handshake timeout. */
|
||||
kNWErrorSSLHandshakeTimeout = -218,
|
||||
|
||||
/** Read connection dropped by server. */
|
||||
kNWErrorReadDroppedByServer = -210,
|
||||
/** Read connection error. */
|
||||
kNWErrorReadClosedAbort = -211,
|
||||
/** Read connection closed. */
|
||||
kNWErrorReadClosedGraceful = -212,
|
||||
/** Read failed. */
|
||||
kNWErrorReadFail = -213,
|
||||
|
||||
/** Write connection dropped by server. */
|
||||
kNWErrorWriteDroppedByServer = -214,
|
||||
/** Write connection error. */
|
||||
kNWErrorWriteClosedAbort = -215,
|
||||
/** Write connection closed. */
|
||||
kNWErrorWriteClosedGraceful = -216,
|
||||
/** Write failed. */
|
||||
kNWErrorWriteFail = -217,
|
||||
|
||||
/** Identity does not contain certificate. */
|
||||
kNWErrorIdentityCopyCertificate = -304,
|
||||
/** Identity does not contain private key. */
|
||||
kNWErrorIdentityCopyPrivateKey = -310,
|
||||
|
||||
/** PKCS12 data cannot be imported. */
|
||||
kNWErrorPKCS12Import = -306,
|
||||
/** PKCS12 data is empty. */
|
||||
kNWErrorPKCS12EmptyData = -305,
|
||||
/** PKCS12 data cannot be read or is malformed. */
|
||||
kNWErrorPKCS12Decode = -311,
|
||||
/** PKCS12 data password incorrect. */
|
||||
kNWErrorPKCS12AuthFailed = -312,
|
||||
/** PKCS12 data wrong password. */
|
||||
kNWErrorPKCS12Password = -313,
|
||||
/** PKCS12 data password required. */
|
||||
kNWErrorPKCS12PasswordRequired = -314,
|
||||
/** PKCS12 data contains no identities. */
|
||||
kNWErrorPKCS12NoItems = -307,
|
||||
kNWErrorPKCS12MutlipleItems = -309,
|
||||
/** PKCS12 data contains multiple identities. */
|
||||
kNWErrorPKCS12MultipleItems = -309,
|
||||
|
||||
/** Keychain cannot be searched. */
|
||||
kNWErrorKeychainCopyMatching = -401,
|
||||
/** Keychain does not contain private key. */
|
||||
kNWErrorKeychainItemNotFound = -302,
|
||||
/** Keychain does not contain certificate. */
|
||||
kNWErrorKeychainCreateIdentity = -303,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, NWEnvironment) {
|
||||
NWEnvironmentNone = 0,
|
||||
NWEnvironmentSandbox = 1,
|
||||
NWEnvironmentProduction = 2,
|
||||
NWEnvironmentAuto = 3,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, NWEnvironmentOptions) {
|
||||
NWEnvironmentOptionNone = 0,
|
||||
NWEnvironmentOptionSandbox = 1 << NWEnvironmentSandbox,
|
||||
NWEnvironmentOptionProduction = 1 << NWEnvironmentProduction,
|
||||
NWEnvironmentOptionAny = NWEnvironmentOptionSandbox | NWEnvironmentOptionProduction
|
||||
};
|
||||
|
||||
/** NSError dictionary key for integer code that indicates underlying reason. */
|
||||
extern NSString * const NWErrorReasonCodeKey;
|
||||
|
||||
/** A collection of helper methods to support Cocoa-style error handling (`NSError`).
|
||||
|
||||
Most methods in this framework return `NO` or `nil` to indicate an error occurred. In that case an error object will be assigned. This class provides a mapping from codes to description string and some methods to instantiate the `NSError` object.
|
||||
*/
|
||||
|
||||
/** Returns string for given environment, for logging purposes */
|
||||
NSString * descriptionForEnvironentOptions(NWEnvironmentOptions environmentOptions);
|
||||
NSString * descriptionForEnvironent(NWEnvironment environment);
|
||||
NSString * descriptionForCertType(NWCertType type);
|
||||
|
||||
@interface NWErrorUtil : NSObject
|
||||
|
||||
+ (NSString *)stringWithError:(NWError)error;
|
||||
/** @name Helpers */
|
||||
|
||||
/** Assigns the error with provided code and associated description, for returning `NO`. */
|
||||
+ (BOOL)noWithErrorCode:(NWError)code error:(NSError **)error;
|
||||
+ (BOOL)noWithErrorCode:(NWError)code reason:(NSInteger)reason error:(NSError **)error;
|
||||
|
||||
/** Assigns the error with provided code and associated description, for returning `nil`. */
|
||||
+ (id)nilWithErrorCode:(NWError)code error:(NSError **)error;
|
||||
+ (id)nilWithErrorCode:(NWError)code reason:(NSInteger)reason error:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -7,12 +7,54 @@
|
||||
|
||||
#import "NWType.h"
|
||||
|
||||
NSString * const NWErrorReasonCodeKey = @"NWErrorReasonCodeKey";
|
||||
|
||||
NSString * descriptionForEnvironentOptions(NWEnvironmentOptions environmentOptions)
|
||||
{
|
||||
switch (environmentOptions) {
|
||||
case NWEnvironmentOptionNone: return @"No environment";
|
||||
case NWEnvironmentOptionSandbox: return @"Sandbox";
|
||||
case NWEnvironmentOptionProduction: return @"Production";
|
||||
case NWEnvironmentOptionAny: return @"Sandbox|Production";
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString * descriptionForEnvironent(NWEnvironment environment)
|
||||
{
|
||||
switch (environment) {
|
||||
case NWEnvironmentNone: return @"none";
|
||||
case NWEnvironmentProduction: return @"production";
|
||||
case NWEnvironmentSandbox: return @"sandbox";
|
||||
case NWEnvironmentAuto: return @"auto";
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString * descriptionForCertType(NWCertType type)
|
||||
{
|
||||
switch (type) {
|
||||
case kNWCertTypeNone: return @"none";
|
||||
case kNWCertTypeIOSDevelopment:
|
||||
case kNWCertTypeIOSProduction: return @"iOS";
|
||||
case kNWCertTypeMacDevelopment:
|
||||
case kNWCertTypeMacProduction: return @"macOS";
|
||||
case kNWCertTypeSimplified: return @"All";
|
||||
case kNWCertTypeWebProduction: return @"Website";
|
||||
case kNWCertTypeVoIPServices: return @"VoIP";
|
||||
case kNWCertTypeWatchKitServices: return @"WatchKit";
|
||||
case kNWCertTypePasses: return @"Pass";
|
||||
case kNWCertTypeUnknown: return @"unknown";
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@implementation NWErrorUtil
|
||||
|
||||
+ (NSString *)stringWithError:(NWError)error
|
||||
+ (NSString *)stringWithCode:(NWError)code
|
||||
{
|
||||
switch (error) {
|
||||
case kNWSuccess : return @"Success (no error)";
|
||||
switch (code) {
|
||||
case kNWErrorNone : return @"No error, that's odd";
|
||||
|
||||
case kNWErrorAPNProcessing : return @"APN processing error";
|
||||
case kNWErrorAPNMissingDeviceToken : return @"APN missing device token";
|
||||
@@ -22,8 +64,9 @@
|
||||
case kNWErrorAPNInvalidTopicSize : return @"APN invalid topic size";
|
||||
case kNWErrorAPNInvalidPayloadSize : return @"APN invalid payload size";
|
||||
case kNWErrorAPNInvalidTokenContent : return @"APN invalid token";
|
||||
case kNWErrorAPNUnknownReason : return @"APN unkown reason";
|
||||
case kNWErrorAPNUnknownReason : return @"APN unknown reason";
|
||||
case kNWErrorAPNShutdown : return @"APN shutdown";
|
||||
case kNWErrorAPNUnknownErrorCode : return @"APN unknown error code";
|
||||
|
||||
case kNWErrorPushResponseCommand : return @"Push response command unknown";
|
||||
case kNWErrorPushNotConnected : return @"Push reconnect requires connection";
|
||||
@@ -35,7 +78,7 @@
|
||||
case kNWErrorSocketCreate : return @"Socket cannot be created";
|
||||
case kNWErrorSocketResolveHostName : return @"Socket host cannot be resolved";
|
||||
case kNWErrorSocketConnect : return @"Socket connecting failed";
|
||||
case kNWErrorSocketFileControl : return @"Socket file contol failed";
|
||||
case kNWErrorSocketFileControl : return @"Socket file control failed";
|
||||
case kNWErrorSocketOptions : return @"Socket options cannot be set";
|
||||
|
||||
case kNWErrorSSLConnection : return @"SSL connection cannot be set";
|
||||
@@ -46,6 +89,18 @@
|
||||
case kNWErrorSSLDroppedByServer : return @"SSL handshake dropped by server";
|
||||
case kNWErrorSSLAuthFailed : return @"SSL handshake authentication failed";
|
||||
case kNWErrorSSLHandshakeFail : return @"SSL handshake failed";
|
||||
case kNWErrorSSLHandshakeUnknownRootCert : return @"SSL handshake root not a known anchor";
|
||||
case kNWErrorSSLHandshakeNoRootCert : return @"SSL handshake chain not verifiable to root";
|
||||
case kNWErrorSSLHandshakeCertExpired : return @"SSL handshake expired certificates";
|
||||
case kNWErrorSSLHandshakeXCertChainInvalid : return @"SSL handshake invalid certificate chain";
|
||||
case kNWErrorSSLHandshakeClientCertRequested : return @"SSL handshake expecting client cert";
|
||||
case kNWErrorSSLHandshakeServerAuthCompleted : return @"SSL handshake auth interrupted";
|
||||
case kNWErrorSSLHandshakePeerCertExpired : return @"SSL handshake certificate expired";
|
||||
case kNWErrorSSLHandshakePeerCertRevoked : return @"SSL handshake certificate revoked";
|
||||
case kNWErrorSSLHandshakePeerCertUnknown : return @"SSL handshake certificate unknown";
|
||||
case kNWErrorSSLHandshakeInternalError : return @"SSL handshake internal error";
|
||||
case kNWErrorSSLInDarkWake : return @"SSL handshake in dark wake";
|
||||
case kNWErrorSSLHandshakeClosedAbort : return @"SSL handshake connection closed via error";
|
||||
case kNWErrorSSLHandshakeTimeout : return @"SSL handshake timeout";
|
||||
|
||||
case kNWErrorReadDroppedByServer : return @"Read connection dropped by server";
|
||||
@@ -66,14 +121,50 @@
|
||||
case kNWErrorPKCS12Decode : return @"PKCS12 data cannot be read or is malformed";
|
||||
case kNWErrorPKCS12AuthFailed : return @"PKCS12 data password incorrect";
|
||||
case kNWErrorPKCS12Password : return @"PKCS12 data wrong password";
|
||||
case kNWErrorPKCS12PasswordRequired : return @"PKCS12 data password required";
|
||||
case kNWErrorPKCS12NoItems : return @"PKCS12 data contains no identities";
|
||||
case kNWErrorPKCS12MutlipleItems : return @"PKCS12 data contains multiple identities";
|
||||
case kNWErrorPKCS12MultipleItems : return @"PKCS12 data contains multiple identities";
|
||||
|
||||
case kNWErrorKeychainCopyMatching : return @"Keychain cannot be searched";
|
||||
case kNWErrorKeychainItemNotFound : return @"Keychain does not contain private key";
|
||||
case kNWErrorKeychainCreateIdentity : return @"Keychain does not contain certificate";
|
||||
}
|
||||
return @"Unkown";
|
||||
return @"Unknown";
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
+ (NSError *)errorWithErrorCode:(NWError)code reason:(NSInteger)reason
|
||||
{
|
||||
NSString *description = [self stringWithCode:code];
|
||||
if (reason) description = [NSString stringWithFormat:@"%@ (%i)", description, (int)reason];
|
||||
NSMutableDictionary *info = @{ NSLocalizedDescriptionKey:description }.mutableCopy;
|
||||
if (reason) [info setValue:@(reason) forKey:NWErrorReasonCodeKey];
|
||||
return [NSError errorWithDomain:@"NWPusherErrorDomain" code:code userInfo:info];
|
||||
}
|
||||
|
||||
+ (BOOL)noWithErrorCode:(NWError)code error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self noWithErrorCode:code reason:0 error:error];
|
||||
}
|
||||
|
||||
+ (BOOL)noWithErrorCode:(NWError)code reason:(NSInteger)reason error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSAssert(code != kNWErrorNone, @"code != kNWErrorNone");
|
||||
if (error) *error = [self errorWithErrorCode:code reason:reason];
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (id)nilWithErrorCode:(NWError)code error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self nilWithErrorCode:code reason:0 error:error];
|
||||
}
|
||||
|
||||
+ (id)nilWithErrorCode:(NWError)code reason:(NSInteger)reason error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSAssert(code != kNWErrorNone, @"code != kNWErrorNone");
|
||||
if (error) *error = [self errorWithErrorCode:code reason:reason];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
Before Width: | Height: | Size: 388 KiB After Width: | Height: | Size: 367 KiB |
|
Before Width: | Height: | Size: 363 KiB After Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 110 KiB |
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5053" systemVersion="13C64" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment defaultVersion="1070" identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5053"/>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner">
|
||||
@@ -21,7 +22,7 @@
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application">
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="kaW-YJ-UA5" id="0Xg-Lu-QoV"/>
|
||||
</connections>
|
||||
@@ -63,7 +64,7 @@
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||
<menuItem title="Quit NewApplication" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<menuItem title="Pusher" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
@@ -158,7 +159,7 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="600" height="300"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="878"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1177"/>
|
||||
<value key="minSize" type="size" width="500" height="200"/>
|
||||
<view key="contentView" id="se5-gp-TjO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="300"/>
|
||||
@@ -166,7 +167,6 @@
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2E1-cP-egh">
|
||||
<rect key="frame" x="480" y="252" width="106" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="94" id="Rut-jE-8JY"/>
|
||||
</constraints>
|
||||
@@ -180,7 +180,6 @@
|
||||
</button>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aJR-Gv-8Xr">
|
||||
<rect key="frame" x="18" y="256" width="463" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Select certificate" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="RvW-sH-WOM" id="XcZ-r4-iAc">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -194,22 +193,8 @@
|
||||
<action selector="certificateSelected:" target="kaW-YJ-UA5" id="Asf-IM-UFv"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<comboBox verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xpc-0j-elb">
|
||||
<rect key="frame" x="20" y="230" width="563" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<comboBoxCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" enabled="NO" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Device push token (only first 64 hex chars used, other text is ignored)" drawsBackground="YES" completes="NO" numberOfVisibleItems="5" id="Dzy-yi-6QY">
|
||||
<font key="font" size="10" name="Monaco"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</comboBoxCell>
|
||||
<connections>
|
||||
<action selector="tokenSelected:" target="kaW-YJ-UA5" id="p9a-qh-3Nh"/>
|
||||
<outlet property="delegate" destination="kaW-YJ-UA5" id="ZZ9-SB-ZBN"/>
|
||||
</connections>
|
||||
</comboBox>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="imk-VN-mp1">
|
||||
<rect key="frame" x="18" y="202" width="155" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<rect key="frame" x="18" y="180" width="155" height="26"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="150" id="rN0-Vp-M6c"/>
|
||||
</constraints>
|
||||
@@ -231,17 +216,16 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="c5v-al-Hn5">
|
||||
<rect key="frame" x="176" y="202" width="155" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<rect key="frame" x="176" y="180" width="160" height="26"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="150" id="DV9-wc-su5"/>
|
||||
<constraint firstAttribute="width" constant="155" id="DV9-wc-su5"/>
|
||||
</constraints>
|
||||
<popUpButtonCell key="cell" type="push" title="Priority: None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="5dF-Og-jd1" id="ijp-4f-mXC">
|
||||
<popUpButtonCell key="cell" type="push" title="Priority: None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="5dF-Og-jd1" id="ijp-4f-mXC">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" title="OtherViews" id="XbO-4x-lw5">
|
||||
<items>
|
||||
<menuItem title="Priority: None" id="5dF-Og-jd1"/>
|
||||
<menuItem title="Priority: None" state="on" id="5dF-Og-jd1"/>
|
||||
<menuItem title="Conserve power (5)" id="7DJ-kb-Wf3"/>
|
||||
<menuItem title="Immediately (10)" id="eBG-1S-cUL"/>
|
||||
</items>
|
||||
@@ -249,9 +233,8 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<segmentedControl verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="EnU-aP-5PD">
|
||||
<rect key="frame" x="464" y="199" width="118" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<segmentedCell key="cell" alignment="left" style="rounded" trackingMode="selectOne" id="Olm-G5-Man">
|
||||
<rect key="frame" x="456" y="177" width="126" height="24"/>
|
||||
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="selectOne" id="Olm-G5-Man">
|
||||
<font key="font" metaFont="system"/>
|
||||
<segments>
|
||||
<segment label="Payload" selected="YES"/>
|
||||
@@ -264,7 +247,6 @@
|
||||
</segmentedControl>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pAB-wf-piT">
|
||||
<rect key="frame" x="504" y="13" width="82" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="70" id="huG-oV-gOx"/>
|
||||
</constraints>
|
||||
@@ -278,7 +260,6 @@
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="M70-t1-JuG">
|
||||
<rect key="frame" x="18" y="23" width="486" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" id="d6h-q1-H0e">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -286,29 +267,29 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Q6j-iu-9I9">
|
||||
<rect key="frame" x="20" y="45" width="560" height="152"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<rect key="frame" x="20" y="45" width="560" height="130"/>
|
||||
<clipView key="contentView" id="e4e-ov-ymY">
|
||||
<rect key="frame" x="1" y="1" width="558" height="150"/>
|
||||
<rect key="frame" x="1" y="1" width="558" height="128"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView importsGraphics="NO" findStyle="panel" continuousSpellChecking="YES" allowsUndo="YES" usesRuler="YES" usesFontPanel="YES" verticallyResizable="YES" allowsNonContiguousLayout="YES" quoteSubstitution="YES" dashSubstitution="YES" spellingCorrection="YES" smartInsertDelete="YES" id="ad5-lb-L5u">
|
||||
<rect key="frame" x="0.0" y="0.0" width="558" height="150"/>
|
||||
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" findStyle="panel" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" allowsNonContiguousLayout="YES" textCompletion="NO" id="ad5-lb-L5u">
|
||||
<rect key="frame" x="0.0" y="0.0" width="558" height="128"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<size key="minSize" width="558" height="150"/>
|
||||
<size key="minSize" width="558" height="128"/>
|
||||
<size key="maxSize" width="573" height="10000000"/>
|
||||
<attributedString key="textStorage">
|
||||
<fragment content="..">
|
||||
<attributes>
|
||||
<font key="NSFont" metaFont="toolTip"/>
|
||||
<font key="NSFont" metaFont="smallSystem"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
</attributedString>
|
||||
<color key="insertionPointColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<size key="minSize" width="558" height="150"/>
|
||||
<size key="maxSize" width="573" height="10000000"/>
|
||||
<allowedInputSourceLocales>
|
||||
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
|
||||
</allowedInputSourceLocales>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="kaW-YJ-UA5" id="bUh-Oq-w5B"/>
|
||||
</connections>
|
||||
@@ -321,13 +302,12 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="sbz-Qw-fOe">
|
||||
<rect key="frame" x="543" y="1" width="16" height="150"/>
|
||||
<rect key="frame" x="543" y="1" width="16" height="128"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gQs-Aa-IJi">
|
||||
<rect key="frame" x="470" y="49" width="104" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="100" id="Vlu-lQ-8rQ"/>
|
||||
</constraints>
|
||||
@@ -338,21 +318,18 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<scrollView hidden="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="z4n-XE-cfI">
|
||||
<rect key="frame" x="20" y="45" width="560" height="152"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<rect key="frame" x="20" y="45" width="560" height="130"/>
|
||||
<clipView key="contentView" id="uaF-sl-etn">
|
||||
<rect key="frame" x="1" y="1" width="558" height="150"/>
|
||||
<rect key="frame" x="1" y="1" width="558" height="128"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView importsGraphics="NO" findStyle="panel" continuousSpellChecking="YES" allowsUndo="YES" usesRuler="YES" usesFontPanel="YES" verticallyResizable="YES" allowsNonContiguousLayout="YES" quoteSubstitution="YES" dashSubstitution="YES" spellingCorrection="YES" smartInsertDelete="YES" id="pi6-RR-ayT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="558" height="150"/>
|
||||
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="panel" continuousSpellChecking="YES" allowsUndo="YES" usesRuler="YES" allowsNonContiguousLayout="YES" quoteSubstitution="YES" dashSubstitution="YES" spellingCorrection="YES" smartInsertDelete="YES" id="pi6-RR-ayT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="558" height="128"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" name="windowBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="minSize" width="558" height="150"/>
|
||||
<size key="minSize" width="558" height="128"/>
|
||||
<size key="maxSize" width="573" height="10000000"/>
|
||||
<color key="insertionPointColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<size key="minSize" width="558" height="150"/>
|
||||
<size key="maxSize" width="573" height="10000000"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -362,10 +339,32 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="pny-Of-exT">
|
||||
<rect key="frame" x="543" y="1" width="16" height="150"/>
|
||||
<rect key="frame" x="543" y="1" width="16" height="128"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="hU6-Ym-KR3">
|
||||
<rect key="frame" x="18" y="235" width="223" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Should use sandbox environment" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="ros-Zj-bni">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="sanboxCheckBoxDidPressed:" target="kaW-YJ-UA5" id="kzM-IO-iQv"/>
|
||||
</connections>
|
||||
</button>
|
||||
<comboBox verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xpc-0j-elb">
|
||||
<rect key="frame" x="20" y="208" width="563" height="23"/>
|
||||
<comboBoxCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" enabled="NO" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Device push token (only first 64 hex chars used, other text is ignored)" drawsBackground="YES" completes="NO" numberOfVisibleItems="5" id="Dzy-yi-6QY">
|
||||
<font key="font" size="10" name="Monaco"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</comboBoxCell>
|
||||
<connections>
|
||||
<action selector="tokenSelected:" target="kaW-YJ-UA5" id="p9a-qh-3Nh"/>
|
||||
<outlet property="delegate" destination="kaW-YJ-UA5" id="ZZ9-SB-ZBN"/>
|
||||
</connections>
|
||||
</comboBox>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="pAB-wf-piT" secondAttribute="trailing" constant="20" id="1DK-KM-tMh"/>
|
||||
@@ -387,9 +386,11 @@
|
||||
<constraint firstItem="c5v-al-Hn5" firstAttribute="leading" secondItem="imk-VN-mp1" secondAttribute="trailing" constant="8" id="Syg-OZ-s0W"/>
|
||||
<constraint firstItem="2E1-cP-egh" firstAttribute="top" secondItem="se5-gp-TjO" secondAttribute="top" constant="20" id="VPW-kd-JRb"/>
|
||||
<constraint firstItem="pAB-wf-piT" firstAttribute="top" secondItem="z4n-XE-cfI" secondAttribute="bottom" constant="4" id="VSi-vV-wpY"/>
|
||||
<constraint firstItem="xpc-0j-elb" firstAttribute="top" secondItem="aJR-Gv-8Xr" secondAttribute="bottom" constant="8" id="ejA-7v-S2S"/>
|
||||
<constraint firstItem="hU6-Ym-KR3" firstAttribute="top" secondItem="aJR-Gv-8Xr" secondAttribute="bottom" constant="8" symbolic="YES" id="ZTZ-NV-b30"/>
|
||||
<constraint firstItem="EnU-aP-5PD" firstAttribute="top" secondItem="c5v-al-Hn5" secondAttribute="top" constant="4" id="emm-N1-qPy"/>
|
||||
<constraint firstAttribute="trailing" secondItem="z4n-XE-cfI" secondAttribute="trailing" constant="20" id="gM0-t7-RqF"/>
|
||||
<constraint firstItem="hU6-Ym-KR3" firstAttribute="leading" secondItem="aJR-Gv-8Xr" secondAttribute="leading" id="h8B-8W-BTt"/>
|
||||
<constraint firstItem="xpc-0j-elb" firstAttribute="top" secondItem="hU6-Ym-KR3" secondAttribute="bottom" constant="8" symbolic="YES" id="hqj-w5-p6V"/>
|
||||
<constraint firstItem="M70-t1-JuG" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" constant="20" id="ief-Fk-3Mg"/>
|
||||
<constraint firstAttribute="trailing" secondItem="gQs-Aa-IJi" secondAttribute="trailing" constant="28" id="oT0-VS-Mam"/>
|
||||
<constraint firstAttribute="bottom" secondItem="M70-t1-JuG" secondAttribute="bottom" constant="23" id="pWW-7F-Cxq"/>
|
||||
@@ -414,6 +415,7 @@
|
||||
<outlet property="_priorityPopup" destination="c5v-al-Hn5" id="6zL-7L-7t3"/>
|
||||
<outlet property="_pushButton" destination="pAB-wf-piT" id="ndY-Uh-dgS"/>
|
||||
<outlet property="_reconnectButton" destination="2E1-cP-egh" id="4CL-8O-MKG"/>
|
||||
<outlet property="_sanboxCheckBox" destination="hU6-Ym-KR3" id="9Zb-UH-d23"/>
|
||||
<outlet property="_tokenCombo" destination="xpc-0j-elb" id="f1o-6I-zGl"/>
|
||||
<outlet property="window" destination="F0z-JX-Cv5" id="a4g-51-daC"/>
|
||||
</connections>
|
||||
|
||||
@@ -6,12 +6,7 @@
|
||||
//
|
||||
|
||||
#import "NWAppDelegate.h"
|
||||
#import "NWHub.h"
|
||||
#import "NWNotification.h"
|
||||
#import "NWSecTools.h"
|
||||
#import "NWLCore.h"
|
||||
#import "NWPushFeedback.h"
|
||||
|
||||
#import <PusherKit/PusherKit.h>
|
||||
|
||||
@interface NWAppDelegate () <NWHubDelegate> @end
|
||||
|
||||
@@ -27,6 +22,7 @@
|
||||
IBOutlet NSPopUpButton *_expiryPopup;
|
||||
IBOutlet NSPopUpButton *_priorityPopup;
|
||||
IBOutlet NSScrollView *_logScroll;
|
||||
IBOutlet NSButton *_sanboxCheckBox;
|
||||
|
||||
NWHub *_hub;
|
||||
NSDictionary *_config;
|
||||
@@ -101,6 +97,7 @@
|
||||
{
|
||||
[self addTokenAndUpdateCombo];
|
||||
[self push];
|
||||
[self upPayloadTextIndex];
|
||||
}
|
||||
|
||||
- (IBAction)reconnect:(NSButton *)sender
|
||||
@@ -108,11 +105,19 @@
|
||||
[self reconnect];
|
||||
}
|
||||
|
||||
- (void)notification:(NWNotification *)notification didFailWithResult:(NWError)result
|
||||
- (IBAction)sanboxCheckBoxDidPressed:(NSButton *)sender
|
||||
{
|
||||
if (_selectedCertificate)
|
||||
{
|
||||
[self reconnect];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notification:(NWNotification *)notification didFailWithError:(NSError *)error
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
//NSLog(@"failed notification: %@ %@ %lu %lu %lu", notification.payload, notification.token, notification.identifier, notification.expires, notification.priority);
|
||||
NWLogWarn(@"Notification error: %@", [NWErrorUtil stringWithError:result]);
|
||||
NWLogWarn(@"Notification error: %@", error.localizedDescription);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -128,19 +133,19 @@
|
||||
|
||||
- (void)loadCertificatesFromKeychain
|
||||
{
|
||||
NSArray *certs = nil;
|
||||
NWError keychain = [NWSecTools keychainCertificates:&certs];
|
||||
if (keychain != kNWSuccess) {
|
||||
NWLogWarn(@"Unable to access keychain: %@", [NWErrorUtil stringWithError:keychain]);
|
||||
NSError *error = nil;
|
||||
NSArray *certs = [NWSecTools keychainCertificatesWithError:&error];
|
||||
if (!certs) {
|
||||
NWLogWarn(@"Unable to access keychain: %@", error.localizedDescription);
|
||||
}
|
||||
if (!certs.count) {
|
||||
NWLogWarn(@"No push certificates in keychain.");
|
||||
}
|
||||
certs = [certs sortedArrayUsingComparator:^NSComparisonResult(NWCertificateRef a, NWCertificateRef b) {
|
||||
BOOL adev = [NWSecTools isSandboxCertificate:a];
|
||||
BOOL bdev = [NWSecTools isSandboxCertificate:b];
|
||||
if (adev != bdev) {
|
||||
return adev ? NSOrderedAscending : NSOrderedDescending;
|
||||
NWEnvironmentOptions envOptionsA = [NWSecTools environmentOptionsForCertificate:a];
|
||||
NWEnvironmentOptions envOptionsB = [NWSecTools environmentOptionsForCertificate:b];
|
||||
if (envOptionsA != envOptionsB) {
|
||||
return envOptionsA < envOptionsB;
|
||||
}
|
||||
NSString *aname = [NWSecTools summaryWithCertificate:a];
|
||||
NSString *bname = [NWSecTools summaryWithCertificate:b];
|
||||
@@ -158,13 +163,20 @@
|
||||
NSMutableString *suffix = @" ".mutableCopy;
|
||||
[_certificatePopup removeAllItems];
|
||||
[_certificatePopup addItemWithTitle:@"Select Push Certificate"];
|
||||
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
|
||||
[formatter setDateStyle:NSDateFormatterShortStyle];
|
||||
[formatter setTimeStyle:NSDateFormatterShortStyle];
|
||||
for (NSArray *pair in _certificateIdentityPairs) {
|
||||
NWCertificateRef certificate = pair[0];
|
||||
BOOL hasIdentity = (pair[1] != NSNull.null);
|
||||
BOOL sandbox = [NWSecTools isSandboxCertificate:certificate];
|
||||
NSString *summary = [NWSecTools summaryWithCertificate:certificate];
|
||||
NWEnvironmentOptions environmentOptions = [NWSecTools environmentOptionsForCertificate:certificate];
|
||||
NSString *summary = nil;
|
||||
NWCertType certType = [NWSecTools typeWithCertificate:certificate summary:&summary];
|
||||
NSString *type = descriptionForCertType(certType);
|
||||
NSDate *date = [NWSecTools expirationWithCertificate:certificate];
|
||||
NSString *expire = [NSString stringWithFormat:@" [%@]", date ? [formatter stringFromDate:date] : @"expired"];
|
||||
// summary = @"com.example.app";
|
||||
[_certificatePopup addItemWithTitle:[NSString stringWithFormat:@"%@%@%@%@", hasIdentity ? @"imported: " : @"", summary, sandbox ? @" (sandbox)" : @"", suffix]];
|
||||
[_certificatePopup addItemWithTitle:[NSString stringWithFormat:@"%@%@ (%@ %@)%@%@", hasIdentity ? @"imported: " : @"", summary, type, descriptionForEnvironentOptions(environmentOptions), expire, suffix]];
|
||||
[suffix appendString:@" "];
|
||||
}
|
||||
[_certificatePopup addItemWithTitle:@"Import PKCS #12 file (.p12)..."];
|
||||
@@ -194,17 +206,20 @@
|
||||
}
|
||||
NSString *password = input.stringValue;
|
||||
NSData *data = [NSData dataWithContentsOfURL:url];
|
||||
NSArray *ids = nil;
|
||||
NWError identdata = [NWSecTools identitiesWithPKCS12Data:data password:password identities:&ids];
|
||||
if (identdata != kNWSuccess) {
|
||||
NWLogWarn(@"Unable to read p12 file: %@", [NWErrorUtil stringWithError:identdata]);
|
||||
NSError *error = nil;
|
||||
NSArray *ids = [NWSecTools identitiesWithPKCS12Data:data password:password error:&error];
|
||||
if (!ids && password.length == 0 && error.code == kNWErrorPKCS12Password) {
|
||||
ids = [NWSecTools identitiesWithPKCS12Data:data password:nil error:&error];
|
||||
}
|
||||
if (!ids) {
|
||||
NWLogWarn(@"Unable to read p12 file: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
for (NWIdentityRef identity in ids) {
|
||||
NWCertificateRef certificate = nil;
|
||||
NWError certident = [NWSecTools certificateWithIdentity:identity certificate:&certificate];
|
||||
if (certident != kNWSuccess) {
|
||||
NWLogWarn(@"Unable to import p12 file: %@", [NWErrorUtil stringWithError:certident]);
|
||||
NSError *error = nil;
|
||||
NWCertificateRef certificate = [NWSecTools certificateWithIdentity:identity error:&error];
|
||||
if (!certificate) {
|
||||
NWLogWarn(@"Unable to import p12 file: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
[pairs addObject:@[certificate, identity]];
|
||||
@@ -271,6 +286,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (NWEnvironment)selectedEnvironmentForCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
return (_sanboxCheckBox.state & NSOnState) ? NWEnvironmentSandbox : NWEnvironmentProduction;
|
||||
}
|
||||
|
||||
- (NWEnvironment)preferredEnvironmentForCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
NWEnvironmentOptions environmentOptions = [NWSecTools environmentOptionsForCertificate:certificate];
|
||||
|
||||
return (environmentOptions & NWEnvironmentOptionSandbox) ? NWEnvironmentSandbox : NWEnvironmentProduction;
|
||||
}
|
||||
|
||||
#pragma mark - Connection
|
||||
|
||||
- (void)connectWithCertificateAtIndex:(NSUInteger)index
|
||||
@@ -278,14 +305,16 @@
|
||||
if (index == 0) {
|
||||
[_certificatePopup selectItemAtIndex:0];
|
||||
_lastSelectedIndex = 0;
|
||||
[self selectCertificate:nil identity:nil];
|
||||
[self selectCertificate:nil identity:nil environment:NWEnvironmentSandbox];
|
||||
_tokenCombo.enabled = NO;
|
||||
[self loadSelectedToken];
|
||||
} else if (index <= _certificateIdentityPairs.count) {
|
||||
[_certificatePopup selectItemAtIndex:index];
|
||||
_lastSelectedIndex = index;
|
||||
NSArray *pair = [_certificateIdentityPairs objectAtIndex:index - 1];
|
||||
[self selectCertificate:pair[0] identity:pair[1] == NSNull.null ? nil : pair[1]];
|
||||
NWCertificateRef certificate = pair[0];
|
||||
NWIdentityRef identity = pair[1];
|
||||
[self selectCertificate:certificate identity:identity == NSNull.null ? nil : identity environment:[self preferredEnvironmentForCertificate:certificate]];
|
||||
_tokenCombo.enabled = YES;
|
||||
[self loadSelectedToken];
|
||||
} else {
|
||||
@@ -294,12 +323,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)selectCertificate:(NWCertificateRef)certificate identity:(NWIdentityRef)identity
|
||||
- (void)disableButtons
|
||||
{
|
||||
_pushButton.enabled = NO;
|
||||
_reconnectButton.enabled = NO;
|
||||
_sanboxCheckBox.enabled = NO;
|
||||
}
|
||||
|
||||
- (void)enableButtonsForCertificate:(NWCertificateRef)certificate environment:(NWEnvironment)environment
|
||||
{
|
||||
NWEnvironmentOptions environmentOptions = [NWSecTools environmentOptionsForCertificate:certificate];
|
||||
|
||||
BOOL shouldEnableEnvButton = (environmentOptions == NWEnvironmentOptionAny);
|
||||
BOOL shouldSelectSandboxEnv = (environment == NWEnvironmentSandbox);
|
||||
|
||||
_pushButton.enabled = YES;
|
||||
_reconnectButton.enabled = YES;
|
||||
_sanboxCheckBox.enabled = shouldEnableEnvButton;
|
||||
_sanboxCheckBox.state = shouldSelectSandboxEnv ? NSOnState : NSOffState;
|
||||
}
|
||||
|
||||
- (void)selectCertificate:(NWCertificateRef)certificate identity:(NWIdentityRef)identity environment:(NWEnvironment)environment
|
||||
{
|
||||
if (_hub) {
|
||||
[_hub disconnect]; _hub = nil;
|
||||
_pushButton.enabled = NO;
|
||||
_reconnectButton.enabled = NO;
|
||||
|
||||
[self disableButtons];
|
||||
NWLogInfo(@"Disconnected from APN");
|
||||
}
|
||||
|
||||
@@ -307,28 +356,22 @@
|
||||
[self updateTokenCombo];
|
||||
|
||||
if (certificate) {
|
||||
BOOL sandbox = [NWSecTools isSandboxCertificate:certificate];
|
||||
|
||||
NSString *summary = [NWSecTools summaryWithCertificate:certificate];
|
||||
NWLogInfo(@"Connecting to APN.. (%@%@)", summary, sandbox ? @" sandbox" : @"");
|
||||
NWLogInfo(@"Connecting to APN... (%@ %@)", summary, descriptionForEnvironent(environment));
|
||||
|
||||
dispatch_async(_serial, ^{
|
||||
NWHub *hub = [[NWHub alloc] initWithDelegate:self];
|
||||
NWError connected = kNWSuccess;
|
||||
NWIdentityRef ident = identity;
|
||||
if (!ident) {
|
||||
connected = [NWSecTools keychainIdentityWithCertificate:certificate identity:&ident];
|
||||
}
|
||||
if (connected == kNWSuccess) {
|
||||
connected = [hub connectWithIdentity:ident];
|
||||
}
|
||||
NSError *error = nil;
|
||||
NWIdentityRef ident = identity ?: [NWSecTools keychainIdentityWithCertificate:certificate error:&error];
|
||||
NWHub *hub = [NWHub connectWithDelegate:self identity:ident environment:environment error:&error];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (connected == kNWSuccess) {
|
||||
NWLogInfo(@"Connected (%@%@)", summary, sandbox ? @" sandbox" : @"");
|
||||
if (hub) {
|
||||
NWLogInfo(@"Connected (%@ %@)", summary, descriptionForEnvironent(environment));
|
||||
_hub = hub;
|
||||
_pushButton.enabled = YES;
|
||||
_reconnectButton.enabled = YES;
|
||||
|
||||
[self enableButtonsForCertificate:certificate environment:environment];
|
||||
} else {
|
||||
NWLogWarn(@"Unable to connect: %@", [NWErrorUtil stringWithError:connected]);
|
||||
NWLogWarn(@"Unable to connect: %@", error.localizedDescription);
|
||||
[hub disconnect];
|
||||
[_certificatePopup selectItemAtIndex:0];
|
||||
}
|
||||
@@ -339,21 +382,12 @@
|
||||
|
||||
- (void)reconnect
|
||||
{
|
||||
NWLogInfo(@"Reconnecting..");
|
||||
_pushButton.enabled = NO;
|
||||
_reconnectButton.enabled = NO;
|
||||
dispatch_async(_serial, ^{
|
||||
NWError connected = [_hub reconnect];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (connected == kNWSuccess) {
|
||||
NWLogInfo(@"Reconnected");
|
||||
_pushButton.enabled = YES;
|
||||
} else {
|
||||
NWLogWarn(@"Unable to reconnect: %@", [NWErrorUtil stringWithError:connected]);
|
||||
}
|
||||
_reconnectButton.enabled = YES;
|
||||
});
|
||||
});
|
||||
NSString *summary = [NWSecTools summaryWithCertificate:_selectedCertificate];
|
||||
NWEnvironment environment = [self selectedEnvironmentForCertificate:_selectedCertificate];
|
||||
|
||||
NWLogInfo(@"Reconnecting to APN...(%@ %@)", summary, descriptionForEnvironent(environment));
|
||||
|
||||
[self selectCertificate:_selectedCertificate identity:nil environment:environment];
|
||||
}
|
||||
|
||||
- (void)push
|
||||
@@ -365,15 +399,24 @@
|
||||
NWLogInfo(@"Pushing..");
|
||||
dispatch_async(_serial, ^{
|
||||
NWNotification *notification = [[NWNotification alloc] initWithPayload:payload token:token identifier:0 expiration:expiry priority:priority];
|
||||
NSUInteger failed = [_hub pushNotifications:@[notification] autoReconnect:NO];
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
|
||||
dispatch_after(popTime, _serial, ^(void){
|
||||
NSUInteger failed2 = failed + [_hub flushFailed];
|
||||
if (!failed2) NWLogInfo(@"Payload has been pushed");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self upPayloadTextIndex];
|
||||
NSError *error = nil;
|
||||
BOOL pushed = [_hub pushNotification:notification autoReconnect:YES error:&error];
|
||||
if (pushed) {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
|
||||
dispatch_after(popTime, _serial, ^(void){
|
||||
NSError *error = nil;
|
||||
NWNotification *failed = nil;
|
||||
BOOL read = [_hub readFailed:&failed autoReconnect:YES error:&error];
|
||||
if (read) {
|
||||
if (!failed) NWLogInfo(@"Payload has been pushed");
|
||||
} else {
|
||||
NWLogWarn(@"Unable to read: %@", error.localizedDescription);
|
||||
}
|
||||
[_hub trimIdentifiers];
|
||||
});
|
||||
});
|
||||
} else {
|
||||
NWLogWarn(@"Unable to push: %@", error.localizedDescription);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -385,24 +428,20 @@
|
||||
NWLogWarn(@"Unable to connect to feedback service: no certificate selected");
|
||||
return;
|
||||
}
|
||||
BOOL sandbox = [NWSecTools isSandboxCertificate:certificate];
|
||||
NWEnvironment environment = [self selectedEnvironmentForCertificate:certificate];
|
||||
NSString *summary = [NWSecTools summaryWithCertificate:certificate];
|
||||
NWLogInfo(@"Connecting to feedback service.. (%@%@)", summary, sandbox ? @" sandbox" : @"");
|
||||
NWIdentityRef identity = nil;
|
||||
NWError connected = [NWSecTools keychainIdentityWithCertificate:_selectedCertificate identity:&identity];
|
||||
NWPushFeedback *feedback = [[NWPushFeedback alloc] init];
|
||||
if (connected == kNWSuccess) {
|
||||
connected = [feedback connectWithIdentity:identity];
|
||||
}
|
||||
if (connected != kNWSuccess) {
|
||||
NWLogWarn(@"Unable to connect to feedback service: %@", [NWErrorUtil stringWithError:connected]);
|
||||
NWLogInfo(@"Connecting to feedback service.. (%@ %@)", summary, descriptionForEnvironent(environment));
|
||||
NSError *error = nil;
|
||||
NWIdentityRef identity = [NWSecTools keychainIdentityWithCertificate:_selectedCertificate error:&error];
|
||||
NWPushFeedback *feedback = [NWPushFeedback connectWithIdentity:identity environment:[self selectedEnvironmentForCertificate:certificate] error:&error];
|
||||
if (!feedback) {
|
||||
NWLogWarn(@"Unable to connect to feedback service: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
NWLogInfo(@"Reading feedback service.. (%@%@)", summary, sandbox ? @" sandbox" : @"");
|
||||
NSArray *pairs = nil;
|
||||
NWError read = [feedback readTokenDatePairs:&pairs max:1000];
|
||||
if (read != kNWSuccess) {
|
||||
NWLogWarn(@"Unable to read feedback: %@", [NWErrorUtil stringWithError:read]);
|
||||
NWLogInfo(@"Reading feedback service.. (%@ %@)", summary, descriptionForEnvironent(environment));
|
||||
NSArray *pairs = [feedback readTokenDatePairsWithMax:1000 error:&error];
|
||||
if (!pairs) {
|
||||
NWLogWarn(@"Unable to read feedback: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
for (NSArray *pair in pairs) {
|
||||
@@ -420,14 +459,16 @@
|
||||
|
||||
- (NSString *)identifierWithCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
BOOL sandbox = [NWSecTools isSandboxCertificate:certificate];
|
||||
NWEnvironmentOptions environmentOptions = [NWSecTools environmentOptionsForCertificate:certificate];
|
||||
NSString *summary = [NWSecTools summaryWithCertificate:certificate];
|
||||
return summary ? [NSString stringWithFormat:@"%@%@", summary, sandbox ? @"-sandbox" : @""] : nil;
|
||||
return summary ? [NSString stringWithFormat:@"%@-%@", summary, descriptionForEnvironentOptions(environmentOptions)] : nil;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)tokensWithCertificate:(NWCertificateRef)certificate create:(BOOL)create
|
||||
{
|
||||
NSString *identifier = [self identifierWithCertificate:certificate];
|
||||
NWEnvironment environment = [self selectedEnvironmentForCertificate:_selectedCertificate];
|
||||
NSString *summary = [NWSecTools summaryWithCertificate:certificate];
|
||||
NSString *identifier = summary ? [NSString stringWithFormat:@"%@%@", summary, environment == NWEnvironmentSandbox ? @"-sandbox" : @""] : nil;
|
||||
if (!identifier) return nil;
|
||||
NSArray *result = _config[@"identifiers"][identifier];
|
||||
if (create && !result) result = (_config[@"identifiers"][identifier] = @[].mutableCopy);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>icon</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.noodlewerk.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
@@ -17,17 +17,17 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.5.1</string>
|
||||
<string>0.7.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>7</string>
|
||||
<string>19</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.developer-tools</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2012 noodlewerk. All rights reserved.</string>
|
||||
<string>Copyright © 2012 noodlewerk. All rights reserved. https://github.com/noodlewerk/NWPusher</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>Application</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'NWPusher'
|
||||
s.version = '0.5.1'
|
||||
s.summary = 'iOS/OS X library for playing with the Apple Push Notification Service.'
|
||||
s.version = '0.7.5'
|
||||
s.summary = 'OS X and iOS application and framework to play with the Apple Push Notification service (APNs).'
|
||||
s.homepage = 'https://github.com/noodlewerk/NWPusher'
|
||||
s.license = { :type => 'BSD', :file => 'LICENSE.txt' }
|
||||
s.author = { 'Leonard van Driel' => 'leonardvandriel@gmail.com' }
|
||||
|
||||
@@ -7,9 +7,45 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5C7803881D3C4668002107FB /* PusherKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C7803861D3C4668002107FB /* PusherKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C78038B1D3C4668002107FB /* PusherKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C7803841D3C4668002107FB /* PusherKit.framework */; };
|
||||
5C78038C1D3C4668002107FB /* PusherKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5C7803841D3C4668002107FB /* PusherKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
5C7803911D3C4683002107FB /* NWHub.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A1189682D30043DA98 /* NWHub.m */; };
|
||||
5C7803921D3C4683002107FB /* NWNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A3189682D30043DA98 /* NWNotification.m */; };
|
||||
5C7803931D3C4683002107FB /* NWPusher.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A5189682D30043DA98 /* NWPusher.m */; };
|
||||
5C7803941D3C4683002107FB /* NWPushFeedback.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A7189682D30043DA98 /* NWPushFeedback.m */; };
|
||||
5C7803951D3C4683002107FB /* NWSecTools.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A9189682D30043DA98 /* NWSecTools.m */; };
|
||||
5C7803961D3C4683002107FB /* NWSSLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232AB189682D30043DA98 /* NWSSLConnection.m */; };
|
||||
5C7803971D3C4683002107FB /* NWType.m in Sources */ = {isa = PBXBuildFile; fileRef = B34BF1B318DDF401004BA9F7 /* NWType.m */; };
|
||||
5C7803981D3C4698002107FB /* NWLCore.c in Sources */ = {isa = PBXBuildFile; fileRef = B3376A61172BB71200242EBB /* NWLCore.c */; };
|
||||
5C7803991D3C4749002107FB /* NWHub.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232A0189682D30043DA98 /* NWHub.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C78039A1D3C4749002107FB /* NWNotification.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232A2189682D30043DA98 /* NWNotification.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C78039B1D3C4749002107FB /* NWPusher.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232A4189682D30043DA98 /* NWPusher.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C78039C1D3C4749002107FB /* NWPushFeedback.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232A6189682D30043DA98 /* NWPushFeedback.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C78039D1D3C4749002107FB /* NWSecTools.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232A8189682D30043DA98 /* NWSecTools.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C78039E1D3C4749002107FB /* NWSSLConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232AA189682D30043DA98 /* NWSSLConnection.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C78039F1D3C4749002107FB /* NWType.h in Headers */ = {isa = PBXBuildFile; fileRef = B34BF1B218DDF401004BA9F7 /* NWType.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C7803A01D3C4749002107FB /* NWLCore.h in Headers */ = {isa = PBXBuildFile; fileRef = B3376A62172BB71200242EBB /* NWLCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C7803AA1D3C4826002107FB /* PusherKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C7803A81D3C4826002107FB /* PusherKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C7803AD1D3C4826002107FB /* PusherKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C7803A61D3C4826002107FB /* PusherKit.framework */; };
|
||||
5C7803AF1D3C4826002107FB /* PusherKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5C7803A61D3C4826002107FB /* PusherKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
5C7803B31D3C486F002107FB /* NWLCore.c in Sources */ = {isa = PBXBuildFile; fileRef = B3376A61172BB71200242EBB /* NWLCore.c */; };
|
||||
5C7803B41D3C487B002107FB /* NWHub.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A1189682D30043DA98 /* NWHub.m */; };
|
||||
5C7803B51D3C487B002107FB /* NWNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A3189682D30043DA98 /* NWNotification.m */; };
|
||||
5C7803B61D3C487B002107FB /* NWPusher.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A5189682D30043DA98 /* NWPusher.m */; };
|
||||
5C7803B71D3C487B002107FB /* NWPushFeedback.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A7189682D30043DA98 /* NWPushFeedback.m */; };
|
||||
5C7803B81D3C487B002107FB /* NWSecTools.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A9189682D30043DA98 /* NWSecTools.m */; };
|
||||
5C7803B91D3C487B002107FB /* NWSSLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232AB189682D30043DA98 /* NWSSLConnection.m */; };
|
||||
5C7803BA1D3C487B002107FB /* NWType.m in Sources */ = {isa = PBXBuildFile; fileRef = B34BF1B318DDF401004BA9F7 /* NWType.m */; };
|
||||
5C7803BB1D3C488C002107FB /* NWHub.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232A0189682D30043DA98 /* NWHub.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C7803BC1D3C488C002107FB /* NWNotification.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232A2189682D30043DA98 /* NWNotification.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C7803BD1D3C488C002107FB /* NWPusher.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232A4189682D30043DA98 /* NWPusher.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C7803BE1D3C488C002107FB /* NWPushFeedback.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232A6189682D30043DA98 /* NWPushFeedback.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C7803BF1D3C488C002107FB /* NWSecTools.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232A8189682D30043DA98 /* NWSecTools.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C7803C01D3C488C002107FB /* NWSSLConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = B3F232AA189682D30043DA98 /* NWSSLConnection.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C7803C11D3C488C002107FB /* NWType.h in Headers */ = {isa = PBXBuildFile; fileRef = B34BF1B218DDF401004BA9F7 /* NWType.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5C7803C21D3C488C002107FB /* NWLCore.h in Headers */ = {isa = PBXBuildFile; fileRef = B3376A62172BB71200242EBB /* NWLCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B3005FC318F43659009BB7C3 /* Application.xib in Resources */ = {isa = PBXBuildFile; fileRef = B3005FC218F43659009BB7C3 /* Application.xib */; };
|
||||
B3376A63172BB71200242EBB /* NWLCore.c in Sources */ = {isa = PBXBuildFile; fileRef = B3376A61172BB71200242EBB /* NWLCore.c */; };
|
||||
B3376A64172BB71200242EBB /* NWLCore.c in Sources */ = {isa = PBXBuildFile; fileRef = B3376A61172BB71200242EBB /* NWLCore.c */; };
|
||||
B33FB1C8172B185C006529CE /* icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = B33FB1BB172B185C006529CE /* icon.icns */; };
|
||||
B33FB1C9172B185C006529CE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B33FB1BC172B185C006529CE /* main.m */; };
|
||||
B33FB1CB172B185C006529CE /* NWAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B33FB1BF172B185C006529CE /* NWAppDelegate.m */; };
|
||||
@@ -26,8 +62,6 @@
|
||||
B33FB215172B303D006529CE /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B33FB211172B303D006529CE /* Icon-72@2x.png */; };
|
||||
B33FB216172B303D006529CE /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = B33FB212172B303D006529CE /* icon.png */; };
|
||||
B33FB217172B303D006529CE /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B33FB213172B303D006529CE /* Icon@2x.png */; };
|
||||
B34BF1B418DDF401004BA9F7 /* NWType.m in Sources */ = {isa = PBXBuildFile; fileRef = B34BF1B318DDF401004BA9F7 /* NWType.m */; };
|
||||
B34BF1B518DDF401004BA9F7 /* NWType.m in Sources */ = {isa = PBXBuildFile; fileRef = B34BF1B318DDF401004BA9F7 /* NWType.m */; };
|
||||
B395BA12172BC17A00631932 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = B3C6BE0015FD30E900F1F3F1 /* README.md */; };
|
||||
B3AFABEF172C81910027346A /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = B3AFABEE172C81910027346A /* config.plist */; };
|
||||
B3B4DCD318A7998300F9F258 /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = B3B4DCD218A7998300F9F258 /* LICENSE.txt */; };
|
||||
@@ -36,21 +70,57 @@
|
||||
B3C6BDD315FD27E900F1F3F1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3C6BDD215FD27E900F1F3F1 /* Security.framework */; };
|
||||
B3C6BE0115FD30E900F1F3F1 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = B3C6BE0015FD30E900F1F3F1 /* README.md */; };
|
||||
B3F23256189657DA0043DA98 /* pusher.p12 in Resources */ = {isa = PBXBuildFile; fileRef = B3F23255189657DA0043DA98 /* pusher.p12 */; };
|
||||
B3F232AE189682D30043DA98 /* NWHub.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A1189682D30043DA98 /* NWHub.m */; };
|
||||
B3F232AF189682D30043DA98 /* NWHub.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A1189682D30043DA98 /* NWHub.m */; };
|
||||
B3F232B0189682D30043DA98 /* NWNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A3189682D30043DA98 /* NWNotification.m */; };
|
||||
B3F232B1189682D30043DA98 /* NWNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A3189682D30043DA98 /* NWNotification.m */; };
|
||||
B3F232B2189682D30043DA98 /* NWPusher.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A5189682D30043DA98 /* NWPusher.m */; };
|
||||
B3F232B3189682D30043DA98 /* NWPusher.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A5189682D30043DA98 /* NWPusher.m */; };
|
||||
B3F232B4189682D30043DA98 /* NWPushFeedback.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A7189682D30043DA98 /* NWPushFeedback.m */; };
|
||||
B3F232B5189682D30043DA98 /* NWPushFeedback.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A7189682D30043DA98 /* NWPushFeedback.m */; };
|
||||
B3F232B6189682D30043DA98 /* NWSecTools.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A9189682D30043DA98 /* NWSecTools.m */; };
|
||||
B3F232B7189682D30043DA98 /* NWSecTools.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232A9189682D30043DA98 /* NWSecTools.m */; };
|
||||
B3F232B8189682D30043DA98 /* NWSSLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232AB189682D30043DA98 /* NWSSLConnection.m */; };
|
||||
B3F232B9189682D30043DA98 /* NWSSLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B3F232AB189682D30043DA98 /* NWSSLConnection.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
5C7803891D3C4668002107FB /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B3C6BD7215FD24D200F1F3F1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 5C7803831D3C4668002107FB;
|
||||
remoteInfo = "PusherKit-iOS";
|
||||
};
|
||||
5C7803AB1D3C4826002107FB /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B3C6BD7215FD24D200F1F3F1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 5C7803A51D3C4826002107FB;
|
||||
remoteInfo = "PusherKit-OSX";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
5C7803901D3C4668002107FB /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
5C78038C1D3C4668002107FB /* PusherKit.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5C7803AE1D3C4826002107FB /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
5C7803AF1D3C4826002107FB /* PusherKit.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5C7803841D3C4668002107FB /* PusherKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PusherKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5C7803861D3C4668002107FB /* PusherKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PusherKit.h; sourceTree = "<group>"; };
|
||||
5C7803871D3C4668002107FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
5C7803A61D3C4826002107FB /* PusherKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PusherKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5C7803A81D3C4826002107FB /* PusherKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PusherKit.h; sourceTree = "<group>"; };
|
||||
5C7803A91D3C4826002107FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B3005FC218F43659009BB7C3 /* Application.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Application.xib; sourceTree = "<group>"; };
|
||||
B3376A61172BB71200242EBB /* NWLCore.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = NWLCore.c; path = Mac/NWLCore.c; sourceTree = SOURCE_ROOT; };
|
||||
B3376A62172BB71200242EBB /* NWLCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NWLCore.h; path = Mac/NWLCore.h; sourceTree = SOURCE_ROOT; };
|
||||
@@ -77,8 +147,10 @@
|
||||
B33FB211172B303D006529CE /* Icon-72@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72@2x.png"; sourceTree = "<group>"; };
|
||||
B33FB212172B303D006529CE /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = "<group>"; };
|
||||
B33FB213172B303D006529CE /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = "<group>"; };
|
||||
B343578419FC360B007A67C1 /* AppledocSettings.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = AppledocSettings.plist; sourceTree = "<group>"; };
|
||||
B34BF1B218DDF401004BA9F7 /* NWType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NWType.h; sourceTree = "<group>"; };
|
||||
B34BF1B318DDF401004BA9F7 /* NWType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NWType.m; sourceTree = "<group>"; };
|
||||
B360423219F4315C00B0E97D /* CHANGLOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGLOG.md; sourceTree = "<group>"; };
|
||||
B3AFABEE172C81910027346A /* config.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = "<group>"; };
|
||||
B3B4DCD218A7998300F9F258 /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; };
|
||||
B3C6BD7B15FD24D200F1F3F1 /* Pusher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pusher.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -104,11 +176,26 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
5C7803801D3C4668002107FB /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5C7803A21D3C4826002107FB /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B33FB1CE172B1A66006529CE /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B33FB20F172B1EC2006529CE /* Security.framework in Frameworks */,
|
||||
5C78038B1D3C4668002107FB /* PusherKit.framework in Frameworks */,
|
||||
B33FB204172B1BAD006529CE /* CoreGraphics.framework in Frameworks */,
|
||||
B33FB202172B1BA5006529CE /* Foundation.framework in Frameworks */,
|
||||
B33FB200172B1B9F006529CE /* UIKit.framework in Frameworks */,
|
||||
@@ -121,12 +208,31 @@
|
||||
files = (
|
||||
B3C6BDD315FD27E900F1F3F1 /* Security.framework in Frameworks */,
|
||||
B3C6BD8015FD24D200F1F3F1 /* Cocoa.framework in Frameworks */,
|
||||
5C7803AD1D3C4826002107FB /* PusherKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
5C7803851D3C4668002107FB /* PusherKit-iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C7803861D3C4668002107FB /* PusherKit.h */,
|
||||
5C7803871D3C4668002107FB /* Info.plist */,
|
||||
);
|
||||
path = "PusherKit-iOS";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5C7803A71D3C4826002107FB /* PusherKit-OSX */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C7803A81D3C4826002107FB /* PusherKit.h */,
|
||||
5C7803A91D3C4826002107FB /* Info.plist */,
|
||||
);
|
||||
path = "PusherKit-OSX";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B3376A5F172B9EFD00242EBB /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -184,11 +290,15 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B3C6BE0015FD30E900F1F3F1 /* README.md */,
|
||||
B360423219F4315C00B0E97D /* CHANGLOG.md */,
|
||||
B3B4DCD218A7998300F9F258 /* LICENSE.txt */,
|
||||
B3F232991896766B0043DA98 /* NWPusher.podspec */,
|
||||
B343578419FC360B007A67C1 /* AppledocSettings.plist */,
|
||||
B3F2329D189682D30043DA98 /* Classes */,
|
||||
B33FB1BA172B185C006529CE /* Mac */,
|
||||
B33FB1ED172B1A7A006529CE /* Touch */,
|
||||
5C7803851D3C4668002107FB /* PusherKit-iOS */,
|
||||
5C7803A71D3C4826002107FB /* PusherKit-OSX */,
|
||||
B3C6BD7E15FD24D200F1F3F1 /* Frameworks */,
|
||||
B3C6BD7C15FD24D200F1F3F1 /* Products */,
|
||||
);
|
||||
@@ -199,6 +309,8 @@
|
||||
children = (
|
||||
B3C6BD7B15FD24D200F1F3F1 /* Pusher.app */,
|
||||
B33FB1D1172B1A66006529CE /* PusherTouch.app */,
|
||||
5C7803841D3C4668002107FB /* PusherKit.framework */,
|
||||
5C7803A61D3C4826002107FB /* PusherKit.framework */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -241,7 +353,78 @@
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
5C7803811D3C4668002107FB /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C7803881D3C4668002107FB /* PusherKit.h in Headers */,
|
||||
5C7803991D3C4749002107FB /* NWHub.h in Headers */,
|
||||
5C78039A1D3C4749002107FB /* NWNotification.h in Headers */,
|
||||
5C78039B1D3C4749002107FB /* NWPusher.h in Headers */,
|
||||
5C78039C1D3C4749002107FB /* NWPushFeedback.h in Headers */,
|
||||
5C78039D1D3C4749002107FB /* NWSecTools.h in Headers */,
|
||||
5C78039E1D3C4749002107FB /* NWSSLConnection.h in Headers */,
|
||||
5C78039F1D3C4749002107FB /* NWType.h in Headers */,
|
||||
5C7803A01D3C4749002107FB /* NWLCore.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5C7803A31D3C4826002107FB /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C7803AA1D3C4826002107FB /* PusherKit.h in Headers */,
|
||||
5C7803BB1D3C488C002107FB /* NWHub.h in Headers */,
|
||||
5C7803BC1D3C488C002107FB /* NWNotification.h in Headers */,
|
||||
5C7803BD1D3C488C002107FB /* NWPusher.h in Headers */,
|
||||
5C7803BE1D3C488C002107FB /* NWPushFeedback.h in Headers */,
|
||||
5C7803BF1D3C488C002107FB /* NWSecTools.h in Headers */,
|
||||
5C7803C01D3C488C002107FB /* NWSSLConnection.h in Headers */,
|
||||
5C7803C11D3C488C002107FB /* NWType.h in Headers */,
|
||||
5C7803C21D3C488C002107FB /* NWLCore.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
5C7803831D3C4668002107FB /* PusherKit-iOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5C78038F1D3C4668002107FB /* Build configuration list for PBXNativeTarget "PusherKit-iOS" */;
|
||||
buildPhases = (
|
||||
5C78037F1D3C4668002107FB /* Sources */,
|
||||
5C7803801D3C4668002107FB /* Frameworks */,
|
||||
5C7803811D3C4668002107FB /* Headers */,
|
||||
5C7803821D3C4668002107FB /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "PusherKit-iOS";
|
||||
productName = "PusherKit-iOS";
|
||||
productReference = 5C7803841D3C4668002107FB /* PusherKit.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
5C7803A51D3C4826002107FB /* PusherKit-OSX */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5C7803B01D3C4826002107FB /* Build configuration list for PBXNativeTarget "PusherKit-OSX" */;
|
||||
buildPhases = (
|
||||
5C7803A11D3C4826002107FB /* Sources */,
|
||||
5C7803A21D3C4826002107FB /* Frameworks */,
|
||||
5C7803A31D3C4826002107FB /* Headers */,
|
||||
5C7803A41D3C4826002107FB /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "PusherKit-OSX";
|
||||
productName = "PusherKit-OSX";
|
||||
productReference = 5C7803A61D3C4826002107FB /* PusherKit.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
B33FB1D0172B1A66006529CE /* PusherTouch */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = B33FB1EA172B1A66006529CE /* Build configuration list for PBXNativeTarget "PusherTouch" */;
|
||||
@@ -249,10 +432,12 @@
|
||||
B33FB1CD172B1A66006529CE /* Sources */,
|
||||
B33FB1CE172B1A66006529CE /* Frameworks */,
|
||||
B33FB1CF172B1A66006529CE /* Resources */,
|
||||
5C7803901D3C4668002107FB /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
5C78038A1D3C4668002107FB /* PBXTargetDependency */,
|
||||
);
|
||||
name = PusherTouch;
|
||||
productName = PusherTouch;
|
||||
@@ -266,10 +451,12 @@
|
||||
B3C6BD7715FD24D200F1F3F1 /* Sources */,
|
||||
B3C6BD7815FD24D200F1F3F1 /* Frameworks */,
|
||||
B3C6BD7915FD24D200F1F3F1 /* Resources */,
|
||||
5C7803AE1D3C4826002107FB /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
5C7803AC1D3C4826002107FB /* PBXTargetDependency */,
|
||||
);
|
||||
name = PusherMac;
|
||||
productName = Pusher;
|
||||
@@ -283,8 +470,18 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
CLASSPREFIX = NW;
|
||||
LastUpgradeCheck = 0500;
|
||||
LastUpgradeCheck = 0800;
|
||||
ORGANIZATIONNAME = noodlewerk;
|
||||
TargetAttributes = {
|
||||
5C7803831D3C4668002107FB = {
|
||||
CreatedOnToolsVersion = 8.0;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
5C7803A51D3C4826002107FB = {
|
||||
CreatedOnToolsVersion = 8.0;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = B3C6BD7515FD24D200F1F3F1 /* Build configuration list for PBXProject "NWPusher" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
@@ -300,11 +497,27 @@
|
||||
targets = (
|
||||
B3C6BD7A15FD24D200F1F3F1 /* PusherMac */,
|
||||
B33FB1D0172B1A66006529CE /* PusherTouch */,
|
||||
5C7803831D3C4668002107FB /* PusherKit-iOS */,
|
||||
5C7803A51D3C4826002107FB /* PusherKit-OSX */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
5C7803821D3C4668002107FB /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5C7803A41D3C4826002107FB /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B33FB1CF172B1A66006529CE /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -337,20 +550,42 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
5C78037F1D3C4668002107FB /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C7803951D3C4683002107FB /* NWSecTools.m in Sources */,
|
||||
5C7803961D3C4683002107FB /* NWSSLConnection.m in Sources */,
|
||||
5C7803911D3C4683002107FB /* NWHub.m in Sources */,
|
||||
5C7803941D3C4683002107FB /* NWPushFeedback.m in Sources */,
|
||||
5C7803981D3C4698002107FB /* NWLCore.c in Sources */,
|
||||
5C7803921D3C4683002107FB /* NWNotification.m in Sources */,
|
||||
5C7803971D3C4683002107FB /* NWType.m in Sources */,
|
||||
5C7803931D3C4683002107FB /* NWPusher.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5C7803A11D3C4826002107FB /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C7803B81D3C487B002107FB /* NWSecTools.m in Sources */,
|
||||
5C7803B41D3C487B002107FB /* NWHub.m in Sources */,
|
||||
5C7803BA1D3C487B002107FB /* NWType.m in Sources */,
|
||||
5C7803B71D3C487B002107FB /* NWPushFeedback.m in Sources */,
|
||||
5C7803B61D3C487B002107FB /* NWPusher.m in Sources */,
|
||||
5C7803B51D3C487B002107FB /* NWNotification.m in Sources */,
|
||||
5C7803B31D3C486F002107FB /* NWLCore.c in Sources */,
|
||||
5C7803B91D3C487B002107FB /* NWSSLConnection.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B33FB1CD172B1A66006529CE /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B33FB1FC172B1A7A006529CE /* main.m in Sources */,
|
||||
B3F232B5189682D30043DA98 /* NWPushFeedback.m in Sources */,
|
||||
B33FB1FD172B1A7A006529CE /* NWAppDelegate.m in Sources */,
|
||||
B34BF1B518DDF401004BA9F7 /* NWType.m in Sources */,
|
||||
B3F232AF189682D30043DA98 /* NWHub.m in Sources */,
|
||||
B3F232B7189682D30043DA98 /* NWSecTools.m in Sources */,
|
||||
B3F232B3189682D30043DA98 /* NWPusher.m in Sources */,
|
||||
B3F232B9189682D30043DA98 /* NWSSLConnection.m in Sources */,
|
||||
B3F232B1189682D30043DA98 /* NWNotification.m in Sources */,
|
||||
B3376A64172BB71200242EBB /* NWLCore.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -358,22 +593,207 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B3F232B4189682D30043DA98 /* NWPushFeedback.m in Sources */,
|
||||
B3F232AE189682D30043DA98 /* NWHub.m in Sources */,
|
||||
B3F232B6189682D30043DA98 /* NWSecTools.m in Sources */,
|
||||
B34BF1B418DDF401004BA9F7 /* NWType.m in Sources */,
|
||||
B3F232B2189682D30043DA98 /* NWPusher.m in Sources */,
|
||||
B3F232B8189682D30043DA98 /* NWSSLConnection.m in Sources */,
|
||||
B3F232B0189682D30043DA98 /* NWNotification.m in Sources */,
|
||||
B33FB1C9172B185C006529CE /* main.m in Sources */,
|
||||
B33FB1CB172B185C006529CE /* NWAppDelegate.m in Sources */,
|
||||
B3376A63172BB71200242EBB /* NWLCore.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
5C78038A1D3C4668002107FB /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 5C7803831D3C4668002107FB /* PusherKit-iOS */;
|
||||
targetProxy = 5C7803891D3C4668002107FB /* PBXContainerItemProxy */;
|
||||
};
|
||||
5C7803AC1D3C4826002107FB /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 5C7803A51D3C4826002107FB /* PusherKit-OSX */;
|
||||
targetProxy = 5C7803AB1D3C4826002107FB /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
5C78038D1D3C4668002107FB /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
INFOPLIST_FILE = "PusherKit-iOS/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.zats.PusherKit-iOS";
|
||||
PRODUCT_NAME = PusherKit;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
5C78038E1D3C4668002107FB /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
INFOPLIST_FILE = "PusherKit-iOS/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.zats.PusherKit-iOS";
|
||||
PRODUCT_NAME = PusherKit;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
5C7803B11D3C4826002107FB /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
INFOPLIST_FILE = "PusherKit-OSX/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.zats.PusherKit-OSX";
|
||||
PRODUCT_NAME = PusherKit;
|
||||
SKIP_INSTALL = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
5C7803B21D3C4826002107FB /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
INFOPLIST_FILE = "PusherKit-OSX/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.zats.PusherKit-OSX";
|
||||
PRODUCT_NAME = PusherKit;
|
||||
SKIP_INSTALL = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
B33FB1EB172B1A66006529CE /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -394,7 +814,9 @@
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = "Touch/PusherTouch-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 6.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.noodlewerk.pusher;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -419,8 +841,10 @@
|
||||
GCC_PREFIX_HEADER = "Touch/PusherTouch-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "NWL_LIB=Pusher";
|
||||
INFOPLIST_FILE = "Touch/PusherTouch-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 6.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.noodlewerk.pusher;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -435,11 +859,22 @@
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
@@ -448,7 +883,9 @@
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
@@ -462,14 +899,26 @@
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
SDKROOT = macosx;
|
||||
@@ -479,6 +928,7 @@
|
||||
B3C6BD9A15FD24D200F1F3F1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Mac/PusherMac-Prefix.pch";
|
||||
@@ -487,7 +937,9 @@
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = "Mac/PusherMac-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.noodlewerk.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = Pusher;
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
@@ -496,6 +948,7 @@
|
||||
B3C6BD9B15FD24D200F1F3F1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Mac/PusherMac-Prefix.pch";
|
||||
@@ -504,7 +957,9 @@
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = "Mac/PusherMac-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.noodlewerk.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = Pusher;
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
@@ -513,6 +968,24 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
5C78038F1D3C4668002107FB /* Build configuration list for PBXNativeTarget "PusherKit-iOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
5C78038D1D3C4668002107FB /* Debug */,
|
||||
5C78038E1D3C4668002107FB /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
5C7803B01D3C4826002107FB /* Build configuration list for PBXNativeTarget "PusherKit-OSX" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
5C7803B11D3C4826002107FB /* Debug */,
|
||||
5C7803B21D3C4826002107FB /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
B33FB1EA172B1A66006529CE /* Build configuration list for PBXNativeTarget "PusherTouch" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5C7803A51D3C4826002107FB"
|
||||
BuildableName = "PusherKit.framework"
|
||||
BlueprintName = "PusherKit-OSX"
|
||||
ReferencedContainer = "container:NWPusher.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5C7803A51D3C4826002107FB"
|
||||
BuildableName = "PusherKit.framework"
|
||||
BlueprintName = "PusherKit-OSX"
|
||||
ReferencedContainer = "container:NWPusher.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5C7803A51D3C4826002107FB"
|
||||
BuildableName = "PusherKit.framework"
|
||||
BlueprintName = "PusherKit-OSX"
|
||||
ReferencedContainer = "container:NWPusher.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5C7803831D3C4668002107FB"
|
||||
BuildableName = "PusherKit.framework"
|
||||
BlueprintName = "PusherKit-iOS"
|
||||
ReferencedContainer = "container:NWPusher.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5C7803831D3C4668002107FB"
|
||||
BuildableName = "PusherKit.framework"
|
||||
BlueprintName = "PusherKit-iOS"
|
||||
ReferencedContainer = "container:NWPusher.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5C7803831D3C4668002107FB"
|
||||
BuildableName = "PusherKit.framework"
|
||||
BlueprintName = "PusherKit-iOS"
|
||||
ReferencedContainer = "container:NWPusher.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2016 noodlewerk. All rights reserved.</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// PusherKit-OSX.h
|
||||
// PusherKit-OSX
|
||||
//
|
||||
// Created by Sash Zats on 7/17/16.
|
||||
// Copyright © 2016 noodlewerk. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
//! Project version number for PusherKit-OSX.
|
||||
FOUNDATION_EXPORT double PusherKit_OSXVersionNumber;
|
||||
|
||||
//! Project version string for PusherKit-OSX.
|
||||
FOUNDATION_EXPORT const unsigned char PusherKit_OSXVersionString[];
|
||||
|
||||
#import <PusherKit/NWHub.h>
|
||||
#import <PusherKit/NWLCore.h>
|
||||
#import <PusherKit/NWNotification.h>
|
||||
#import <PusherKit/NWPushFeedback.h>
|
||||
#import <PusherKit/NWPusher.h>
|
||||
#import <PusherKit/NWSSLConnection.h>
|
||||
#import <PusherKit/NWSecTools.h>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// PusherKit-iOS.h
|
||||
// PusherKit-iOS
|
||||
//
|
||||
// Created by Sash Zats on 7/17/16.
|
||||
// Copyright © 2016 noodlewerk. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for PusherKit-iOS.
|
||||
FOUNDATION_EXPORT double PusherKit_iOSVersionNumber;
|
||||
|
||||
//! Project version string for PusherKit-iOS.
|
||||
FOUNDATION_EXPORT const unsigned char PusherKit_iOSVersionString[];
|
||||
|
||||
#import <PusherKit/NWHub.h>
|
||||
#import <PusherKit/NWLCore.h>
|
||||
#import <PusherKit/NWNotification.h>
|
||||
#import <PusherKit/NWPusher.h>
|
||||
#import <PusherKit/NWSSLConnection.h>
|
||||
#import <PusherKit/NWSecTools.h>
|
||||
#import <PusherKit/NWPushFeedback.h>
|
||||
@@ -1,16 +1,17 @@
|
||||

|
||||
<img src="icon.png" alt="Pusher Icon" width="72"/>
|
||||
|
||||
|
||||
Pusher
|
||||
======
|
||||
|
||||
*iOS/OS X application and library for playing with the Apple Push Notification Service (APNS).*
|
||||
*OS X and iOS application and framework to play with the Apple Push Notification service (APNs)*
|
||||
|
||||
<img src="Docs/osx.png" alt="Pusher OS X" width="591"/>
|
||||
<img src="Docs/osx1.png" alt="Pusher OS X" width="612"/>
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
Install the Mac app using [Homebrew cask](https://github.com/phinze/homebrew-cask):
|
||||
Install the Mac app using [Homebrew cask](https://github.com/caskroom/homebrew-cask):
|
||||
|
||||
```shell
|
||||
brew cask install pusher
|
||||
@@ -20,10 +21,16 @@ Or download the latest `Pusher.app` binary:
|
||||
|
||||
- [Download latest binary](https://github.com/noodlewerk/NWPusher/releases/latest)
|
||||
|
||||
Alternatively, you can include NWPusher as a library, using [CocoaPods](http://cocoapods.org/):
|
||||
Alternatively, you can include NWPusher as a framework, using [CocoaPods](https://cocoapods.org/):
|
||||
|
||||
```ruby
|
||||
pod 'NWPusher', '~> 0.5.1'
|
||||
pod 'NWPusher', '~> 0.7.0'
|
||||
```
|
||||
|
||||
or [Carthage](https://github.com/Carthage/Carthage) (iOS 8+ is required to use Cocoa Touch Frameworks)
|
||||
|
||||
```
|
||||
github "noodlewerk/NWPusher"
|
||||
```
|
||||
|
||||
Or simply include the source files you need. NWPusher has a modular architecture and does not have any external dependencies, so use what you like.
|
||||
@@ -31,11 +38,11 @@ Or simply include the source files you need. NWPusher has a modular architecture
|
||||
|
||||
About
|
||||
-----
|
||||
Testing push notifications for your iOS app can be a pain. You might consider setting up your own server or use one of the many push webservices online. Either way it's a lot of work to get all these systems connected properly.
|
||||
Testing push notifications for your iOS or Mac app can be a pain. You might consider setting up your own server or use one of the many push webservices online. Either way it's a lot of work to get all these systems connected properly. When it is all working properly, push notifications come in fast (< 1 sec) and reliably. However when nothing comes in, it can be very hard to find out why.
|
||||
|
||||
Enter *Pusher*, a Mac and iPhone app for sending push notifications *directly* to the *Apple Push Notification Service*. No need to set up a server or create an account online. You only need the SSL certificate and a device token to start pushing directly from your Mac, or even from an iPhone!
|
||||
That's why I made *Pusher*. It is a Mac and iPhone app for sending push notifications *directly* to the *Apple Push Notification Service*. No need to set up a server or create an account online. You only need the SSL certificate and a device token to start pushing directly from your Mac, or even from an iPhone! Pusher has detailed error reporting and logs, which are very helpful with verifying your setup.
|
||||
|
||||
Pusher comes with a small library for both OS X and iOS, that provides various tools for sending notifications programmatically. On OS X it can use the keychain to retrieve push certificates and keys. Pusher can also be used without keychain, using a PKCS #12 file.
|
||||
Pusher comes with a small framework for both OS X and iOS. It provides various tools for sending notifications programmatically. On OS X it can use the keychain to retrieve push certificates and keys. Pusher can also be used without keychain, using a PKCS #12 file. If you want to get a better understanding of how push notifications work, then this framework is a good place to start and play around.
|
||||
|
||||
|
||||
Features
|
||||
@@ -47,11 +54,12 @@ Mac OS X application for sending push notifications through the APN service:
|
||||
- *Stores device tokens* so you don't have to copy-paste them every time
|
||||
- Handles *PKCS #12* files (.p12)
|
||||
- Automatic configuration for *sandbox*
|
||||
- Reports *detailed error messages* returned by APNS
|
||||
- Reports *detailed error messages* returned by APNs
|
||||
- Reads from *feedback service*
|
||||
|
||||
OS X/iOS library for sending pushes from your own application:
|
||||
OS X and iOS framework for sending pushes from your own application:
|
||||
- Modular, no dependencies, use what you like
|
||||
- Fully documented source code
|
||||
- Detailed error handling
|
||||
- iOS compatible, so you can also push directly from your iPhone :o
|
||||
- Demo applications for both platforms
|
||||
@@ -61,10 +69,10 @@ Getting started
|
||||
---------------
|
||||
Before you can start sending push notification payloads, there are a few hurdles to take. First you'll need to obtain the *Apple Push Services SSL Certificate* of the app you want to send notifications to. This certificate is used by Pusher to set up the SSL connection through which the payloads will be sent to Apple.
|
||||
|
||||
Second you'll need the *device token* of the device you want to send your payload to. Every device has its own unique token that can only be obtained from within the app. While this might sound very complicated, it all comes down to just a few clicks on Apple's Dev Center website, some gray hairs, and a bit of patience.
|
||||
Second you'll need the *device token* of the device you want to send your payload to. Every device has its own unique token that can only be obtained from within the app. It's a bit complicated, but in the end it all comes down to just a few clicks on Apple's Dev Center website, some gray hairs, and a bit of patience.
|
||||
|
||||
### Certificate
|
||||
Let's start with the SSL certificate. The goal is to get both the certificate *and* the private key into your OS X keychain. If someone else already generated this certificate, you'll need to ask him or her to export these into a PKCS12 file. If there is no certificate generated yet, you can generate the certificate and the private key in the following steps:
|
||||
Let's start with the SSL certificate. The goal is to get both the certificate *and* the private key into your OS X keychain. If someone else already generated this certificate, you'll need to ask for exporting these into a PKCS12 file. If there is no certificate generated yet, you can generate the certificate and the private key in the following steps:
|
||||
|
||||
1. Log in to [Apple's Dev Center](https://developer.apple.com)
|
||||
2. Go to the *Provisioning Portal* or *Certificates, Identifiers & Profiles*
|
||||
@@ -73,29 +81,48 @@ Let's start with the SSL certificate. The goal is to get both the certificate *a
|
||||
|
||||
Keep in mind that you will eventually be downloading a certificate, which you will need to install in your keychain together with the private key. This should look something like this:
|
||||
|
||||
<img src="Docs/keychain1.png" alt="Keychain export" width="681"/>
|
||||
<img src="Docs/keychain1.png" alt="Keychain export" width="690"/>
|
||||
|
||||
Both can be exported into a PKCS12 file, which allows you to share these with fellow developers:
|
||||
NB: There is `Development` and `Production` certificates, which should (generally) correspond to respectively `DEBUG` and `RELEASE` versions of your app. Make sure you get the right one, check *Development (sandbox) or Production*, *iOS or Mac*, and the *bundle identifier*.
|
||||
|
||||
<img src="Docs/keychain2.png" alt="PKCS12 file" width="679"/>
|
||||
The push certificate should be exported to a PKCS12 file, which allows you to share these with fellow developers:
|
||||
|
||||
<img src="Docs/keychain2.png" alt="PKCS12 file" width="690"/>
|
||||
|
||||
### Device token
|
||||
Now you need to obtain a device token, which is a 64 character hex string (256 bits indeed). This should be done from within the iOS app you're going to push to. Add the following lines to the application delegate:
|
||||
Now you need to obtain a device token, which is a 64 character hex string (256 bits). This should be done from within the iOS app you're going to push to. Add the following lines to the application delegate (Xcode 6 required):
|
||||
|
||||
```objective-c
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
NSLog(@"Registering device for push notifications...");
|
||||
[UIApplication.sharedApplication registerForRemoteNotificationTypes:
|
||||
UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge
|
||||
| UIRemoteNotificationTypeSound];
|
||||
if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
|
||||
NSLog(@"Requesting permission for push notifications..."); // iOS 8
|
||||
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:
|
||||
UIUserNotificationTypeAlert | UIUserNotificationTypeBadge |
|
||||
UIUserNotificationTypeSound categories:nil];
|
||||
[UIApplication.sharedApplication registerUserNotificationSettings:settings];
|
||||
} else {
|
||||
NSLog(@"Registering device for push notifications..."); // iOS 7 and earlier
|
||||
[UIApplication.sharedApplication registerForRemoteNotificationTypes:
|
||||
UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge |
|
||||
UIRemoteNotificationTypeSound];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application
|
||||
didRegisterUserNotificationSettings:(UIUserNotificationSettings *)settings
|
||||
{
|
||||
NSLog(@"Registering device for push notifications..."); // iOS 8
|
||||
[application registerForRemoteNotifications];
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application
|
||||
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token
|
||||
{
|
||||
NSLog(@"Registration successful, device token: %@", token);
|
||||
NSLog(@"Registration successful, bundle identifier: %@, mode: %@, device token: %@",
|
||||
[NSBundle.mainBundle bundleIdentifier], [self modeString], token);
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application
|
||||
@@ -104,25 +131,43 @@ Now you need to obtain a device token, which is a 64 character hex string (256 b
|
||||
NSLog(@"Failed to register: %@", error);
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier
|
||||
forRemoteNotification:(NSDictionary *)notification completionHandler:(void(^)())completionHandler
|
||||
{
|
||||
NSLog(@"Received push notification: %@, identifier: %@", notification, identifier); // iOS 8
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application
|
||||
didReceiveRemoteNotification:(NSDictionary *)notification
|
||||
{
|
||||
NSLog(@"Received push notification: %@", notification);
|
||||
NSLog(@"Received push notification: %@", notification); // iOS 7 and earlier
|
||||
}
|
||||
|
||||
- (NSString *)modeString
|
||||
{
|
||||
#if DEBUG
|
||||
return @"Development (sandbox)";
|
||||
#else
|
||||
return @"Production";
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
Now, when you run the application, the 64 character push string will be logged to the console.
|
||||
|
||||
### Push from OS X
|
||||
With the SSL certificate and private key in the keychain and the device token on the pasteboard, you're (finally) ready to send some push notifications. Let's start by sending a notification using the Pusher OS X app. Open the Pusher Xcode project and run the PusherMac target:
|
||||
With the SSL certificate and private key in the keychain and the device token on the pasteboard, you're ready to send some push notifications. Let's start by sending a notification using the *Pusher app for Mac OS X*. Open the Pusher Xcode project and run the PusherMac target:
|
||||
|
||||
<img src="Docs/osx.png" alt="Pusher OS X" width="591"/>
|
||||
<img src="Docs/osx1.png" alt="Pusher OS X" width="612"/>
|
||||
|
||||
The combo box at the top lists the available SSL certificates in the keychain. Select the certificate you want to use and paste the device token of the device you're pushing to. The text field below shows the JSON formatted payload text that you're sending. Read more about this format in the Apple documentation under *Apple Push Notification Service*.
|
||||
|
||||
Now before you press *Push*, make sure the application you're *sending to* is in the *background*, e.g. by pressing the home button. This way you're sure the app is not going to interfere with the message, yet. Press push, wait a few seconds, and see.
|
||||
Now before you press *Push*, make sure the application you're *sending to* is in the *background*, e.g. by pressing the home button. This way you're sure the app is not going to interfere with the message, yet. Press push, wait a few seconds, and see the notification coming in.
|
||||
|
||||
If things are not working as expected, send me a message on GitHub or post an issue.
|
||||
If things are not working as expected, then take a look at the *Troubleshooting* section below.
|
||||
|
||||
<img src="Docs/osx2.png" alt="Pusher OS X" width="612"/>
|
||||
|
||||
### Push from iOS
|
||||
The ultimate experience is of course pushing from an iPhone to an iPhone, directly. This can be done with the Pusher iOS app. Before you run the PusherTouch target, make sure to include the *certificate, private key, and device token* inside the app. Take the PKCS12 file that you exported earlier and include it in the PusherTouch bundle. Then go to `NWAppDelegate.m` in the `Touch` folder and configure `pkcs12FileName`, `pkcs12Password`, and `deviceToken`. Now run the PusherTouch target:
|
||||
@@ -131,21 +176,21 @@ The ultimate experience is of course pushing from an iPhone to an iPhone, direct
|
||||
|
||||
If everything is set up correctly, you only need to *Connect* and *Push*. Then you should receive the `Testing..` push message on the device.
|
||||
|
||||
Again, if things are not working as expected, send me a message on GitHub or post an issue.
|
||||
Again, if things are not working as expected, take a look at the *Troubleshooting* section below or post an issue on GitHub.
|
||||
|
||||
Consult Apple's documentation for more info on the APNS architecture: [Apple Push Notification Service](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html)
|
||||
Consult Apple's documentation for more info on the APNs architecture: [Apple Push Notification Service](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html)
|
||||
|
||||
Pushing from code
|
||||
-----------------
|
||||
Pusher can also be used as a library to send notifications programmatically. The included Xcode project provides examples for both OS X and iOS. The easiest way to include NWPusher is through CocoaPods:
|
||||
Pusher can also be used as a framework to send notifications programmatically. The included Xcode project provides examples for both OS X and iOS. The easiest way to include NWPusher is through CocoaPods:
|
||||
|
||||
```ruby
|
||||
pod 'NWPusher', '~> 0.5.1'
|
||||
pod 'NWPusher', '~> 0.7.0'
|
||||
```
|
||||
|
||||
Alternatively you can include just the files you need from the `Classes` folder. Make sure you link with `Foundation.framework` and `Security.framework`.
|
||||
CocoaPods also compiles documentation, which can be accessed through [CocoaDocs](http://cocoadocs.org/docsets/NWPusher). Alternatively you can include just the files you need from the `Classes` folder. Make sure you link with `Foundation.framework` and `Security.framework`.
|
||||
|
||||
Before any notification can be sent, you first need to create a connection. When this connection is established, any number of payload can be sent.
|
||||
Before any notification can be sent, you first need to create a connection. When this connection is established, any number of payloads can be sent.
|
||||
|
||||
*Note that Apple doesn't like it when you create a connection for every push.* Therefore be careful to reuse a connection as much as possible in order to prevent Apple from blocking.
|
||||
|
||||
@@ -154,10 +199,12 @@ To create a connection directly from a PKCS12 (.p12) file:
|
||||
```objective-c
|
||||
NSURL *url = [NSBundle.mainBundle URLForResource:@"pusher.p12" withExtension:nil];
|
||||
NSData *pkcs12 = [NSData dataWithContentsOfURL:url];
|
||||
NWPusher *pusher = [[NWPusher alloc] init];
|
||||
NWError connect = [pusher connectWithPKCS12Data:pkcs12 password:@"pa$$word"];
|
||||
if (connect != kNWSuccess) {
|
||||
NSLog(@"Unable to connect: %@", [NWErrorUtil stringWithError:connect]);
|
||||
NSError *error = nil;
|
||||
NWPusher *pusher = [NWPusher connectWithPKCS12Data:pkcs12 password:@"pa$$word" error:&error];
|
||||
if (pusher) {
|
||||
NSLog(@"Connected to APNs");
|
||||
} else {
|
||||
NSLog(@"Unable to connect: %@", error);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -166,9 +213,12 @@ When pusher is successfully connected, send a payload to your device:
|
||||
```objective-c
|
||||
NSString *payload = @"{\"aps\":{\"alert\":\"Testing..\"}}";
|
||||
NSString *token = @"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
|
||||
NWError push = [pusher pushPayload:payload token:token identifier:rand()];
|
||||
if (push != kNWSuccess) {
|
||||
NSLog(@"Unable to sent: %@", [NWErrorUtil stringWithError:push]);
|
||||
NSError *error = nil;
|
||||
BOOL pushed = [pusher pushPayload:payload token:token identifier:rand() error:&error];
|
||||
if (pushed) {
|
||||
NSLog(@"Pushed to APNs");
|
||||
} else {
|
||||
NSLog(@"Unable to push: %@", error);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -176,32 +226,39 @@ After a second or so, we can take a look to see if the notification was accepted
|
||||
|
||||
```objective-c
|
||||
NSUInteger identifier = 0;
|
||||
NWError apnError = kNWSuccess;
|
||||
NWError fetch = [pusher fetchFailedIdentifier:&identifier apnError:apnError];
|
||||
if (fetch != kNWSuccess) {
|
||||
NSLog(@"Unable to read response: %@", [NWErrorUtil stringWithError:push]);
|
||||
} else if (apnError != kNWSuccess) {
|
||||
NSLog(@"Notification with identifier %i rejected: %@", (int)identifier, [NWErrorUtil stringWithError:apnError]);
|
||||
NSError *apnError = nil;
|
||||
NSError *error = nil;
|
||||
BOOL read = [pusher readFailedIdentifier:&identifier apnError:&apnError error:&error];
|
||||
if (read && apnError) {
|
||||
NSLog(@"Notification with identifier %i rejected: %@", (int)identifier, apnError);
|
||||
} else if (read) {
|
||||
NSLog(@"Read and none failed");
|
||||
} else {
|
||||
NSLog(@"Unable to read: %@", error);
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively on OS X you can also use the keychain to obtain the SSL certificate. In that case first collect all certificates:
|
||||
|
||||
```objective-c
|
||||
NSArray *certificates = nil;
|
||||
NWError keychain = [NWSecTools keychainCertificates:&certificates];
|
||||
if (keychain != kNWSuccess) {
|
||||
NSLog(@"Unable to access keychain: %@", [NWErrorUtil stringWithError:keychain]);
|
||||
NSError *error = nil;
|
||||
NSArray *certificates = [NWSecTools keychainCertificatesWithError:&error];
|
||||
if (certificates) {
|
||||
NSLog(@"Loaded %i certificates", (int)certificates.count);
|
||||
} else {
|
||||
NSLog(@"Unable to access keychain: %@", error);
|
||||
}
|
||||
```
|
||||
|
||||
After selecting the right certificate, obtain the identity from the keychain:
|
||||
|
||||
```objective-c
|
||||
NWIdentityRef identity = nil;
|
||||
NWError ident = [NWSecTools keychainIdentityWithCertificate:certificate identity:&identity];
|
||||
if (ident != kNWSuccess) {
|
||||
NSLog(@"Unable to create identity: %@", [NWErrorUtil stringWithError:ident]);
|
||||
NSError *error = nil;
|
||||
NWIdentityRef identity = [NWSecTools keychainIdentityWithCertificate:certificate error:&error];
|
||||
if (identity) {
|
||||
NSLog(@"Loaded identity: %@", [NWSecTools inspectIdentity:identity]);
|
||||
} else {
|
||||
NSLog(@"Unable to create identity: %@", error);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -211,53 +268,124 @@ Consult Apple's documentation for more info on the client-server communication:
|
||||
|
||||
Feedback Service
|
||||
----------------
|
||||
The feedback service is part of the Apple Push Notification Service. The feedback service is basically a list containing device tokens that became invalid. Apple recommends that you read from the feedback service once every 24 hours, and no longer send notifications to listed devices. Communication with the feedback service can be done with the `NWPushFeedback` class. First connect using one of the `-connect*` methods:
|
||||
The feedback service is part of the Apple Push Notification service. The feedback service is basically a list containing device tokens that became invalid. Apple recommends that you read from the feedback service once every 24 hours, and no longer send notifications to listed devices. Note that this can be used to find out who removed your app from their device.
|
||||
|
||||
Communication with the feedback service can be done with the `NWPushFeedback` class. First connect using one of the `connect` methods:
|
||||
|
||||
```objective-c
|
||||
NSURL *url = [NSBundle.mainBundle URLForResource:@"pusher.p12" withExtension:nil];
|
||||
NSData *pkcs12 = [NSData dataWithContentsOfURL:url];
|
||||
NWPushFeedback *feedback = [[NWPushFeedback alloc] init];
|
||||
NWError connect = [feedback connectWithPKCS12Data:pkcs12 password:@"pa$$word"];
|
||||
if (connect != kNWSuccess) {
|
||||
NSLog(@"Unable to connect to feedback service: %@", [NWErrorUtil stringWithError:connect]);
|
||||
NSError *error = nil;
|
||||
NWPushFeedback *feedback = [NWPushFeedback connectWithPKCS12Data:pkcs12 password:@"pa$$word" error:&error];
|
||||
if (feedback) {
|
||||
NSLog(@"Connected to feedback service");
|
||||
} else {
|
||||
NSLog(@"Unable to connect to feedback service: %@", error);
|
||||
}
|
||||
```
|
||||
|
||||
When connected read the device token and date of invalidation:
|
||||
|
||||
```objective-c
|
||||
NSString *token = nil;
|
||||
NSDate *date = nil;
|
||||
NWError read = [feedback readToken:&token date:&date];
|
||||
if (read == kNWErrorReadClosedGraceful) {
|
||||
NSLog(@"All tokens have been read, connection closed");
|
||||
} else if (read != kNWSuccess) {
|
||||
NSLog(@"Unable to read feedback: %@", [NWErrorUtil stringWithError:read]);
|
||||
NSError *error = nil;
|
||||
NSArray *pairs = [feedback readTokenDatePairsWithMax:100 error:&error];
|
||||
if (pairs) {
|
||||
NSLog(@"Read token-date pairs: %@", pairs);
|
||||
} else {
|
||||
NSLog(@"Feedback service invalidated token: %@ on date: %@", token, date);
|
||||
NSLog(@"Unable to read feedback: %@", error);
|
||||
}
|
||||
```
|
||||
|
||||
Apple closes the connection after the last device token is read.
|
||||
|
||||
Pushing to macOS
|
||||
---------------
|
||||
|
||||
On macOS, you obtain a device token for your app by calling the `registerForRemoteNotificationTypes:` method of the `NSApplication` object. It is recommended that you call this method at launch time as part of your normal startup sequence. The first time your app calls this method, the app object requests the token from APNs. After the initial call, the app object contacts APNs only when the device token changes; otherwise, it returns the existing token quickly.
|
||||
|
||||
The app object notifies its delegate asynchronously upon the successful or unsuccessful retrieval of the device token. You use these delegate callbacks to process the device token or to handle any errors that arose. You must implement the following delegate methods to track whether registration was successful:
|
||||
|
||||
- Use the `application:didRegisterForRemoteNotificationsWithDeviceToken:` to receive the device token and forward it to your server.
|
||||
- Use the `application:didFailToRegisterForRemoteNotificationsWithError:` to respond to errors.
|
||||
|
||||
Note: If the device token changes while your app is running, the app object calls the appropriate delegate method again to notify you of the change.
|
||||
|
||||
The app delegate calls the `registerForRemoteNotificationTypes:` method as part of its regular launch-time setup, passing along the types of interactions that you intend to use. Upon receiving the device token, the `application:didRegisterForRemoteNotificationsWithDeviceToken:` method forwards it to the app’s associated server using a custom method. If an error occurs during registration, the app temporarily disables any features related to remote notifications. Those features are re-enabled when a valid device token is received.
|
||||
|
||||
```objective-c
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
|
||||
// Configure the user interactions first.
|
||||
[self configureUserInteractions];
|
||||
|
||||
[NSApp registerForRemoteNotificationTypes:(NSRemoteNotificationTypeAlert | NSRemoteNotificationTypeSound)];
|
||||
}
|
||||
```
|
||||
|
||||
```objective-c
|
||||
- (void)application:(NSApplication *)application
|
||||
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
|
||||
// Forward the token to your server.
|
||||
[self forwardTokenToServer:deviceToken];
|
||||
}
|
||||
```
|
||||
|
||||
```objective-c
|
||||
- (void)application:(NSApplication *)application
|
||||
didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
|
||||
NSLog(@"Remote notification support is unavailable due to error: %@", error);
|
||||
[self disableRemoteNotificationFeatures];
|
||||
}
|
||||
```
|
||||
|
||||
Apple closes the connection after the last device token is read. Use `-readTokenDatePairs:max:` to read all device tokens in one method call.
|
||||
|
||||
Certificate and key files
|
||||
-------------------------
|
||||
Pusher reads certificate and key data from PKCS12 files. This is a binary format that bundles both X.509 certificates and a private key in one file. Conversion from other file formats to and from PKCS12 is provided by the OpenSSL CLI.
|
||||
|
||||
PKCS12 to PEM:
|
||||
openssl pkcs12 -in pusher.p12 -out pusher.pem -clcerts -aes256
|
||||
*Inspect PKCS12:*
|
||||
|
||||
PEM to PKCS12:
|
||||
openssl pkcs12 -export -in pusher.pem -out pusher.p12 -name "Apple Push Services"
|
||||
openssl pkcs12 -in pusher.p12
|
||||
|
||||
where the output should be something like:
|
||||
|
||||
...
|
||||
friendlyName: Apple Development/Production IOS/Mac Push Services: <your-bundle-identifier>
|
||||
localKeyID: <key-id>
|
||||
...
|
||||
-----BEGIN CERTIFICATE-----
|
||||
...
|
||||
friendlyName: <private-key-used-for-generating-above-certificate>
|
||||
localKeyID: <same-key-id>
|
||||
...
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
...
|
||||
|
||||
Make sure your build matches the `Development/Production`, `iOS/Mac`, and bundle identifier.
|
||||
|
||||
*Inspect PKCS12 structure:*
|
||||
|
||||
Inspect PKCS12:
|
||||
openssl pkcs12 -in pusher.p12 -info -noout
|
||||
|
||||
Inspect PEM:
|
||||
|
||||
*Inspect PEM:*
|
||||
|
||||
openssl rsa -in pusher.pem -noout -check
|
||||
openssl rsa -in pusher.pem -pubout
|
||||
openssl x509 -in pusher.pem -noout -pubkey
|
||||
|
||||
*PKCS12 to PEM:*
|
||||
|
||||
openssl pkcs12 -in pusher.p12 -out pusher.pem -clcerts -aes256
|
||||
|
||||
Alternatively you can use the command below, which does *not* encrypt the private key (not recommended):
|
||||
|
||||
openssl pkcs12 -in pusher.p12 -out pusher.pem -nodes -clcerts
|
||||
|
||||
*PEM to PKCS12:*
|
||||
|
||||
openssl pkcs12 -export -in pusher.pem -out pusher.p12
|
||||
|
||||
Consult the OpenSSL documentation for more details: [OpenSSL Documents - pkcs12](https://www.openssl.org/docs/apps/pkcs12.html)
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
Apple's Push Notification Service is not very forgiving in nature. If things are done in the wrong order or data is formatted incorrectly the service will refuse to deliver any notification, but generally provides few clues about went wrong and how to fix it. In the worst case, it simply disconnects without even notifying the client.
|
||||
@@ -266,10 +394,46 @@ Some tips on what to look out for:
|
||||
|
||||
- A device token is unique to both the device, the developer's certificate, and to whether the app was built with a production or development (sandbox) certificate. Therefore make sure that the push certificate matches the app's provisioning profile exactly. This doesn't mean the tokens are always different; device tokens can be the same for different bundle identifiers.
|
||||
|
||||
- There are two channels through which Apple responds to sent notifications: the notification connection and the feedback connection. Both operate asynchronously, so for example after the second push has been sent, we might get a response to the first push, saying it has an invalid payload. Use a new identifier for every notification so these responses can be linked to the right notification.
|
||||
- There are two channels through which Apple responds to pushed notifications: the notification connection and the feedback connection. Both operate asynchronously, so for example after the second push has been sent, we might get a response to the first push, saying it has an invalid payload. Use a new identifier for every notification so these responses can be linked to the right notification.
|
||||
|
||||
If it fails to connect then check:
|
||||
|
||||
- Are the certificates and keys in order? Use the OpenSSL commands listed above to inspect the certificate. See if there is one push certificate and key present. Also make sure you're online, try `ping www.apple.com`.
|
||||
|
||||
- Is the certificate properly loaded? Try initializing an identity using `[NWSecTools identityWithPKCS12Data:data password:password error:&error]` or `[NWSecTools keychainIdentityWithCertificate:certificate error:&error]`.
|
||||
|
||||
- Are you using the right identity? Use `[NWSecTools inspectIdentity:identity]` to inspect the identity instance. In general `NWSecTools` can be helpful for inspecting certificates, identities and the keychain.
|
||||
|
||||
- Can you connect with the push servers? Try `[NWPusher connectWithIdentity:identity error:&error]` or `[NWPusher connectWithPKCS12Data:pkcs12 password:password error:&error]`.
|
||||
|
||||
- Pusher connects on port `2195` with hosts `gateway.push.apple.com` and `gateway.sandbox.push.apple.com`, and on port `2196` with hosts `feedback.push.apple.com` and `feedback.sandbox.push.apple.com`. Make sure your firewall is configured to allow these connections.
|
||||
|
||||
If nothing is delivered to the device then check:
|
||||
|
||||
- Is the device online? Is it able to receive push notifications from other services? Try to get pushes from other apps, for example a messenger. Many wireless connections work visibly fine, but do not deliver push notifications. Try to switch to another wifi or cellular network.
|
||||
|
||||
- Are you pushing to the right device token? This token should be returned by the OS of the receiving device, in the callback `-application: didRegisterForRemoteNotificationsWithDeviceToken:`. The push certificate should match the provisioning profile of the app, check *Development or Production*, *iOS or Mac*, and the *bundle identifier*. Make sure the receiving app is closed, so it cannot interfere with the delivery.
|
||||
|
||||
- Does the push call succeed? Isn't there any negative response from the push server or feedback server? Both `[pusher pushPayload:payload token:token identifier:rand() error:&error]` and `[pusher readFailedIdentifier:&identifier apnError:&apnError error:&error]` should return `YES`, but wait a second between pushing and reading. Also try to connect to the feedback service to read feedback.
|
||||
|
||||
Consult Apple's documentation for more troubleshooting tips: [Troubleshooting Push Notifications](https://developer.apple.com/library/mac/technotes/tn2265/_index.html)
|
||||
|
||||
Build with Xcode
|
||||
----------------
|
||||
The source comes with an Xcode project file that should take care of building the OS X and iOS demo applications. Alternatively you can also build `Pusher.app` from the commandline with `xcodebuild`:
|
||||
|
||||
xcodebuild -project NWPusher.xcodeproj -target PusherMac -configuration Release clean install
|
||||
|
||||
After a successful build, `Pusher.app` can be found in the `build` folder of the project.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
Documentation generated and installed using *appledoc* by running from the project root:
|
||||
|
||||
appledoc .
|
||||
|
||||
See the [appledoc documentation](http://gentlebytes.com/appledoc/) for more info.
|
||||
|
||||
License
|
||||
-------
|
||||
Pusher is licensed under the terms of the BSD 2-Clause License, see the included LICENSE file.
|
||||
|
||||
@@ -6,15 +6,16 @@
|
||||
//
|
||||
|
||||
#import "NWAppDelegate.h"
|
||||
#import "NWHub.h"
|
||||
#import "NWPusher.h"
|
||||
#import "NWNotification.h"
|
||||
#import "NWLCore.h"
|
||||
#import "NWSSLConnection.h"
|
||||
#import "NWSecTools.h"
|
||||
#import <PusherKit/PusherKit.h>
|
||||
|
||||
// TODO: Export your push certificate and key in PKCS12 format to pusher.p12 in the root of the project directory.
|
||||
static NSString * const pkcs12FileName = @"pusher.p12";
|
||||
static NSString * const pkcs12Password = @"pusher";
|
||||
|
||||
// TODO: Set the password of this .p12 file below, but be careful *not* to commit passwords to a (public) repository.
|
||||
static NSString * const pkcs12Password = @"pa$$word";
|
||||
|
||||
// TODO: Set the device token of the device you want to push to, see
|
||||
// `-application:didRegisterForRemoteNotificationsWithDeviceToken:` for more details.
|
||||
static NSString * const deviceToken = @"ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789";
|
||||
|
||||
static NWPusherViewController *controller = nil;
|
||||
@@ -26,9 +27,13 @@ static NWPusherViewController *controller = nil;
|
||||
UITextField *_textField;
|
||||
UIButton *_pushButton;
|
||||
UILabel *_infoLabel;
|
||||
UISwitch *_sanboxSwitch;
|
||||
NWHub *_hub;
|
||||
NSUInteger _index;
|
||||
dispatch_queue_t _serial;
|
||||
|
||||
NWIdentityRef _identity;
|
||||
NWCertificateRef _certificate;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
@@ -41,11 +46,22 @@ static NWPusherViewController *controller = nil;
|
||||
_serial = dispatch_queue_create("NWAppDelegate", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
_connectButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
|
||||
_connectButton.frame = CGRectMake(20, 20, self.view.bounds.size.width - 40, 40);
|
||||
_connectButton.frame = CGRectMake(20, 20, (self.view.bounds.size.width - 40)/2, 40);
|
||||
[_connectButton setTitle:@"Connect" forState:UIControlStateNormal];
|
||||
[_connectButton addTarget:self action:@selector(connect) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_connectButton addTarget:self action:@selector(connectButtonPressed) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:_connectButton];
|
||||
|
||||
_sanboxSwitch = [[UISwitch alloc] init];
|
||||
_sanboxSwitch.frame = CGRectMake((self.view.bounds.size.width + 40)/2, 20, 40, 40);
|
||||
[_sanboxSwitch addTarget:self action:@selector(sanboxCheckBoxDidPressed:) forControlEvents:UIControlEventValueChanged];
|
||||
[self.view addSubview:_sanboxSwitch];
|
||||
|
||||
UILabel *sandboxLabel = [[UILabel alloc] init];
|
||||
sandboxLabel.frame = CGRectMake(CGRectGetMaxX(_sanboxSwitch.frame) + 10, 20, 80, 40);
|
||||
sandboxLabel.font = [UIFont systemFontOfSize:12];
|
||||
sandboxLabel.text = @"Use sandbox";
|
||||
[self.view addSubview:sandboxLabel];
|
||||
|
||||
_textField = [[UITextField alloc] init];
|
||||
_textField.frame = CGRectMake(20, 70, self.view.bounds.size.width - 40, 26);
|
||||
_textField.text = @"Testing..";
|
||||
@@ -60,45 +76,107 @@ static NWPusherViewController *controller = nil;
|
||||
[self.view addSubview:_pushButton];
|
||||
|
||||
_infoLabel = [[UILabel alloc] init];
|
||||
_infoLabel.frame = CGRectMake(20, 156, self.view.bounds.size.width - 40, 26);
|
||||
_infoLabel.frame = CGRectMake(20, 156, self.view.bounds.size.width - 40, 60);
|
||||
_infoLabel.font = [UIFont systemFontOfSize:12];
|
||||
_infoLabel.numberOfLines = 0;
|
||||
[self.view addSubview:_infoLabel];
|
||||
|
||||
NWLogInfo(@"Connect with Apple's Push Notification service");
|
||||
|
||||
[self loadCertificate];
|
||||
}
|
||||
|
||||
- (void)connect
|
||||
- (void)loadCertificate
|
||||
{
|
||||
if (!_hub) {
|
||||
NWLogInfo(@"Connecting..");
|
||||
_connectButton.enabled = NO;
|
||||
dispatch_async(_serial, ^{
|
||||
NSURL *url = [NSBundle.mainBundle URLForResource:pkcs12FileName withExtension:nil];
|
||||
NSData *pkcs12 = [NSData dataWithContentsOfURL:url];
|
||||
NWHub *hub = [[NWHub alloc] initWithDelegate:self];
|
||||
NWError connected = [hub connectWithPKCS12Data:pkcs12 password:pkcs12Password];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (connected == kNWSuccess) {
|
||||
NWCertificateRef certificate = nil;
|
||||
[NWSecTools certificateWithIdentity:hub.pusher.connection.identity certificate:&certificate];
|
||||
BOOL sandbox = [NWSecTools isSandboxCertificate:certificate];
|
||||
NSString *summary = [NWSecTools summaryWithCertificate:certificate];
|
||||
NWLogInfo(@"Connected to APN: %@%@", summary, sandbox ? @" (sandbox)" : @"");
|
||||
_hub = hub;
|
||||
_pushButton.enabled = YES;
|
||||
[_connectButton setTitle:@"Disconnect" forState:UIControlStateNormal];
|
||||
} else {
|
||||
NWLogWarn(@"Unable to connect: %@", [NWErrorUtil stringWithError:connected]);
|
||||
}
|
||||
_connectButton.enabled = YES;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
_pushButton.enabled = NO;
|
||||
[_hub disconnect]; _hub = nil;
|
||||
NWLogInfo(@"Disconnected");
|
||||
[_connectButton setTitle:@"Connect" forState:UIControlStateNormal];
|
||||
NSURL *url = [NSBundle.mainBundle URLForResource:pkcs12FileName withExtension:nil];
|
||||
NSData *pkcs12 = [NSData dataWithContentsOfURL:url];
|
||||
NSError *error = nil;
|
||||
|
||||
NSArray *ids = [NWSecTools identitiesWithPKCS12Data:pkcs12 password:pkcs12Password error:&error];
|
||||
if (!ids) {
|
||||
NWLogWarn(@"Unable to read p12 file: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
for (NWIdentityRef identity in ids) {
|
||||
NSError *error = nil;
|
||||
NWCertificateRef certificate = [NWSecTools certificateWithIdentity:identity error:&error];
|
||||
if (!certificate) {
|
||||
NWLogWarn(@"Unable to import p12 file: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
_identity = identity;
|
||||
_certificate = certificate;
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)sanboxCheckBoxDidPressed:(UISwitch *)sender
|
||||
{
|
||||
if (_certificate)
|
||||
{
|
||||
[self disconnect];
|
||||
[self connectToEnvironment:[self selectedEnvironmentForCertificate:_certificate]];
|
||||
}
|
||||
}
|
||||
|
||||
- (NWEnvironment)selectedEnvironmentForCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
return _sanboxSwitch.isOn ? NWEnvironmentSandbox : NWEnvironmentProduction;
|
||||
}
|
||||
|
||||
- (NWEnvironment)preferredEnvironmentForCertificate:(NWCertificateRef)certificate
|
||||
{
|
||||
NWEnvironmentOptions environmentOptions = [NWSecTools environmentOptionsForCertificate:certificate];
|
||||
|
||||
return (environmentOptions & NWEnvironmentOptionSandbox) ? NWEnvironmentSandbox : NWEnvironmentProduction;
|
||||
}
|
||||
|
||||
- (void)connectButtonPressed
|
||||
{
|
||||
if (_hub)
|
||||
{
|
||||
[self disconnect];
|
||||
_connectButton.enabled = YES;
|
||||
[_connectButton setTitle:@"Connect" forState:UIControlStateNormal];
|
||||
return;
|
||||
}
|
||||
|
||||
NWEnvironment preferredEnvironment = [self preferredEnvironmentForCertificate:_certificate];
|
||||
|
||||
[self connectToEnvironment:preferredEnvironment];
|
||||
}
|
||||
|
||||
- (void)disconnect
|
||||
{
|
||||
[self disableButtons];
|
||||
[_hub disconnect]; _hub = nil;
|
||||
NWLogInfo(@"Disconnected");
|
||||
}
|
||||
|
||||
- (void)connectToEnvironment:(NWEnvironment)environment
|
||||
{
|
||||
[self disableButtons];
|
||||
|
||||
NWLogInfo(@"Connecting..");
|
||||
dispatch_async(_serial, ^{
|
||||
NSError *error = nil;
|
||||
|
||||
NWHub *hub = [NWHub connectWithDelegate:self identity:_identity environment:environment error:&error];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (hub) {
|
||||
NSString *summary = [NWSecTools summaryWithCertificate:_certificate];
|
||||
NWLogInfo(@"Connected to APN: %@ (%@)", summary, descriptionForEnvironent(environment));
|
||||
_hub = hub;
|
||||
|
||||
[_connectButton setTitle:@"Disconnect" forState:UIControlStateNormal];
|
||||
} else {
|
||||
NWLogWarn(@"Unable to connect: %@", error.localizedDescription);
|
||||
}
|
||||
|
||||
[self enableButtonsForCertificate:_certificate environment:environment];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)push
|
||||
@@ -110,20 +188,41 @@ static NWPusherViewController *controller = nil;
|
||||
NSUInteger failed = [_hub pushPayload:payload token:token];
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
|
||||
dispatch_after(popTime, _serial, ^(void){
|
||||
NSUInteger failed2 = failed + [_hub flushFailed];
|
||||
NSUInteger failed2 = failed + [_hub readFailed];
|
||||
if (!failed2) NWLogInfo(@"Payload has been pushed");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)notification:(NWNotification *)notification didFailWithResult:(NWError)result
|
||||
- (void)notification:(NWNotification *)notification didFailWithError:(NSError *)error
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
//NSLog(@"failed notification: %@ %@ %lu %lu %lu", notification.payload, notification.token, notification.identifier, notification.expires, notification.priority);
|
||||
NWLogWarn(@"Notification error: %@", [NWErrorUtil stringWithError:result]);
|
||||
NWLogWarn(@"Notification error: %@", error.localizedDescription);
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - BUtton states
|
||||
|
||||
- (void)disableButtons
|
||||
{
|
||||
_pushButton.enabled = NO;
|
||||
_connectButton.enabled = NO;
|
||||
_sanboxSwitch.enabled = NO;
|
||||
}
|
||||
|
||||
- (void)enableButtonsForCertificate:(NWCertificateRef)certificate environment:(NWEnvironment)environment
|
||||
{
|
||||
NWEnvironmentOptions environmentOptions = [NWSecTools environmentOptionsForCertificate:certificate];
|
||||
|
||||
BOOL shouldEnableEnvButton = (environmentOptions == NWEnvironmentOptionAny);
|
||||
BOOL shouldSelectSandboxEnv = (environment == NWEnvironmentSandbox);
|
||||
|
||||
_pushButton.enabled = YES;
|
||||
_connectButton.enabled = YES;
|
||||
_sanboxSwitch.enabled = shouldEnableEnvButton;
|
||||
_sanboxSwitch.on = shouldSelectSandboxEnv;
|
||||
}
|
||||
|
||||
#pragma mark - NWLogging
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</dict>
|
||||
</dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.noodlewerk.pusher</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
@@ -32,11 +32,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.5.1</string>
|
||||
<string>0.7.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>7</string>
|
||||
<string>19</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
|
||||