New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PROD-835] Push notifications #221
Changes from 1 commit
4cd57e4
cceae46
243ece6
09e0535
9d5d679
5da1c9f
6576165
aa6012a
74dc72e
e98aeb6
9f9a0f7
b5d7c53
727a0fc
44ef422
0d37475
a9db9c5
d6580e1
15c8814
07dfc9b
1aff281
4810feb
07d54a9
99ba736
3befe05
1db090a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// | ||
// PTNativePusher.h | ||
// libPusher | ||
// | ||
// Created by James Fisher on 25/07/2016. | ||
// | ||
// | ||
|
||
#ifndef PTNativePusher_h | ||
#define PTNativePusher_h | ||
|
||
@interface PTNativePusher : NSObject | ||
|
||
// Takes ownership of the pusherAppKey string | ||
- (id)initWithPusherAppKey:(NSString *)pusherAppKey; | ||
|
||
@end | ||
|
||
#endif /* PTNativePusher_h */ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// | ||
// PTNativePusher.m | ||
// libPusher | ||
// | ||
// Created by James Fisher on 25/07/2016. | ||
// | ||
// | ||
|
||
#import "PTNativePusher.h" | ||
|
||
NSString *const CLIENT_API_V1_ENDPOINT = @"https://nativepushclient-cluster1.pusher.com/client_api/v1"; | ||
|
||
const int MAX_FAILED_REQUEST_ATTEMPTS = 6; | ||
|
||
// FIXME free() of below vars? ref counting? | ||
@implementation PTNativePusher { | ||
NSURLSession * urlSession; | ||
int failedNativeServiceRequests; | ||
NSString * pusherAppKey; | ||
NSString * clientId; | ||
NSMutableArray * outbox; | ||
} | ||
|
||
- (id)initWithPusherAppKey:(NSString *)_pusherAppKey { | ||
if (self = [super init]) { | ||
self->urlSession = [NSURLSession sharedSession]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not familiar with the Typically I've seen where you just do something like |
||
self->failedNativeServiceRequests = 0; | ||
self->pusherAppKey = _pusherAppKey; | ||
self->clientId = NULL; // NULL until we register | ||
self->outbox = [NSMutableArray array]; | ||
} | ||
return self; | ||
} | ||
|
||
// Lovingly borrowed from http://stackoverflow.com/a/16411517/229792 | ||
- (NSString *) deviceTokenToString:(NSData *)deviceToken { | ||
const char *data = [deviceToken bytes]; | ||
NSMutableString *token = [NSMutableString string]; | ||
|
||
for (NSUInteger i = 0; i < [deviceToken length]; i++) { | ||
[token appendFormat:@"%02.2hhX", data[i]]; | ||
} | ||
|
||
return [token copy]; | ||
} | ||
|
||
- (void) registerWithDeviceToken: (NSData*) deviceToken { | ||
NSMutableURLRequest * request = | ||
[NSMutableURLRequest requestWithURL:[NSURL URLWithString: [NSString stringWithFormat:@"%@%@", CLIENT_API_V1_ENDPOINT, @"/clients"]]]; | ||
[request setHTTPMethod:@"POST"]; | ||
|
||
NSString* deviceTokenString = [self deviceTokenToString:deviceToken]; | ||
|
||
NSDictionary * params = @{ | ||
@"app_key": self->pusherAppKey, | ||
@"platform_type": @"apns", // FIXME constant | ||
@"token": deviceTokenString | ||
// TODO client name/version | ||
}; | ||
|
||
assert([NSJSONSerialization isValidJSONObject:params]); | ||
|
||
[request setHTTPBody:[NSJSONSerialization dataWithJSONObject:params options:@[] error: NULL]]; // FIXME error NULL? | ||
|
||
// FIXME what encoding does the above ^ serialization use? UTF-8? | ||
[request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; | ||
|
||
NSURLSessionDataTask * task = [urlSession dataTaskWithRequest:request | ||
completionHandler: ^(NSData * data, NSURLResponse * response, NSError * error) { | ||
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response; | ||
if ([httpResponse statusCode] >= 200 && [httpResponse statusCode] < 300) { | ||
NSError * jsonDecodingError; | ||
id jsonObj = [NSJSONSerialization JSONObjectWithData:data options:@[] error:&jsonDecodingError]; | ||
// FIXME check jsonDecodingError | ||
NSDictionary* jsonDict = (NSDictionary*) jsonObj; | ||
NSObject * clientIdObj = [jsonDict objectForKey:@"id"]; | ||
NSString * clientIdString = (NSString*) clientIdObj; | ||
self->clientId = clientIdString; | ||
[self tryFlushOutbox]; | ||
} else { | ||
// TODO error | ||
} | ||
}]; | ||
[task resume]; | ||
} | ||
|
||
- (void) subscribe:(NSString *)interestName { | ||
// TODO using a dictionary here is kinda horrible | ||
[self->outbox addObject:@{ @"interestName": interestName, @"change": @"subscribe" }]; | ||
[self tryFlushOutbox]; | ||
} | ||
|
||
- (void) unsubscribe:(NSString *)interestName { | ||
[self->outbox addObject:@{ @"interestName": interestName, @"change": @"unsubscribe" }]; | ||
[self tryFlushOutbox]; | ||
} | ||
|
||
- (void) tryFlushOutbox { | ||
if (self->clientId != NULL && 0 < [self->outbox count]) { | ||
NSDictionary* subscriptionChange = (NSDictionary*) [self->outbox objectAtIndex:0]; | ||
NSString* interestName = (NSString*) [subscriptionChange objectForKey:@"interestName"]; | ||
NSString* change = (NSString*) [subscriptionChange objectForKey:@"change"]; | ||
[self | ||
modifySubscriptionForPusherAppKey:self->pusherAppKey | ||
clientId:self->clientId | ||
interestName:interestName | ||
subscriptionChange:change | ||
callback: ^() { [self tryFlushOutbox]; }]; | ||
} | ||
} | ||
|
||
- (void) modifySubscriptionForPusherAppKey:(NSString*) _pusherAppKey clientId: (NSString*) _clientId interestName: (NSString*) interestName subscriptionChange: (NSString*) subscriptionChange callback: (void(^)(void)) callback { | ||
NSString* url = [NSString stringWithFormat:@"%@/clients/%@/interests/%@", CLIENT_API_V1_ENDPOINT, _clientId, interestName]; | ||
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL: [NSURL URLWithString:url]]; | ||
|
||
if ([subscriptionChange isEqualToString:@"subscribe"]) { | ||
[request setHTTPMethod:@"POST"]; | ||
} else if ([subscriptionChange isEqualToString:@"subscribe"]) { | ||
[request setHTTPMethod:@"DELETE"]; | ||
} | ||
|
||
NSDictionary* params = @{ | ||
@"app_key": _pusherAppKey | ||
// TODO client name/version | ||
}; | ||
|
||
[request setHTTPBody:[NSJSONSerialization dataWithJSONObject:params options:@[] error:NULL]]; // TODO error?? | ||
[request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; // TODO charset | ||
|
||
NSURLSessionDataTask* task = [urlSession dataTaskWithRequest:request completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) { | ||
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response; | ||
if ([httpResponse statusCode] >= 200 && [httpResponse statusCode] < 300) { | ||
// Reset number of failed requests to 0 upon success | ||
self->failedNativeServiceRequests = 0; | ||
|
||
callback(); | ||
} else { | ||
[self->outbox insertObject:@{ @"interestName": interestName, @"change": subscriptionChange } atIndex:0]; | ||
|
||
if (error != nil) { | ||
// TODO print error | ||
} else { | ||
// TODO print error | ||
} | ||
|
||
self->failedNativeServiceRequests += 1; | ||
|
||
if (self->failedNativeServiceRequests < MAX_FAILED_REQUEST_ATTEMPTS) { | ||
callback(); | ||
} else { | ||
// TODO print error | ||
} | ||
// TODO error | ||
} | ||
}]; | ||
[task resume]; | ||
} | ||
|
||
@end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rewrite as
#define kPUSHER_CLIENT_API_V1_ENDPOINT @"https://nativepushclient-cluster1.pusher.com/client_api/v1";