Repository: boundsj/ObjectiveDDP Branch: master Commit: bd34e49242d1 Files: 95 Total size: 297.7 KB Directory structure: gitextract_2gf25u8g/ ├── .gitignore ├── .swift-version ├── .travis.yml ├── Example/ │ ├── Example/ │ │ ├── Example/ │ │ │ ├── AddViewController.h │ │ │ ├── AddViewController.m │ │ │ ├── AddViewController.xib │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Images.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── green_light.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── red_light.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Info.plist │ │ │ ├── ListViewController.h │ │ │ ├── ListViewController.m │ │ │ ├── ListViewController.xib │ │ │ ├── LoginViewController.h │ │ │ ├── LoginViewController.m │ │ │ ├── LoginViewController.xib │ │ │ ├── ViewController.h │ │ │ ├── ViewController.m │ │ │ ├── ViewController.xib │ │ │ ├── en.lproj/ │ │ │ │ └── InfoPlist.strings │ │ │ └── main.m │ │ ├── Example.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ └── project.xcworkspace/ │ │ │ └── contents.xcworkspacedata │ │ ├── Example.xcworkspace/ │ │ │ └── contents.xcworkspacedata │ │ ├── Podfile │ │ └── example.txt │ ├── swiftExample/ │ │ ├── Podfile │ │ ├── bridge.m │ │ ├── swiftddp/ │ │ │ ├── AddViewController.swift │ │ │ ├── AddViewController.xib │ │ │ ├── AppDelegate.swift │ │ │ ├── Example-Info.plist │ │ │ ├── Info.plist │ │ │ ├── ListViewController.swift │ │ │ ├── ListViewController.xib │ │ │ ├── LoginViewController.swift │ │ │ ├── LoginViewController.xib │ │ │ ├── ViewController.swift │ │ │ ├── ViewController.xib │ │ │ └── images.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ ├── swiftddp-Bridging-Header.h │ │ ├── swiftddp.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ └── project.xcworkspace/ │ │ │ └── contents.xcworkspacedata │ │ ├── swiftddp.xcworkspace/ │ │ │ └── contents.xcworkspacedata │ │ └── swiftddpTests/ │ │ ├── Info.plist │ │ └── swiftddpTests.swift │ └── todos/ │ ├── .meteor/ │ │ ├── .finished-upgraders │ │ ├── .gitignore │ │ ├── .id │ │ ├── packages │ │ ├── platforms │ │ ├── release │ │ └── versions │ ├── server.css │ ├── server.html │ └── server.js ├── LICENSE.txt ├── ObjectiveDDP/ │ ├── BSONIdGenerator.h │ ├── BSONIdGenerator.m │ ├── DependencyProvider.h │ ├── DependencyProvider.m │ ├── MeteorClient+Parsing.m │ ├── MeteorClient+Private.h │ ├── MeteorClient.h │ ├── MeteorClient.m │ ├── ObjectiveDDP-Prefix.pch │ ├── ObjectiveDDP.h │ └── ObjectiveDDP.m ├── ObjectiveDDP.podspec ├── ObjectiveDDP.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ └── contents.xcworkspacedata ├── ObjectiveDDP.xcworkspace/ │ └── contents.xcworkspacedata ├── Podfile ├── README.md ├── Rakefile ├── Specs/ │ ├── DDPSpecHelper.h │ ├── DDPSpecHelper.m │ ├── DependencyProvider+Spec.m │ ├── MeteorClientSpec.mm │ ├── Mocks/ │ │ ├── FakeDependencyProvider.h │ │ ├── FakeDependencyProvider.m │ │ ├── MockObjectiveDDPDelegate.h │ │ ├── MockObjectiveDDPDelegate.m │ │ ├── MockSRWebSocket.h │ │ └── MockSRWebSocket.m │ ├── NSObject+Spec.m │ ├── ObjectiveDDPSpec.mm │ ├── Specs-Info.plist │ ├── Specs-Prefix.pch │ ├── en.lproj/ │ │ └── InfoPlist.strings │ └── main.m └── thrust.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea xcuserdata xcshareddata Example/Pods Example/swiftExample/Pods build Pods .DS_Store Podfile.lock ================================================ FILE: .swift-version ================================================ 2.3 ================================================ FILE: .travis.yml ================================================ language: objective-c install: - brew install ios-sim - gem install thrust script: rake specs ================================================ FILE: Example/Example/Example/AddViewController.h ================================================ #import @protocol AddViewControllerDelegate; @interface AddViewController : UIViewController @property (assign, nonatomic) id delegate; @property (weak, nonatomic) IBOutlet UITextView *messageTextView; @end @protocol AddViewControllerDelegate - (void)didAddThing:(NSString *)message; @end ================================================ FILE: Example/Example/Example/AddViewController.m ================================================ #import "AddViewController.h" @interface AddViewController () @end @implementation AddViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization } return self; } - (IBAction)didTouchAddButton:(id)sender { [self.delegate didAddThing: self.messageTextView.text]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self.view endEditing:YES]; } @end ================================================ FILE: Example/Example/Example/AddViewController.xib ================================================ 1552 12D78 3084 1187.37 626.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin 2083 IBNSLayoutConstraint IBProxyObject IBUIButton IBUILabel IBUITextView IBUIView com.apple.InterfaceBuilder.IBCocoaTouchPlugin PluginDependencyRecalculationVersion IBFilesOwner IBCocoaTouchFramework IBFirstResponder IBCocoaTouchFramework 274 274 {{20, 96}, {280, 176}} _NS:9 1 MC45MDE5NjA3OTAyIDAuOTAxOTYwNzkwMiAwLjkwMTk2MDc5MDIAA YES YES IBCocoaTouchFramework 2 IBCocoaTouchFramework 1 22 Helvetica 22 16 292 {{20, 20}, {280, 68}} _NS:9 NO YES 7 NO IBCocoaTouchFramework NEW TODO 1 MCAwIDAAA darkTextColor 0 1 43 Helvetica 43 16 NO 292 {{20, 297}, {280, 51}} _NS:9 1 MC45MDE5NjA3OTAyIDAuOTAxOTYwNzkwMiAwLjkwMTk2MDc5MDIAA NO IBCocoaTouchFramework 0 0 Add 3 MQA 1 MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA 3 MC41AA 2 31 Helvetica-Bold 31 16 {{0, 20}, {320, 548}} 3 MQA 2 IBUIScreenMetrics YES {320, 568} {568, 320} IBCocoaTouchFramework Retina 4 Full Screen 2 IBCocoaTouchFramework view 3 messageTextView 110 didTouchAddButton: 7 109 0 1 4 0 4 1 200 1000 3 9 3 6 0 6 1 20 1000 8 29 3 5 0 5 1 20 1000 8 29 3 3 0 4 1 8 1000 6 24 3 5 0 5 1 20 1000 8 29 3 6 0 6 1 20 1000 8 29 3 6 0 6 1 20 1000 8 29 3 3 0 3 1 20 1000 8 29 3 5 0 5 1 20 1000 8 29 3 -1 File's Owner -2 11 8 0 0 1 176 1000 3 9 1 25 26 27 8 0 0 1 68 1000 3 9 1 34 59 61 62 8 0 0 1 51 1000 3 9 1 67 71 80 81 91 107 108 AddViewController com.apple.InterfaceBuilder.IBCocoaTouchPlugin UIResponder com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin 110 AddViewController UIViewController didTouchAddButton: id didTouchAddButton: didTouchAddButton: id messageTextView UITextView messageTextView messageTextView UITextView IBProjectSource ./Classes/AddViewController.h NSLayoutConstraint NSObject IBProjectSource ./Classes/NSLayoutConstraint.h 0 IBCocoaTouchFramework YES 3 YES 2083 ================================================ FILE: Example/Example/Example/AppDelegate.h ================================================ #import @class ViewController, MeteorClient; @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) ViewController *viewController; @property (strong, nonatomic) UINavigationController *navController; @property (strong, nonatomic) MeteorClient *meteorClient; @end ================================================ FILE: Example/Example/Example/AppDelegate.m ================================================ #import "AppDelegate.h" #import "ViewController.h" #import "LoginViewController.h" #import "ObjectiveDDP.h" #import @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.meteorClient = [[MeteorClient alloc] initWithDDPVersion:@"pre2"]; [self.meteorClient addSubscription:@"things"]; [self.meteorClient addSubscription:@"lists"]; LoginViewController *loginController = [[LoginViewController alloc] initWithNibName:@"LoginViewController" bundle:nil]; loginController.meteor = self.meteorClient; ObjectiveDDP *ddp = [[ObjectiveDDP alloc] initWithURLString:@"wss://ddptester.meteor.com/websocket" delegate:self.meteorClient]; // local testing //ObjectiveDDP *ddp = [[ObjectiveDDP alloc] initWithURLString:@"ws://localhost:3000/websocket" delegate:self.meteorClient]; self.meteorClient.ddp = ddp; [self.meteorClient.ddp connectWebSocket]; self.navController = [[UINavigationController alloc] initWithRootViewController:loginController]; self.navController.navigationBarHidden = YES; self.window.rootViewController = self.navController; [self.window makeKeyAndVisible]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reportConnection) name:MeteorClientDidConnectNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reportDisconnection) name:MeteorClientDidDisconnectNotification object:nil]; return YES; } - (void)reportConnection { NSLog(@"================> connected to server!"); } - (void)reportDisconnection { NSLog(@"================> disconnected from server!"); } - (void)applicationDidBecomeActive:(UIApplication *)application { [self.meteorClient.ddp connectWebSocket]; } @end ================================================ FILE: Example/Example/Example/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Example/Example/Example/Images.xcassets/green_light.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "green_light.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "green_light@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Example/Example/Example/Images.xcassets/red_light.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "red_light.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "red_light@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Example/Example/Example/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier com.boundsj.$(PRODUCT_NAME:rfc1034identifier) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Example/Example/Example/ListViewController.h ================================================ #import #import @interface ListViewController : UIViewController @property (nonatomic, strong) MeteorClient *meteor; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil meteor:(MeteorClient *)meteor; @property (copy, nonatomic) NSString *userId; @end ================================================ FILE: Example/Example/Example/ListViewController.m ================================================ #import "ListViewController.h" #import "ViewController.h" #import "MeteorClient.h" @interface ListViewController () @property (weak, nonatomic) IBOutlet UITableView *tableview; @property (strong, nonatomic) M13MutableOrderedDictionary *lists; @end @implementation ListViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil meteor:(MeteorClient *)meteor { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { self.meteor = meteor; self.lists = self.meteor.collections[@"lists"]; } return self; } - (void)viewWillAppear:(BOOL)animated { self.navigationItem.title = @"My Lists"; self.navigationController.navigationBarHidden = NO; self.navigationItem.hidesBackButton = YES; UIBarButtonItem *logoutButton = [[UIBarButtonItem alloc] initWithTitle:@"Logout" style:UIBarButtonItemStylePlain target:self action:@selector(logout)]; self.navigationItem.rightBarButtonItem = logoutButton; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveUpdate:) name:@"added" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveUpdate:) name:@"removed" object:nil]; } - (void)didReceiveUpdate:(NSNotification *)notification { [self.tableview reloadData]; } - (void)logout { [self.meteor logout]; [self.navigationController popToRootViewControllerAnimated:YES]; } #pragma mark - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.lists count]; } static NSDictionary *selectedList; - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cellIdentifier = @"list"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } NSDictionary *list = self.lists[indexPath.row]; selectedList = list; cell.textLabel.text = list[@"name"]; UIButton *shareButton = [UIButton buttonWithType:UIButtonTypeCustom]; shareButton.frame = CGRectMake(255.0f, 5.0f, 55.0f, 34.0f); shareButton.backgroundColor = [UIColor greenColor]; [shareButton setTitle:@"Share" forState:UIControlStateNormal]; [shareButton addTarget:self action:@selector(didClickShareButton:forEvent:) forControlEvents:UIControlEventTouchUpInside]; // XXX: shareButton needs to be able to link to its list // shareButton [cell addSubview:shareButton]; return cell; } static UITextField *shareWithTF; - (void)didClickShareButton:(id)sender forEvent:(UIEvent *)event { UITouch *touch = [[event allTouches] anyObject]; CGPoint location = [touch locationInView:self.view]; UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0, location.y, 320.0, 100.0)]; view.backgroundColor = [UIColor whiteColor]; UITextField *shareWithTextField = [[UITextField alloc] initWithFrame:CGRectMake(10.0, 50.0, 240.0, 44.0)]; shareWithTF = shareWithTextField; shareWithTextField.borderStyle = UITextBorderStyleLine; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(255.0, 50.0, 60.0, 44.0); button.backgroundColor = [UIColor greenColor]; [button setTitle:@"Send" forState:UIControlStateNormal]; [button addTarget:self action:@selector(didClickShareWithButton:) forControlEvents:UIControlEventTouchUpInside]; [view addSubview:shareWithTextField]; [view addSubview:button]; UIView *modalBackground = [[UIView alloc] initWithFrame:self.view.frame]; modalBackground.backgroundColor = [UIColor blackColor]; modalBackground.alpha = 0.7; [self.view addSubview:modalBackground]; [self.view addSubview:view]; } - (void)didClickShareWithButton:(id)sender { NSArray *parameters = @[@{@"_id": selectedList[@"_id"]}, @{@"$set":@{@"share_with": shareWithTF.text}}]; [self.meteor callMethodName:@"/lists/update" parameters:parameters responseCallback:nil]; [[[self.view subviews] lastObject] removeFromSuperview]; [[[self.view subviews] lastObject] removeFromSuperview]; } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { NSDictionary *list = self.lists[indexPath.row]; [self.meteor callMethodName:@"/lists/remove" parameters:@[@{@"_id": list[@"_id"]}] responseCallback:nil]; } #pragma mark - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { NSDictionary *list = self.lists[indexPath.row]; ViewController *controller = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil meteor:self.meteor listName:list[@"name"]]; controller.userId = self.userId; [self.navigationController pushViewController:controller animated:YES]; } @end ================================================ FILE: Example/Example/Example/ListViewController.xib ================================================ ================================================ FILE: Example/Example/Example/LoginViewController.h ================================================ #import @class MeteorClient; @interface LoginViewController : UIViewController @property (weak, nonatomic) IBOutlet UITextField *email; @property (weak, nonatomic) IBOutlet UITextField *password; @property (weak, nonatomic) IBOutlet UILabel *connectionStatusText; @property (weak, nonatomic) IBOutlet UIImageView *connectionStatusLight; @property (weak, nonatomic) IBOutlet UIButton *loginButton; @property (strong, nonatomic) MeteorClient *meteor; @property (weak, nonatomic) IBOutlet UIButton *sayHiButton; - (IBAction)didTapLoginButton:(id)sender; @end ================================================ FILE: Example/Example/Example/LoginViewController.m ================================================ #import "LoginViewController.h" #import "ListViewController.h" #import @implementation LoginViewController - (void)viewWillAppear:(BOOL)animated { [self.meteor addObserver:self forKeyPath:@"websocketReady" options:NSKeyValueObservingOptionNew context:nil]; } #pragma mark - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"websocketReady"] && self.meteor.websocketReady) { self.connectionStatusText.text = @"Connected to Todo Server"; UIImage *image = [UIImage imageNamed: @"green_light.png"]; [self.connectionStatusLight setImage:image]; } } #pragma mark UI Actions - (IBAction)didTapLoginButton:(id)sender { if (!self.meteor.websocketReady) { UIAlertView *notConnectedAlert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Can't find the Todo server, try again" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [notConnectedAlert show]; return; } [self.meteor logonWithEmail:self.email.text password:self.password.text responseCallback:^(NSDictionary *response, NSError *error) { if (error) { [self handleFailedAuth:error]; return; } [self handleSuccessfulAuth]; }]; } - (IBAction)didTapSayHiButton { [self.meteor callMethodName:@"sayHelloTo" parameters:@[self.email.text] responseCallback:^(NSDictionary *response, NSError *error) { NSString *message = response[@"result"]; [[[UIAlertView alloc] initWithTitle:@"Meteor Todos" message:message delegate:nil cancelButtonTitle:@"Great" otherButtonTitles:nil] show]; }]; } #pragma mark - Internal - (void)handleSuccessfulAuth { ListViewController *controller = [[ListViewController alloc] initWithNibName:@"ListViewController" bundle:nil meteor:self.meteor]; controller.userId = self.meteor.userId; [self.navigationController pushViewController:controller animated:YES]; } - (void)handleFailedAuth:(NSError *)error { [[[UIAlertView alloc] initWithTitle:@"Meteor Todos" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"Try Again" otherButtonTitles:nil] show]; } @end ================================================ FILE: Example/Example/Example/LoginViewController.xib ================================================ ================================================ FILE: Example/Example/Example/ViewController.h ================================================ #import #import "AddViewController.h" #import @interface ViewController : UIViewController @property (strong, nonatomic) MeteorClient *meteor; @property (copy, nonatomic) NSString *userId; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil meteor:(MeteorClient *)meteor listName:(NSString *)listName; @end ================================================ FILE: Example/Example/Example/ViewController.m ================================================ #import "ViewController.h" #import @interface ViewController () @property (weak, nonatomic) IBOutlet UITableView *tableview; @property (copy, nonatomic) NSString *listName; @end @implementation ViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil meteor:(MeteorClient *)meteor listName:(NSString *) listName { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { self.meteor = meteor; self.listName = listName; } return self; } - (void)viewWillAppear:(BOOL)animated { self.navigationItem.title = self.listName; UIBarButtonItem *addButton = [[UIBarButtonItem alloc ] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(didTouchAdd:)]; [self.navigationItem setRightBarButtonItem:addButton]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveUpdate:) name:@"added" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveUpdate:) name:@"removed" object:nil]; } - (void)viewWillDisappear:(BOOL)animated { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)didReceiveUpdate:(NSNotification *)notification { [self.tableview reloadData]; } - (NSArray *)computedList { NSPredicate *pred = [NSPredicate predicateWithFormat:@"(listName like %@)", self.listName]; M13MutableOrderedDictionary *things = self.meteor.collections[@"things"]; return [things.allObjects filteredArrayUsingPredicate:pred]; } #pragma mark UI Actions - (IBAction)didTouchAdd:(id)sender { AddViewController *addController = [[AddViewController alloc] initWithNibName:@"AddViewController" bundle:nil]; addController.delegate = self; [self presentViewController:addController animated:YES completion:nil]; } #pragma mark - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.computedList count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cellIdentifier = @"thing"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } NSDictionary *thing = self.computedList[indexPath.row]; cell.textLabel.text = thing[@"msg"]; return cell; } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { NSDictionary *thing = self.computedList[indexPath.row]; [self.meteor callMethodName:@"/things/remove" parameters:@[@{@"_id": thing[@"_id"]}] responseCallback:nil]; } } - (void)didAddThing:(NSString *)message { [self dismissViewControllerAnimated:YES completion:nil]; NSArray *parameters = @[@{@"_id": [[NSUUID UUID] UUIDString], @"msg": message, @"owner": self.userId, @"listName": self.listName}]; [self.meteor callMethodName:@"/things/insert" parameters:parameters responseCallback:nil]; } @end ================================================ FILE: Example/Example/Example/ViewController.xib ================================================ 1552 12D78 3084 1187.37 626.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin 2083 IBNSLayoutConstraint IBProxyObject IBUITableView IBUIView com.apple.InterfaceBuilder.IBCocoaTouchPlugin PluginDependencyRecalculationVersion IBFilesOwner IBCocoaTouchFramework IBFirstResponder IBCocoaTouchFramework 274 274 {320, 548} _NS:9 3 MQA YES IBCocoaTouchFramework YES 1 0 YES 44 22 22 {{0, 20}, {320, 548}} 3 MC43NQA 2 NO IBUIScreenMetrics YES {320, 568} {568, 320} IBCocoaTouchFramework Retina 4 Full Screen 2 IBCocoaTouchFramework view 7 tableview 54 dataSource 53 0 -1 File's Owner -2 6 3 0 3 1 0.0 1000 8 29 3 4 0 4 1 0.0 1000 8 29 3 6 0 6 1 0.0 1000 8 29 3 5 0 5 1 0.0 1000 8 29 3 22 29 32 84 89 ViewController com.apple.InterfaceBuilder.IBCocoaTouchPlugin UIResponder com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin 89 0 IBCocoaTouchFramework YES 3 YES 2083 ================================================ FILE: Example/Example/Example/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: Example/Example/Example/main.m ================================================ // // main.m // Example // // Created by Michael Arthur on 14/10/14. // Copyright (c) 2014 Jesse Bounds. All rights reserved. // #import #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: Example/Example/Example.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 0B3B61448B57250B0026C6F1 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F8D62AFA66EC41C7B554BB8B /* libPods.a */; }; EAC693A319EC94C000F0627B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = EAC693A219EC94C000F0627B /* main.m */; }; EAC693A619EC94C000F0627B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EAC693A519EC94C000F0627B /* AppDelegate.m */; }; EAC693A919EC94C000F0627B /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EAC693A819EC94C000F0627B /* ViewController.m */; }; EAC693AE19EC94C000F0627B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EAC693AD19EC94C000F0627B /* Images.xcassets */; }; EAC693C919EC94EC00F0627B /* AddViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EAC693C719EC94EC00F0627B /* AddViewController.m */; }; EAC693CA19EC94EC00F0627B /* AddViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = EAC693C819EC94EC00F0627B /* AddViewController.xib */; }; EAC693D119EC94F700F0627B /* ListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EAC693CC19EC94F700F0627B /* ListViewController.m */; }; EAC693D219EC94F700F0627B /* ListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = EAC693CD19EC94F700F0627B /* ListViewController.xib */; }; EAC693D319EC94F700F0627B /* LoginViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EAC693CF19EC94F700F0627B /* LoginViewController.m */; }; EAC693D419EC94F700F0627B /* LoginViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = EAC693D019EC94F700F0627B /* LoginViewController.xib */; }; EAC693DA19EC95A700F0627B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EAC693D619EC95A700F0627B /* InfoPlist.strings */; }; EAC693DD19EC95C800F0627B /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = EAC693DC19EC95C800F0627B /* ViewController.xib */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 7548EC47874C5A256237302C /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; 831424A6A73DAFA3814E1212 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; EAC6939D19EC94C000F0627B /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; EAC693A119EC94C000F0627B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EAC693A219EC94C000F0627B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; EAC693A419EC94C000F0627B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; EAC693A519EC94C000F0627B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; EAC693A719EC94C000F0627B /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; EAC693A819EC94C000F0627B /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; EAC693AD19EC94C000F0627B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; EAC693C619EC94EC00F0627B /* AddViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddViewController.h; sourceTree = ""; }; EAC693C719EC94EC00F0627B /* AddViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddViewController.m; sourceTree = ""; }; EAC693C819EC94EC00F0627B /* AddViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AddViewController.xib; sourceTree = ""; }; EAC693CB19EC94F700F0627B /* ListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ListViewController.h; sourceTree = ""; }; EAC693CC19EC94F700F0627B /* ListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ListViewController.m; sourceTree = ""; }; EAC693CD19EC94F700F0627B /* ListViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ListViewController.xib; sourceTree = ""; }; EAC693CE19EC94F700F0627B /* LoginViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoginViewController.h; sourceTree = ""; }; EAC693CF19EC94F700F0627B /* LoginViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LoginViewController.m; sourceTree = ""; }; EAC693D019EC94F700F0627B /* LoginViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LoginViewController.xib; sourceTree = ""; }; EAC693D719EC95A700F0627B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = InfoPlist.strings; sourceTree = ""; }; EAC693DC19EC95C800F0627B /* ViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ViewController.xib; sourceTree = ""; }; F8D62AFA66EC41C7B554BB8B /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ EAC6939A19EC94C000F0627B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 0B3B61448B57250B0026C6F1 /* libPods.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0665AB3D930148AD9C6AF02D /* Frameworks */ = { isa = PBXGroup; children = ( F8D62AFA66EC41C7B554BB8B /* libPods.a */, ); name = Frameworks; sourceTree = ""; }; 1CE09A3E15BC83048B99A0FD /* Pods */ = { isa = PBXGroup; children = ( 831424A6A73DAFA3814E1212 /* Pods.debug.xcconfig */, 7548EC47874C5A256237302C /* Pods.release.xcconfig */, ); name = Pods; sourceTree = ""; }; EAC6939419EC94C000F0627B = { isa = PBXGroup; children = ( EAC6939F19EC94C000F0627B /* Example */, EAC6939E19EC94C000F0627B /* Products */, 1CE09A3E15BC83048B99A0FD /* Pods */, 0665AB3D930148AD9C6AF02D /* Frameworks */, ); sourceTree = ""; }; EAC6939E19EC94C000F0627B /* Products */ = { isa = PBXGroup; children = ( EAC6939D19EC94C000F0627B /* Example.app */, ); name = Products; sourceTree = ""; }; EAC6939F19EC94C000F0627B /* Example */ = { isa = PBXGroup; children = ( EAC693CB19EC94F700F0627B /* ListViewController.h */, EAC693CC19EC94F700F0627B /* ListViewController.m */, EAC693CD19EC94F700F0627B /* ListViewController.xib */, EAC693CE19EC94F700F0627B /* LoginViewController.h */, EAC693CF19EC94F700F0627B /* LoginViewController.m */, EAC693D019EC94F700F0627B /* LoginViewController.xib */, EAC693D519EC95A700F0627B /* en.lproj */, EAC693C619EC94EC00F0627B /* AddViewController.h */, EAC693C719EC94EC00F0627B /* AddViewController.m */, EAC693C819EC94EC00F0627B /* AddViewController.xib */, EAC693A419EC94C000F0627B /* AppDelegate.h */, EAC693A519EC94C000F0627B /* AppDelegate.m */, EAC693A719EC94C000F0627B /* ViewController.h */, EAC693A819EC94C000F0627B /* ViewController.m */, EAC693DC19EC95C800F0627B /* ViewController.xib */, EAC693AD19EC94C000F0627B /* Images.xcassets */, EAC693A019EC94C000F0627B /* Supporting Files */, ); path = Example; sourceTree = ""; }; EAC693A019EC94C000F0627B /* Supporting Files */ = { isa = PBXGroup; children = ( EAC693A119EC94C000F0627B /* Info.plist */, EAC693A219EC94C000F0627B /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; EAC693D519EC95A700F0627B /* en.lproj */ = { isa = PBXGroup; children = ( EAC693D619EC95A700F0627B /* InfoPlist.strings */, ); path = en.lproj; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ EAC6939C19EC94C000F0627B /* Example */ = { isa = PBXNativeTarget; buildConfigurationList = EAC693C019EC94C000F0627B /* Build configuration list for PBXNativeTarget "Example" */; buildPhases = ( E824E0346DA8AEFE30585424 /* Check Pods Manifest.lock */, EAC6939919EC94C000F0627B /* Sources */, EAC6939A19EC94C000F0627B /* Frameworks */, EAC6939B19EC94C000F0627B /* Resources */, 7E1438145A4311B6E1F83379 /* Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Example; productName = Example; productReference = EAC6939D19EC94C000F0627B /* Example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ EAC6939519EC94C000F0627B /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0600; ORGANIZATIONNAME = "Jesse Bounds"; TargetAttributes = { EAC6939C19EC94C000F0627B = { CreatedOnToolsVersion = 6.0.1; }; }; }; buildConfigurationList = EAC6939819EC94C000F0627B /* Build configuration list for PBXProject "Example" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = EAC6939419EC94C000F0627B; productRefGroup = EAC6939E19EC94C000F0627B /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( EAC6939C19EC94C000F0627B /* Example */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ EAC6939B19EC94C000F0627B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( EAC693D419EC94F700F0627B /* LoginViewController.xib in Resources */, EAC693CA19EC94EC00F0627B /* AddViewController.xib in Resources */, EAC693AE19EC94C000F0627B /* Images.xcassets in Resources */, EAC693D219EC94F700F0627B /* ListViewController.xib in Resources */, EAC693DD19EC95C800F0627B /* ViewController.xib in Resources */, EAC693DA19EC95A700F0627B /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 7E1438145A4311B6E1F83379 /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; showEnvVarsInLog = 0; }; E824E0346DA8AEFE30585424 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ EAC6939919EC94C000F0627B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( EAC693D119EC94F700F0627B /* ListViewController.m in Sources */, EAC693A919EC94C000F0627B /* ViewController.m in Sources */, EAC693D319EC94F700F0627B /* LoginViewController.m in Sources */, EAC693A619EC94C000F0627B /* AppDelegate.m in Sources */, EAC693C919EC94EC00F0627B /* AddViewController.m in Sources */, EAC693A319EC94C000F0627B /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ EAC693D619EC95A700F0627B /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( EAC693D719EC95A700F0627B /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ EAC693BE19EC94C000F0627B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 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; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; EAC693BF19EC94C000F0627B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 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; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; EAC693C119EC94C000F0627B /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 831424A6A73DAFA3814E1212 /* Pods.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Example/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; EAC693C219EC94C000F0627B /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7548EC47874C5A256237302C /* Pods.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Example/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ EAC6939819EC94C000F0627B /* Build configuration list for PBXProject "Example" */ = { isa = XCConfigurationList; buildConfigurations = ( EAC693BE19EC94C000F0627B /* Debug */, EAC693BF19EC94C000F0627B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; EAC693C019EC94C000F0627B /* Build configuration list for PBXNativeTarget "Example" */ = { isa = XCConfigurationList; buildConfigurations = ( EAC693C119EC94C000F0627B /* Debug */, EAC693C219EC94C000F0627B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = EAC6939519EC94C000F0627B /* Project object */; } ================================================ FILE: Example/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/Example/Example.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/Example/Podfile ================================================ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '7.1' pod 'ObjectiveDDP', :git => 'https://github.com/boundsj/ObjectiveDDP.git', :branch => 'master' ================================================ FILE: Example/Example/example.txt ================================================ if you intend to do meteor auth with your ios client then add -lcrypto to other linker flags in settings ================================================ FILE: Example/swiftExample/Podfile ================================================ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '7.1' pod 'ObjectiveDDP', :git => 'https://github.com/boundsj/ObjectiveDDP.git', :branch => 'master' ================================================ FILE: Example/swiftExample/bridge.m ================================================ // // bridge.m // swiftddp // // Created by Michael Arthur on 7/6/14. // Copyright (c) 2014 . All rights reserved. // #import #import "MeteorClient.h" #import "ObjectiveDDP.h" #import MeteorClient* initialiseMeteor(NSString* version, NSString* endpoint) { MeteorClient *meteorClient = [[MeteorClient alloc] initWithDDPVersion:version]; ObjectiveDDP *ddp = [[ObjectiveDDP alloc] initWithURLString:endpoint delegate:meteorClient]; meteorClient.ddp = ddp; [meteorClient.ddp connectWebSocket]; return meteorClient; } ================================================ FILE: Example/swiftExample/swiftddp/AddViewController.swift ================================================ // // AddViewController.swift // swiftddp // // Created by Michael Arthur on 13/08/14. // Copyright (c) 2014. All rights reserved. // import Foundation import UIKit class AddViewController : UIViewController { @IBOutlet weak var messageTextView: UITextView! var delegate:AddViewControllerDelegate! required init(coder aDecoder: NSCoder) { fatalError("NSCoding not supported") } override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } override func touchesBegan(touches: Set, withEvent event: UIEvent?) { self.view.endEditing(true) } @IBAction func didTouchAddButton(sender: AnyObject!) { self.delegate.didAddThing(self.messageTextView.text) } } protocol AddViewControllerDelegate { func didAddThing(message:NSString!) } ================================================ FILE: Example/swiftExample/swiftddp/AddViewController.xib ================================================ ================================================ FILE: Example/swiftExample/swiftddp/AppDelegate.swift ================================================ // // AppDelegate.swift // swiftddp // // Created by Michael Arthur on 7/6/14. // Copyright (c) 2014 . All rights reserved. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var navController: UINavigationController! // Override point for customization after application launch. creates our singleton var meteorClient = initialiseMeteor("pre2", "wss://ddptester.meteor.com/websocket"); func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { meteorClient.addSubscription("things") meteorClient.addSubscription("lists") let loginController:LoginViewController = LoginViewController(nibName: "LoginViewController", bundle: nil) loginController.meteor = self.meteorClient self.navController = UINavigationController(rootViewController:loginController) self.navController.navigationBarHidden = true //This needs to be modified to fix the screen size issue. (Currently a Bug) self.window = UIWindow(frame: UIScreen.mainScreen().bounds) self.window!.rootViewController = self.navController self.window!.makeKeyAndVisible() print(self.window?.frame) NSNotificationCenter.defaultCenter().addObserver(self, selector: "reportConnection", name: MeteorClientDidConnectNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: "reportDisconnection", name: MeteorClientDidDisconnectNotification, object: nil) return true } func reportConnection() { print("================> connected to server!") } func reportDisconnection() { print("================> disconnected from server!") } func applicationWillResignActive(application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } func applicationDidEnterBackground(application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: Example/swiftExample/swiftddp/Example-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.rebounds.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Example/swiftExample/swiftddp/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UIRequiredDeviceCapabilities armv7 ================================================ FILE: Example/swiftExample/swiftddp/ListViewController.swift ================================================ // // ListViewController.swift // swiftddp // // Created by Michael Arthur on 13/08/14. // Copyright (c) 2014. All rights reserved. // import Foundation import UIKit class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet weak var tableview: UITableView! var meteor:MeteorClient! var lists:M13MutableOrderedDictionary! var userId:String? required init(coder aDecoder: NSCoder) { fatalError("NSCoding not supported") } init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!, meteor: MeteorClient!) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) self.meteor = meteor self.lists = self.meteor.collections["lists"] as! M13MutableOrderedDictionary } override func viewWillAppear(animated: Bool) { self.meteor.addObserver(self, forKeyPath: "websocketReady", options: NSKeyValueObservingOptions.New, context: nil) self.navigationItem.title = "My Lists" self.navigationController?.navigationBarHidden = false self.navigationItem.hidesBackButton = true let logoutButton:UIBarButtonItem = UIBarButtonItem(title: "Logout", style: UIBarButtonItemStyle.Plain, target: self, action: "logout") self.navigationItem.rightBarButtonItem = logoutButton NSNotificationCenter.defaultCenter().addObserver(self, selector: "didReceiveUpdate:", name: "lists_added", object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: "didReceiveUpdate:", name: "lists_removed", object: nil) } func didReceiveUpdate(notification:NSNotification) { self.tableview.reloadData() } func logout() { self.meteor.logout() self.navigationController?.popToRootViewControllerAnimated(true) } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return Int(self.lists.count()) } // // override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafeMutablePointer<()>) { // // if (keyPath == "websocketReady" && meteor.websocketReady) { // // } // } var selectedList:[String:String]! func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cellIdentifier = "list" var cell:UITableViewCell if let tmpCell: AnyObject = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) { cell = tmpCell as! UITableViewCell } else { cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: cellIdentifier) as UITableViewCell } selectedList = self.lists.objectAtIndex(UInt(indexPath.row)) as? [String:String] cell.textLabel?.text = selectedList["name"] let shareButton:UIButton = UIButton(type: UIButtonType.Custom) as UIButton shareButton.frame = CGRectMake(255.0, 5.0, 55.0, 34.0) shareButton.backgroundColor = UIColor.greenColor() shareButton.setTitle("Share", forState: UIControlState.Normal) shareButton.addTarget(self, action: "didClickShareButton:forEvent:", forControlEvents: .TouchUpInside) cell.addSubview(shareButton) return cell } var shareWithTF:UITextField! func didClickShareButton(sender:AnyObject!,forEvent event:UIEvent!) { let touch = event.allTouches()! as Set let location:CGPoint = touch.first!.locationInView(self.view) let view:UIView = UIView(frame: CGRectMake(0.0, location.y, 320.0, 100.0)) view.backgroundColor = UIColor.whiteColor() let shareWithTextField:UITextField = UITextField(frame: CGRectMake(10.0, 50.0, 240.0, 44.0)) shareWithTF = shareWithTextField shareWithTextField.borderStyle = UITextBorderStyle.Line let button:UIButton = UIButton(type: UIButtonType.Custom) as UIButton button.frame = CGRectMake(255.0, 50.0, 60.0, 44.0) button.backgroundColor = UIColor.greenColor() button.setTitle("Send", forState: UIControlState.Normal) button.addTarget(self, action: "didClickShareWithButton:forEvent:", forControlEvents: .TouchUpInside) view .addSubview(shareWithTextField) view .addSubview(button) let modalBackground:UIView = UIView(frame: self.view.frame) modalBackground.backgroundColor = UIColor.blackColor() modalBackground.alpha = 0.7 self.view .addSubview(modalBackground) self.view .addSubview(view) } func didClickShareWithButton(sender: AnyObject!, forEvent event:UIEvent!) { let id = selectedList["_id"] as String! let parameters = [["_id":id!], ["set": ["share_with":shareWithTF.text!]]] //This has to be an NSArray self.meteor.callMethodName("/lists/update", parameters: parameters, responseCallback: {(response, error) -> Void in if((error) != nil) { self.handleFailedShare(error) return } self.handleSuccessfulShare() } ) self.view.subviews.last?.removeFromSuperview() self.view.subviews.last?.removeFromSuperview() } func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { return true } func tableView(tableView: UITableView,commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath){ var list = self.lists.objectAtIndex(UInt(indexPath.row)) as! [String:AnyObject] let id = list["_id"] as! String self.meteor.callMethodName("/lists/remove", parameters: [["_id":id]], responseCallback: nil) } func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { var list = self.lists.objectAtIndex(UInt(indexPath.row)) as! [String:AnyObject] let viewController:ViewController = ViewController(nibNameOrNil: "ViewController", bundle: nil, meteor: self.meteor, listName: list["name"] as! String) viewController.userId = self.userId self.navigationController?.pushViewController(viewController, animated: true) } func handleSuccessfulShare() { UIAlertView(title: "Shared", message:"Success", delegate: nil, cancelButtonTitle: "Dismiss").show() } func handleFailedShare(error: NSError) { UIAlertView(title: "Meteor Todos", message:error.localizedDescription, delegate: nil, cancelButtonTitle: "Try Again").show() } } ================================================ FILE: Example/swiftExample/swiftddp/ListViewController.xib ================================================ ================================================ FILE: Example/swiftExample/swiftddp/LoginViewController.swift ================================================ // // LoginViewController.swift // swiftddp // // Created by Michael Arthur on 12/08/14. // Copyright (c) 2014. All rights reserved. // import Foundation import UIKit class LoginViewController: UIViewController { @IBOutlet weak var email: UITextField! @IBOutlet weak var password: UITextField! @IBOutlet weak var connectionStatusLight: UIImageView! @IBOutlet weak var connectionStatusText: UILabel! var meteor:MeteorClient! override func viewWillAppear(animated: Bool) { let observingOption = NSKeyValueObservingOptions.New meteor.addObserver(self, forKeyPath:"websocketReady", options: observingOption, context:nil) } override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) { if (keyPath == "websocketReady" && meteor.websocketReady) { connectionStatusText.text = "Connected to Todo Server" let image:UIImage = UIImage(named: "green_light.png")! connectionStatusLight.image = image } } @IBAction func didTapLoginButton(sender: AnyObject) { if (!meteor.websocketReady) { let notConnectedAlert = UIAlertView(title: "Connection Error", message: "Can't find the Todo server, try again", delegate: nil, cancelButtonTitle: "OK") notConnectedAlert.show() return } meteor.logonWithEmail(self.email.text, password: self.password.text) {(response, error) -> Void in if((error) != nil) { self.handleFailedAuth(error) return } self.handleSuccessfulAuth() } } func handleSuccessfulAuth() { let listViewController = ListViewController(nibName: "ListViewController", bundle: nil, meteor: self.meteor) listViewController.userId = self.meteor.userId self.navigationController?.pushViewController(listViewController, animated: true) } func handleFailedAuth(error: NSError) { UIAlertView(title: "Meteor Todos", message:error.localizedDescription, delegate: nil, cancelButtonTitle: "Try Again").show() } @IBAction func didTapSayHiButton(sender: AnyObject) { self.meteor.callMethodName("sayHelloTo", parameters:[self.email.text!]) {(response, error) -> Void in if((error) != nil) { self.handleFailedAuth(error) return } let message = response["result"] as! String UIAlertView(title: "Meteor Todos", message: message, delegate: nil, cancelButtonTitle:"Great").show() } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } override func touchesBegan(touches: Set, withEvent event: UIEvent?) { self.view.endEditing(true) } } ================================================ FILE: Example/swiftExample/swiftddp/LoginViewController.xib ================================================ ================================================ FILE: Example/swiftExample/swiftddp/ViewController.swift ================================================ // // ViewController.swift // swiftddp // // Created by Michael Arthur on 7/6/14. // Copyright (c) 2014. All rights reserved. // import UIKit class ViewController: UIViewController,UITableViewDataSource, AddViewControllerDelegate { var meteor:MeteorClient! var listName:String! var userId:String! @IBOutlet weak var tableview: UITableView! required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! } override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } init(nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!, meteor: MeteorClient!, listName:String!) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) // if(self != nil) { self.meteor = meteor self.listName = listName //} } override func viewWillAppear(animated: Bool) { self.navigationItem.title = self.listName let addButton:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: "didTouchAdd:") self.navigationItem.setRightBarButtonItem(addButton, animated: true) NSNotificationCenter.defaultCenter().addObserver(self, selector: "didReceiveUpdate:", name: "things_added", object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: "didReceiveUpdate:", name: "things_changed", object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: "didReceiveUpdate:", name: "things_removed", object: nil) } override func viewWillDisappear(animated: Bool) { NSNotificationCenter.defaultCenter().removeObserver(self) } func didReceiveUpdate(notification:NSNotification) { self.tableview.reloadData() } func computedList() -> NSArray { let pred:NSPredicate = NSPredicate(format: "(listName like %@)", self.listName) let temp = self.meteor.collections["things"] as! M13MutableOrderedDictionary let temp2 = temp.allObjects() as NSArray return temp2.filteredArrayUsingPredicate(pred) } @IBAction func didTouchAdd(sender: AnyObject) { let addController = AddViewController(nibName: "AddViewController", bundle: nil) addController.delegate = self self.presentViewController(addController, animated: true, completion: nil) } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if(self.meteor.collections["things"] != nil){ return self.computedList().count } return 0 } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cellIdentifier:String! = "thing" var cell:UITableViewCell if let tmpCell: AnyObject = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) { cell = tmpCell as! UITableViewCell } else { cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: cellIdentifier) as UITableViewCell } if(self.meteor.collections["things"] != nil){ let thing:NSDictionary = self.computedList()[indexPath.row] as! NSDictionary cell.textLabel?.text = thing["msg"] as? String return cell } cell.textLabel?.text = "dummy" return cell } func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { return true } func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if(editingStyle == UITableViewCellEditingStyle.Delete) { //If statement prevents crash if(self.meteor.collections["things"] != nil){ let thing:NSDictionary = self.computedList()[indexPath.row] as! NSDictionary let thingy = thing["_id"] as! String self.meteor.callMethodName("/things/remove", parameters: [["_id":thingy]], responseCallback: nil) } } } func didAddThing(message: NSString!) { self.dismissViewControllerAnimated(true, completion: nil) let parameters:NSArray = [["_id": NSUUID().UUIDString, "msg":message, "owner":self.userId, "listName":self.listName]] self.meteor.callMethodName("/things/insert", parameters: parameters as [AnyObject], responseCallback: nil) } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: Example/swiftExample/swiftddp/ViewController.xib ================================================ ================================================ FILE: Example/swiftExample/swiftddp/images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Example/swiftExample/swiftddp/images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "subtype" : "retina4", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Example/swiftExample/swiftddp-Bridging-Header.h ================================================ // // Use this file to import your target's public headers that you would like to expose to Swift. // #import #import "MeteorClient.h" #import "ObjectiveDDP.h" #import #import #import "bridge.m" ================================================ FILE: Example/swiftExample/swiftddp.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 028E8AFD1969A9E8006F2115 /* bridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 028E8AFC1969A9E8006F2115 /* bridge.m */; }; 02E399621969A81300E2FE77 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E399611969A81300E2FE77 /* AppDelegate.swift */; }; 02E399641969A81300E2FE77 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E399631969A81300E2FE77 /* ViewController.swift */; }; 02E399691969A81300E2FE77 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 02E399681969A81300E2FE77 /* Images.xcassets */; }; 02E399751969A81300E2FE77 /* swiftddpTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E399741969A81300E2FE77 /* swiftddpTests.swift */; }; EA1C0B47199AEFC4005545A7 /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C0B46199AEFC4005545A7 /* ListViewController.swift */; }; EA8054BE199B5C3B00805893 /* AddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8054BD199B5C3B00805893 /* AddViewController.swift */; }; EA97639619998E2E00866844 /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = EA97639519998E2E00866844 /* ViewController.xib */; }; EA97639A19998E4500866844 /* AddViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = EA97639719998E4500866844 /* AddViewController.xib */; }; EA97639B19998E4500866844 /* ListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = EA97639819998E4500866844 /* ListViewController.xib */; }; EA97639C19998E4500866844 /* LoginViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = EA97639919998E4500866844 /* LoginViewController.xib */; }; EA9763A219998E7600866844 /* green_light.png in Resources */ = {isa = PBXBuildFile; fileRef = EA97639D19998E7600866844 /* green_light.png */; }; EA9763A319998E7600866844 /* green_light@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = EA97639E19998E7600866844 /* green_light@2x.png */; }; EA9763A419998E7600866844 /* red_light.png in Resources */ = {isa = PBXBuildFile; fileRef = EA97639F19998E7600866844 /* red_light.png */; }; EA9763A519998E7600866844 /* red_light@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = EA9763A019998E7600866844 /* red_light@2x.png */; }; EA9763A619998E7600866844 /* Example-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = EA9763A119998E7600866844 /* Example-Info.plist */; }; EA9763A81999905400866844 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9763A71999905400866844 /* LoginViewController.swift */; }; F085ED994E3844018475E51E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EBABBCC4B77C49EAA6854302 /* libPods.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 02E3996F1969A81300E2FE77 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 02E399541969A81300E2FE77 /* Project object */; proxyType = 1; remoteGlobalIDString = 02E3995B1969A81300E2FE77; remoteInfo = swiftddp; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 028E8AFB1969A9E8006F2115 /* swiftddp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "swiftddp-Bridging-Header.h"; sourceTree = ""; }; 028E8AFC1969A9E8006F2115 /* bridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = bridge.m; sourceTree = ""; }; 02E3995C1969A81300E2FE77 /* swiftddp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = swiftddp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 02E399601969A81300E2FE77 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 02E399611969A81300E2FE77 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 02E399631969A81300E2FE77 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 02E399681969A81300E2FE77 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 02E3996E1969A81300E2FE77 /* swiftddpTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = swiftddpTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 02E399731969A81300E2FE77 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 02E399741969A81300E2FE77 /* swiftddpTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = swiftddpTests.swift; sourceTree = ""; }; 68DC64A1F8BABCBDDEDFBCE4 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; EA1C0B46199AEFC4005545A7 /* ListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewController.swift; sourceTree = ""; }; EA8054BD199B5C3B00805893 /* AddViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddViewController.swift; sourceTree = ""; }; EA97639519998E2E00866844 /* ViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ViewController.xib; sourceTree = ""; }; EA97639719998E4500866844 /* AddViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AddViewController.xib; sourceTree = ""; }; EA97639819998E4500866844 /* ListViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ListViewController.xib; sourceTree = ""; }; EA97639919998E4500866844 /* LoginViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LoginViewController.xib; sourceTree = ""; }; EA97639D19998E7600866844 /* green_light.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = green_light.png; sourceTree = ""; }; EA97639E19998E7600866844 /* green_light@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "green_light@2x.png"; sourceTree = ""; }; EA97639F19998E7600866844 /* red_light.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = red_light.png; sourceTree = ""; }; EA9763A019998E7600866844 /* red_light@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "red_light@2x.png"; sourceTree = ""; }; EA9763A119998E7600866844 /* Example-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Example-Info.plist"; sourceTree = ""; }; EA9763A71999905400866844 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; EBABBCC4B77C49EAA6854302 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; FAD4C15CAE906A029B1F8C9C /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 02E399591969A81300E2FE77 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F085ED994E3844018475E51E /* libPods.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 02E3996B1969A81300E2FE77 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 02E399531969A81300E2FE77 = { isa = PBXGroup; children = ( 028E8AFB1969A9E8006F2115 /* swiftddp-Bridging-Header.h */, 028E8AFC1969A9E8006F2115 /* bridge.m */, 02E3995E1969A81300E2FE77 /* swiftddp */, 02E399711969A81300E2FE77 /* swiftddpTests */, 02E3995D1969A81300E2FE77 /* Products */, 71997703989144BE8A6BA401 /* Frameworks */, 2ADD1B4FCCC14601FC099EA0 /* Pods */, ); sourceTree = ""; }; 02E3995D1969A81300E2FE77 /* Products */ = { isa = PBXGroup; children = ( 02E3995C1969A81300E2FE77 /* swiftddp.app */, 02E3996E1969A81300E2FE77 /* swiftddpTests.xctest */, ); name = Products; sourceTree = ""; }; 02E3995E1969A81300E2FE77 /* swiftddp */ = { isa = PBXGroup; children = ( EA8054BD199B5C3B00805893 /* AddViewController.swift */, EA97639719998E4500866844 /* AddViewController.xib */, EA1C0B46199AEFC4005545A7 /* ListViewController.swift */, EA97639819998E4500866844 /* ListViewController.xib */, EA97639919998E4500866844 /* LoginViewController.xib */, EA9763A71999905400866844 /* LoginViewController.swift */, EA97639519998E2E00866844 /* ViewController.xib */, 02E399631969A81300E2FE77 /* ViewController.swift */, 02E399611969A81300E2FE77 /* AppDelegate.swift */, 02E399681969A81300E2FE77 /* Images.xcassets */, 02E3995F1969A81300E2FE77 /* Supporting Files */, ); path = swiftddp; sourceTree = ""; }; 02E3995F1969A81300E2FE77 /* Supporting Files */ = { isa = PBXGroup; children = ( EA97639D19998E7600866844 /* green_light.png */, EA97639E19998E7600866844 /* green_light@2x.png */, EA97639F19998E7600866844 /* red_light.png */, EA9763A019998E7600866844 /* red_light@2x.png */, EA9763A119998E7600866844 /* Example-Info.plist */, 02E399601969A81300E2FE77 /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; 02E399711969A81300E2FE77 /* swiftddpTests */ = { isa = PBXGroup; children = ( 02E399741969A81300E2FE77 /* swiftddpTests.swift */, 02E399721969A81300E2FE77 /* Supporting Files */, ); path = swiftddpTests; sourceTree = ""; }; 02E399721969A81300E2FE77 /* Supporting Files */ = { isa = PBXGroup; children = ( 02E399731969A81300E2FE77 /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; 2ADD1B4FCCC14601FC099EA0 /* Pods */ = { isa = PBXGroup; children = ( FAD4C15CAE906A029B1F8C9C /* Pods.debug.xcconfig */, 68DC64A1F8BABCBDDEDFBCE4 /* Pods.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 71997703989144BE8A6BA401 /* Frameworks */ = { isa = PBXGroup; children = ( EBABBCC4B77C49EAA6854302 /* libPods.a */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 02E3995B1969A81300E2FE77 /* swiftddp */ = { isa = PBXNativeTarget; buildConfigurationList = 02E399781969A81300E2FE77 /* Build configuration list for PBXNativeTarget "swiftddp" */; buildPhases = ( F9079674B49640559CB2094C /* Check Pods Manifest.lock */, 02E399581969A81300E2FE77 /* Sources */, 02E399591969A81300E2FE77 /* Frameworks */, 02E3995A1969A81300E2FE77 /* Resources */, 6DD3D46A18134164A66CCBBE /* Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = swiftddp; productName = swiftddp; productReference = 02E3995C1969A81300E2FE77 /* swiftddp.app */; productType = "com.apple.product-type.application"; }; 02E3996D1969A81300E2FE77 /* swiftddpTests */ = { isa = PBXNativeTarget; buildConfigurationList = 02E3997B1969A81300E2FE77 /* Build configuration list for PBXNativeTarget "swiftddpTests" */; buildPhases = ( 02E3996A1969A81300E2FE77 /* Sources */, 02E3996B1969A81300E2FE77 /* Frameworks */, 02E3996C1969A81300E2FE77 /* Resources */, ); buildRules = ( ); dependencies = ( 02E399701969A81300E2FE77 /* PBXTargetDependency */, ); name = swiftddpTests; productName = swiftddpTests; productReference = 02E3996E1969A81300E2FE77 /* swiftddpTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 02E399541969A81300E2FE77 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; LastUpgradeCheck = 0700; ORGANIZATIONNAME = RainHaven; TargetAttributes = { 02E3995B1969A81300E2FE77 = { CreatedOnToolsVersion = 6.0; }; 02E3996D1969A81300E2FE77 = { CreatedOnToolsVersion = 6.0; TestTargetID = 02E3995B1969A81300E2FE77; }; }; }; buildConfigurationList = 02E399571969A81300E2FE77 /* Build configuration list for PBXProject "swiftddp" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 02E399531969A81300E2FE77; productRefGroup = 02E3995D1969A81300E2FE77 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 02E3995B1969A81300E2FE77 /* swiftddp */, 02E3996D1969A81300E2FE77 /* swiftddpTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 02E3995A1969A81300E2FE77 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( EA9763A219998E7600866844 /* green_light.png in Resources */, EA97639C19998E4500866844 /* LoginViewController.xib in Resources */, EA9763A419998E7600866844 /* red_light.png in Resources */, EA9763A619998E7600866844 /* Example-Info.plist in Resources */, EA97639A19998E4500866844 /* AddViewController.xib in Resources */, EA97639619998E2E00866844 /* ViewController.xib in Resources */, EA97639B19998E4500866844 /* ListViewController.xib in Resources */, 02E399691969A81300E2FE77 /* Images.xcassets in Resources */, EA9763A319998E7600866844 /* green_light@2x.png in Resources */, EA9763A519998E7600866844 /* red_light@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 02E3996C1969A81300E2FE77 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 6DD3D46A18134164A66CCBBE /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; showEnvVarsInLog = 0; }; F9079674B49640559CB2094C /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 02E399581969A81300E2FE77 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( EA9763A81999905400866844 /* LoginViewController.swift in Sources */, 028E8AFD1969A9E8006F2115 /* bridge.m in Sources */, EA8054BE199B5C3B00805893 /* AddViewController.swift in Sources */, 02E399641969A81300E2FE77 /* ViewController.swift in Sources */, EA1C0B47199AEFC4005545A7 /* ListViewController.swift in Sources */, 02E399621969A81300E2FE77 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 02E3996A1969A81300E2FE77 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 02E399751969A81300E2FE77 /* swiftddpTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 02E399701969A81300E2FE77 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 02E3995B1969A81300E2FE77 /* swiftddp */; targetProxy = 02E3996F1969A81300E2FE77 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 02E399761969A81300E2FE77 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 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; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.0; METAL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 02E399771969A81300E2FE77 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 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; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.0; METAL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 02E399791969A81300E2FE77 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = FAD4C15CAE906A029B1F8C9C /* Pods.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = swiftddp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "bounds.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "swiftddp-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 02E3997A1969A81300E2FE77 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 68DC64A1F8BABCBDDEDFBCE4 /* Pods.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = swiftddp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 7.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "bounds.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "swiftddp-Bridging-Header.h"; }; name = Release; }; 02E3997C1969A81300E2FE77 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/swiftddp.app/swiftddp"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = swiftddpTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; METAL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "bounds.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; }; name = Debug; }; 02E3997D1969A81300E2FE77 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/swiftddp.app/swiftddp"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); INFOPLIST_FILE = swiftddpTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; METAL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "bounds.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 02E399571969A81300E2FE77 /* Build configuration list for PBXProject "swiftddp" */ = { isa = XCConfigurationList; buildConfigurations = ( 02E399761969A81300E2FE77 /* Debug */, 02E399771969A81300E2FE77 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 02E399781969A81300E2FE77 /* Build configuration list for PBXNativeTarget "swiftddp" */ = { isa = XCConfigurationList; buildConfigurations = ( 02E399791969A81300E2FE77 /* Debug */, 02E3997A1969A81300E2FE77 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 02E3997B1969A81300E2FE77 /* Build configuration list for PBXNativeTarget "swiftddpTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 02E3997C1969A81300E2FE77 /* Debug */, 02E3997D1969A81300E2FE77 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 02E399541969A81300E2FE77 /* Project object */; } ================================================ FILE: Example/swiftExample/swiftddp.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/swiftExample/swiftddp.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/swiftExample/swiftddpTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: Example/swiftExample/swiftddpTests/swiftddpTests.swift ================================================ // // swiftddpTests.swift // swiftddpTests // // Created by Michael Arthur on 7/6/14. // Copyright (c) 2014 . All rights reserved. // import XCTest class swiftddpTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. XCTAssert(true, "Pass") } func testPerformanceExample() { // This is an example of a performance test case. self.measureBlock() { // Put the code you want to measure the time of here. } } } ================================================ FILE: Example/todos/.meteor/.finished-upgraders ================================================ # This file contains information which helps Meteor properly upgrade your # app when you run 'meteor update'. You should check it into version control # with your project. notices-for-0.9.0 notices-for-0.9.1 0.9.4-platform-file ================================================ FILE: Example/todos/.meteor/.gitignore ================================================ local ================================================ FILE: Example/todos/.meteor/.id ================================================ # This file contains a token that is unique to your project. # Check it into your repository along with the rest of this directory. # It can be used for purposes such as: # - ensuring you don't accidentally deploy one app on top of another # - providing package authors with aggregated statistics 1tbs2bs1n5hbv0mlpjek ================================================ FILE: Example/todos/.meteor/packages ================================================ # Meteor packages used by this project, one per line. # # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. accounts-password accounts-ui bootstrap insecure standard-app-packages ================================================ FILE: Example/todos/.meteor/platforms ================================================ server browser ================================================ FILE: Example/todos/.meteor/release ================================================ METEOR@1.0 ================================================ FILE: Example/todos/.meteor/versions ================================================ accounts-base@1.1.2 accounts-password@1.0.4 accounts-ui-unstyled@1.1.4 accounts-ui@1.1.3 application-configuration@1.0.3 autoupdate@1.1.3 base64@1.0.1 binary-heap@1.0.1 blaze-tools@1.0.1 blaze@2.0.3 boilerplate-generator@1.0.1 bootstrap@1.0.1 callback-hook@1.0.1 check@1.0.2 ctl-helper@1.0.4 ctl@1.0.2 ddp@1.0.11 deps@1.0.5 ejson@1.0.4 email@1.0.4 fastclick@1.0.1 follower-livedata@1.0.2 geojson-utils@1.0.1 html-tools@1.0.2 htmljs@1.0.2 http@1.0.8 id-map@1.0.1 insecure@1.0.1 jquery@1.0.1 json@1.0.1 launch-screen@1.0.0 less@1.0.11 livedata@1.0.11 localstorage@1.0.1 logging@1.0.5 meteor-platform@1.2.0 meteor@1.1.3 minifiers@1.1.2 minimongo@1.0.5 mobile-status-bar@1.0.1 mongo@1.0.8 npm-bcrypt@0.7.7 observe-sequence@1.0.3 ordered-dict@1.0.1 random@1.0.1 reactive-dict@1.0.4 reactive-var@1.0.3 reload@1.1.1 retry@1.0.1 routepolicy@1.0.2 service-configuration@1.0.2 session@1.0.4 sha@1.0.1 spacebars-compiler@1.0.3 spacebars@1.0.3 srp@1.0.1 standard-app-packages@1.0.3 templating@1.0.9 tracker@1.0.3 ui@1.0.4 underscore@1.0.1 url@1.0.2 webapp-hashing@1.0.1 webapp@1.1.4 ================================================ FILE: Example/todos/server.css ================================================ /* CSS declarations go here */ ================================================ FILE: Example/todos/server.html ================================================ TodoS {{> header }} {{> main }} ================================================ FILE: Example/todos/server.js ================================================ Things = new Meteor.Collection('things'); Lists = new Meteor.Collection('lists'); if (Meteor.isClient) { Meteor.subscribe('things'); Meteor.subscribe('lists'); Template.main.helpers({ things: function () { return Things.find({listName: Session.get('list-name')}); }, lists: function() { return Lists.find(); } }); Template.main.events({ 'click .add-item': function () { $('.todo-input').removeClass('hidden'); }, 'blur .todo-input': function() { var todoInput = $('.todo-input'); todoInput.addClass('hidden'); Things.insert({ msg: todoInput.val(), owner: Meteor.userId(), listName: Session.get('list-name'), share_with: Session.get('share-with'), listOwner: Session.get('list-owner') }); }, 'click .remove-item': function() { Things.remove(this._id); }, 'click .add-list': function () { $('.list-input').removeClass('hidden'); }, 'blur .list-input': function() { var listInput = $('.list-input'); listInput.addClass('hidden'); Lists.insert({ name: listInput.val(), owner: Meteor.userId() }); Session.set('list-name', listInput.val()); }, 'click .list-name': function() { Session.set('list-name', this.name); Session.set('share-with', this.share_with); Session.set('list-owner', this.owner); }, 'click .remove-list': function() { Lists.remove(this._id); }, 'click .share-list': function() { var self = this; $("." + this._id).popover('destroy'); var content = 'Send'; $("." + this._id).popover({ title: 'Invite a Friend! ' + this._id, content: content, html: true }); $("." + this._id).popover('show'); $(".cancel-share").click(function() { $("." + self._id).popover('destroy'); }); $(".send-share").click(function() { $("." + self._id).popover('destroy'); var email = $('.share-input').val(); Lists.update(self._id, {$set: {share_with: email}}); Meteor.call('updateRelatedThings', self.name, email); }); } }); } if (Meteor.isServer) { Meteor.startup(function () { var getEmailFromUserId = function(userId) { var email = Meteor.users.findOne({_id: userId }, {emails: 1}); if (email && email['emails']) { return email['emails'][0]['address']; } return null; } var getUserIdFromEmail = function(email) { return Meteor.users.findOne({"emails.address": {$in: [email]}}, {_id: 1}) } Meteor.publish('things', function() { return Things.find({$or: [{"share_with": getEmailFromUserId(this.userId)}, {"owner": this.userId}, {"listOwner": this.userId}]}); }); Meteor.publish('lists', function() { return Lists.find({$or: [{"share_with": getEmailFromUserId(this.userId)}, {"owner": this.userId}]}); }); Things.allow({ insert: function(userId, doc) { return (userId && doc.owner === userId); }, remove: function(userId, doc) { return (userId && (doc.owner === userId || doc.share_with === getEmailFromUserId(userId) || doc.listOwner === userId)); } }); Lists.allow({ insert: function(userId, doc) { return (userId && doc.owner === userId); }, remove: function(userId, doc) { var allow = (userId && doc.owner === userId); deleteRelatedThings(allow, doc); return allow; }, update: function(userId, doc) { return (userId && doc.owner === userId); } }); }); Meteor.methods({ updateRelatedThings: function(listName, shareWith) { Things.update({listName: listName}, {$set: {share_with: shareWith}}, {multi: true}); }, sayHelloTo: function(name) { return "hello " + name; } }); var deleteRelatedThings = function(allow, doc) { // this breaks because allow is done for the list, not for the listName // we could lose all Things that have a name "blarg" even if "blarg" // is on two totally diff lists if (allow) { Things.remove({listName: doc.name}); } } } ================================================ FILE: LICENSE.txt ================================================ The MIT License (MIT) Copyright (c) 2013 Jesse Bounds Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: ObjectiveDDP/BSONIdGenerator.h ================================================ #import @interface BSONIdGenerator : NSObject + (NSString *)generate; @end ================================================ FILE: ObjectiveDDP/BSONIdGenerator.m ================================================ #import "BSONIdGenerator.h" @implementation BSONIdGenerator static NSInteger methodCallCount = 1; + (NSString *)generate { return [NSString stringWithFormat:@"%ld", (long)methodCallCount++]; } @end ================================================ FILE: ObjectiveDDP/DependencyProvider.h ================================================ #import @class SRWebSocket; @interface DependencyProvider : NSObject + (DependencyProvider *)sharedProvider; - (SRWebSocket *)provideSRWebSocketWithRequest:(NSURLRequest *)request; @end ================================================ FILE: ObjectiveDDP/DependencyProvider.m ================================================ #import "DependencyProvider.h" #import @implementation DependencyProvider static DependencyProvider *sharedProvider = nil; + (DependencyProvider *)sharedProvider { if (!sharedProvider) { sharedProvider = [[DependencyProvider alloc] init]; } return sharedProvider; } - (SRWebSocket *)provideSRWebSocketWithRequest:(NSURLRequest *)request { return [[SRWebSocket alloc] initWithURLRequest:request]; } @end ================================================ FILE: ObjectiveDDP/MeteorClient+Parsing.m ================================================ #import "MeteorClient+Private.h" #import @implementation MeteorClient (Parsing) - (void)_handleMethodResultMessageWithMessageId:(NSString *)messageId message:(NSDictionary *)message msg:(NSString *)msg { if ([_methodIds containsObject:messageId]) { if([msg isEqualToString:@"result"]) { MeteorClientMethodCallback callback = _responseCallbacks[messageId]; id response; if(message[@"error"]) { NSDictionary *errorDesc = message[@"error"]; NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorDesc[@"message"]}; NSError *responseError = [NSError errorWithDomain:errorDesc[@"errorType"] code:[errorDesc[@"error"] integerValue] userInfo:userInfo]; if (callback) { callback(nil, responseError); } response = responseError; } else { if (callback) { callback(message, nil); } } NSString *notificationName = [NSString stringWithFormat:@"response_%@", messageId]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:response]; [_responseCallbacks removeObjectForKey:messageId]; [_methodIds removeObject:messageId]; } } } - (void)_handleAddedMessage:(NSDictionary *)message msg:(NSString *)msg { if ([msg isEqualToString:@"added"] && message[@"collection"]) { NSDictionary *object = [self _parseObjectAndAddToCollection:message]; NSString *notificationName = [NSString stringWithFormat:@"%@_added", message[@"collection"]]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:object]; [[NSNotificationCenter defaultCenter] postNotificationName:@"added" object:self userInfo:object]; } } - (void)_handleAddedBeforeMessage:(NSDictionary *)message msg:(NSString *)msg { if ([msg isEqualToString:@"addedBefore"] && message[@"collection"]) { NSDictionary *object = [self _parseObjectAndAddToCollection:message beforeId:[message valueForKey:@"before"]]; NSString *notificationName = [NSString stringWithFormat:@"%@_addedBefore", message[@"collection"]]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:object]; [[NSNotificationCenter defaultCenter] postNotificationName:@"addedBefore" object:self userInfo:object]; } } - (void)_handleMovedBeforeMessage:(NSDictionary *)message msg:(NSString *)msg { if ([msg isEqualToString:@"movedBefore"] && message[@"collection"]) { NSDictionary *object = [self _parseMovedBefore:message]; NSString *notificationName = [NSString stringWithFormat:@"%@_movedBefore", message[@"collection"]]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:object]; [[NSNotificationCenter defaultCenter] postNotificationName:@"movedBefore" object:self userInfo:object]; } } - (NSDictionary *)_parseMovedBefore:(NSDictionary *)message { NSMutableDictionary *object = [NSMutableDictionary dictionaryWithDictionary:@{@"_id": message[@"id"]}]; M13MutableOrderedDictionary *collection = self.collections[message[@"collection"]]; NSString * beforeDocumentId = [message valueForKey:@"before"]; //if document doesn't exist, add it to end if (!beforeDocumentId) { [collection addObject:object pairedWithKey:message[@"id"]]; } //move document to before index else{ NSUInteger currentIndex = [collection indexOfKey:message[@"id"]]; NSUInteger moveToIndex = [collection indexOfKey:beforeDocumentId]; if (currentIndex != NSNotFound && moveToIndex != NSNotFound) { //remove object from its current place object = [collection objectForKey:message[@"id"]]; [collection removeObjectForKey:message[@"id"]]; //insert object at before index [collection insertObject:object pairedWithKey:message[@"id"] atIndex:moveToIndex]; } } return object; } - (NSUInteger)_indexForDocumentId:(NSString*)documentId inCollection:(NSMutableArray*)collection { //get index of document to insert before NSUInteger documentIndex = [collection indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { if ([[obj valueForKey:@"_id"] isEqualToString:documentId]) { *stop = YES; return YES; } return NO; }]; if (documentIndex != NSNotFound) { NSLog(@"The title of category at index %lu is %@", (unsigned long)documentIndex, [[collection objectAtIndex:documentIndex] valueForKey:@"_id"]); } else { NSLog(@"Not found"); } return documentIndex; } - (NSDictionary *)_parseObjectAndAddToCollection:(NSDictionary *)message { NSMutableDictionary *object = [NSMutableDictionary dictionaryWithDictionary:@{@"_id": message[@"id"]}]; for (id key in message[@"fields"]) { object[key] = message[@"fields"][key]; } if (!self.collections[message[@"collection"]]) { self.collections[message[@"collection"]] = [M13MutableOrderedDictionary new]; } M13MutableOrderedDictionary *collection = self.collections[message[@"collection"]]; [collection addObject:object pairedWithKey:message[@"id"]]; return object; } - (NSDictionary *)_parseObjectAndAddToCollection:(NSDictionary *)message beforeId:(NSString*)documentId { NSMutableDictionary *object = [NSMutableDictionary dictionaryWithDictionary:@{@"_id": message[@"id"]}]; for (id key in message[@"fields"]) { object[key] = message[@"fields"][key]; } M13MutableOrderedDictionary *collection = self.collections[message[@"collection"]]; //if documentId, insert at beforeId index if (documentId) { NSUInteger documentIndex = [collection indexOfKey:documentId]; // _indexForDocumentId:documentId inCollection:collection]; if (documentIndex != NSNotFound) { [collection insertObject:object pairedWithKey:message[@"id"] atIndex:documentIndex]; } } //if no documentId, insert at end else{ [collection addObject:object pairedWithKey:message[@"id"]]; } return object; } - (void)_handleRemovedMessage:(NSDictionary *)message msg:(NSString *)msg { if ([msg isEqualToString:@"removed"] && message[@"collection"]) { [self _parseRemoved:message]; NSString *notificationName = [NSString stringWithFormat:@"%@_removed", message[@"collection"]]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:@{@"_id": message[@"id"]}]; [[NSNotificationCenter defaultCenter] postNotificationName:@"removed" object:self]; } } - (void)_parseRemoved:(NSDictionary *)message { M13MutableOrderedDictionary *collection = self.collections[message[@"collection"]]; [collection removeObjectForKey:message[@"id"]]; } - (void)_handleChangedMessage:(NSDictionary *)message msg:(NSString *)msg { if ([msg isEqualToString:@"changed"] && message[@"collection"]) { NSDictionary *object = [self _parseObjectAndUpdateCollection:message]; NSString *notificationName = [NSString stringWithFormat:@"%@_changed", message[@"collection"]]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:object]; [[NSNotificationCenter defaultCenter] postNotificationName:@"changed" object:self userInfo:object]; } } - (NSDictionary *)_parseObjectAndUpdateCollection:(NSDictionary *)message { M13MutableOrderedDictionary *collection = self.collections[message[@"collection"]]; NSMutableDictionary *object = [collection objectForKey:message[@"id"]]; for (id key in message[@"fields"]) { object[key] = message[@"fields"][key]; } for (id key in message[@"cleared"]) { [object removeObjectForKey:key]; } return object; } @end ================================================ FILE: ObjectiveDDP/MeteorClient+Private.h ================================================ #import "MeteorClient.h" @interface MeteorClient () { @public // for tests. This header is not exported anyway. NSMutableDictionary *_subscriptions; NSMutableSet *_methodIds; NSMutableDictionary *_responseCallbacks; NSString *_userName; NSString *_password; NSMutableDictionary *_subscriptionsParameters; BOOL _disconnecting; double _tries; double _maxRetryIncrement; } // These are public and should be KVO compliant so use accessor instead of direct ivar access @property (nonatomic, copy, readwrite) NSString *userId; @property (nonatomic, copy, readwrite) NSString *sessionToken; @property (nonatomic, assign, readwrite) BOOL connected; @property (nonatomic, strong, readwrite) NSMutableDictionary *collections; @property (nonatomic, assign, readwrite) BOOL websocketReady; @property (nonatomic, assign, readwrite) AuthState authState; //xxx: temporary methods to corral state vars - (void)_setAuthStateToLoggingIn; - (void)_setAuthStateToLoggedIn:(NSString *)userId withToken:()token; - (void)_setAuthStatetoLoggedOut; - (NSDictionary *)_buildUserParametersWithUsername:(NSString *)username password:(NSString *)password; - (NSDictionary *)_buildUserParametersWithEmail:(NSString *)email password:(NSString *)password; - (NSDictionary *)_buildUserParametersWithUsernameOrEmail:(NSString *)usernameOrEmail password:(NSString *)password; @end @interface MeteorClient (Parsing) - (void)_handleMethodResultMessageWithMessageId:(NSString *)messageId message:(NSDictionary *)message msg:(NSString *)msg; - (void)_handleAddedMessage:(NSDictionary *)message msg:(NSString *)msg; - (void)_handleAddedBeforeMessage:(NSDictionary *)message msg:(NSString *)msg; - (void)_handleMovedBeforeMessage:(NSDictionary *)message msg:(NSString *)msg; - (void)_handleRemovedMessage:(NSDictionary *)message msg:(NSString *)msg; - (void)_handleChangedMessage:(NSDictionary *)message msg:(NSString *)msg; @end ================================================ FILE: ObjectiveDDP/MeteorClient.h ================================================ #import "ObjectiveDDP.h" @protocol DDPAuthDelegate; extern NSString * const MeteorClientConnectionReadyNotification; extern NSString * const MeteorClientDidConnectNotification; extern NSString * const MeteorClientDidDisconnectNotification; /** Errors due to transport (connection) problems will have this domain. For errors being reported from the backend, they will have the "errorType" key as their error domain. */ extern NSString * const MeteorClientTransportErrorDomain; // xxx: typedef NS_ENUM(NSUInteger, MeteorClientError) { MeteorClientErrorNotConnected, MeteorClientErrorDisconnectedBeforeCallbackComplete, MeteorClientErrorLogonRejected }; typedef NS_ENUM(NSUInteger, AuthState) { AuthStateNoAuth, AuthStateLoggingIn, AuthStateLoggedIn, /* implies using auth but not currently authorized */ AuthStateLoggedOut }; typedef void(^MeteorClientMethodCallback)(NSDictionary *response, NSError *error); @interface MeteorClient : NSObject @property (nonatomic, strong) ObjectiveDDP *ddp; @property (nonatomic, weak) id authDelegate; @property (nonatomic, strong, readonly) NSMutableDictionary *collections; @property (nonatomic, copy, readonly) NSString *userId; @property (nonatomic, copy, readonly) NSString *sessionToken; @property (nonatomic, assign, readonly) BOOL websocketReady; @property (nonatomic, assign, readonly) BOOL connected; @property (nonatomic, assign, readonly) AuthState authState; @property (nonatomic, copy, readonly) NSString *ddpVersion; @property (nonatomic, strong ,readonly) NSArray *supportedVersions; // In flux; use "pre1" for meteor versions up to v0.8.0.1 // use "pre2" for meteor versions v0.8.1.1 and above (until they change it again) // use "1" for meteor versions v0.8.9 and above - (id)initWithDDPVersion:(NSString *)ddpVersion; // Prevent user from start with this methods - (id)init __attribute__((unavailable("Must use initWithDDPVersion: instead."))); + (instancetype)new __attribute__((unavailable("Must use initWithDDPVersion: instead."))); #pragma mark - Methods - (void) logonWithSessionToken:(NSString *) sessionToken responseCallback:(MeteorClientMethodCallback)responseCallback; - (NSString *)callMethodName:(NSString *)methodName parameters:(NSArray *)parameters responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)logonWithUsername:(NSString *)username password:(NSString *)password responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)logonWithEmail:(NSString *)email password:(NSString *)password responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)logonWithUsernameOrEmail:(NSString *)usernameOrEmail password:(NSString *)password responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)logonWithOAuthAccessToken: (NSString *)accessToken serviceName: (NSString *) serviceName responseCallback: (MeteorClientMethodCallback)responseCallback; - (void)logonWithOAuthAccessToken:(NSString *)accessToken serviceName:(NSString *)serviceName optionsKey:(NSString *)key responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)logonWithUserParameters:(NSDictionary *)userParameters responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)signupWithUsernameAndEmail:(NSString *)username email:(NSString *)email password:(NSString *)password fullname:(NSString *)fullname responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)signupWithUsernameAndEmail:(NSString *)username email:(NSString *)email password:(NSString *)password userParameters:(NSDictionary *)userParameters responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)signupWithUsername:(NSString *)username password:(NSString *)password fullname:(NSString *)fullname responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)signupWithEmail:(NSString *)email password:(NSString *)password fullname:(NSString *)fullname responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)signupWithEmail:(NSString *)email password:(NSString *)password firstName:(NSString *)firstName lastName:(NSString *)lastName responseCallback:(MeteorClientMethodCallback)responseCallback; - (void)signupWithUserParameters:userParameters responseCallback:(MeteorClientMethodCallback) responseCallback; - (void)addSubscription:(NSString *)subscriptionName; - (void)addSubscription:(NSString *)subscriptionName withParameters:(NSArray *)parameters; - (void)removeSubscription:(NSString *)subscriptionName; - (void)logout; - (void)disconnect; - (void)reconnect; - (void)ping; // Deprecated methods - (void)logonWithUsername:(NSString *)username password:(NSString *)password __attribute__((deprecated("use logonWithUsername:password:responseCallback: instead"))); - (NSString *)sendWithMethodName:(NSString *)methodName parameters:(NSArray *)parameters notifyOnResponse:(BOOL)notify __attribute__((deprecated("use callMethodName:parameters:responseCallback: instead"))); - (void)sendWithMethodName:(NSString *)methodName parameters:(NSArray *)parameters __attribute__((deprecated("use callMethodName:parameters:responseCallback: instead"))); @end #pragma mark - @protocol DDPAuthDelegate - (void)authenticationWasSuccessful; - (void)authenticationFailedWithError:(NSError *)reason; @end ================================================ FILE: ObjectiveDDP/MeteorClient.m ================================================ #import #import "DependencyProvider.h" #import "MeteorClient.h" #import "MeteorClient+Private.h" #import "BSONIdGenerator.h" #import NSString * const MeteorClientConnectionReadyNotification = @"bounsj.objectiveddp.ready"; NSString * const MeteorClientDidConnectNotification = @"boundsj.objectiveddp.connected"; NSString * const MeteorClientDidDisconnectNotification = @"boundsj.objectiveddp.disconnected"; NSString * const MeteorClientTransportErrorDomain = @"boundsj.objectiveddp.transport"; double const MeteorClientRetryIncreaseBy = 1; double const MeteorClientMaxRetryIncrease = 6; @interface MeteorClient () @property (nonatomic, copy, readwrite) NSString *ddpVersion; @end @implementation MeteorClient - (id)initWithDDPVersion:(NSString *)ddpVersion { self = [super init]; if (self) { _collections = [NSMutableDictionary dictionary]; _subscriptions = [NSMutableDictionary dictionary]; _subscriptionsParameters = [NSMutableDictionary dictionary]; _methodIds = [NSMutableSet set]; _responseCallbacks = [NSMutableDictionary dictionary]; _ddpVersion = ddpVersion; _maxRetryIncrement = MeteorClientMaxRetryIncrease; _tries = MeteorClientRetryIncreaseBy; if ([ddpVersion isEqualToString:@"1"]) { _supportedVersions = @[@"1", @"pre2"]; } else { _supportedVersions = @[@"pre2", @"pre1"]; } } return self; } #pragma mark MeteorClient public API - (void)resetCollections { [self.collections removeAllObjects]; } - (void)sendWithMethodName:(NSString *)methodName parameters:(NSArray *)parameters { [self sendWithMethodName:methodName parameters:parameters notifyOnResponse:NO]; } -(NSString *)sendWithMethodName:(NSString *)methodName parameters:(NSArray *)parameters notifyOnResponse:(BOOL)notify { if (![self okToSend]) { return nil; } return [self _send:notify parameters:parameters methodName:methodName]; } - (NSString *)callMethodName:(NSString *)methodName parameters:(NSArray *)parameters responseCallback:(MeteorClientMethodCallback)responseCallback { if ([self _rejectIfNotConnected:responseCallback]) { return nil; }; NSString *methodId = [self _send:YES parameters:parameters methodName:methodName]; if (responseCallback) { _responseCallbacks[methodId] = [responseCallback copy]; } return methodId; } - (void)addSubscription:(NSString *)subscriptionName { [self addSubscription:subscriptionName withParameters:nil]; } - (void)addSubscription:(NSString *)subscriptionName withParameters:(NSArray *)parameters { NSString *uid = [BSONIdGenerator generate]; [_subscriptions setObject:uid forKey:subscriptionName]; if (parameters) { [_subscriptionsParameters setObject:parameters forKey:subscriptionName]; } if (![self okToSend]) { return; } [self.ddp subscribeWith:uid name:subscriptionName parameters:parameters]; } - (void)removeSubscription:(NSString *)subscriptionName { if (![self okToSend]) { return; } NSString *uid = [_subscriptions objectForKey:subscriptionName]; if (uid) { [self.ddp unsubscribeWith:uid]; [_subscriptions removeObjectForKey:subscriptionName]; } } - (BOOL)okToSend { if (!self.connected) { return NO; } return YES; } - (void)logonWithSessionToken:(NSString *)sessionToken { [self logonWithSessionToken:sessionToken responseCallback:nil]; } // tokenExpires.$date : expiry date - (void)logonWithSessionToken:(NSString *) sessionToken responseCallback:(MeteorClientMethodCallback)responseCallback { [self logonWithUserParameters:@{@"resume": sessionToken} responseCallback:responseCallback]; } - (void)logonWithUsername:(NSString *)username password:(NSString *)password { [self logonWithUserParameters:[self _buildUserParametersWithUsername:username password:password] responseCallback:nil]; } - (void)logonWithUsername:(NSString *)username password:(NSString *)password responseCallback:(MeteorClientMethodCallback)responseCallback { [self logonWithUserParameters:[self _buildUserParametersWithUsername:username password:password] responseCallback:responseCallback]; } - (void)logonWithEmail:(NSString *)email password:(NSString *)password responseCallback:(MeteorClientMethodCallback)responseCallback { [self logonWithUserParameters:[self _buildUserParametersWithEmail:email password:password] responseCallback:responseCallback]; } - (void)logonWithUsernameOrEmail:(NSString *)usernameOrEmail password:(NSString *)password responseCallback:(MeteorClientMethodCallback)responseCallback { [self logonWithUserParameters:[self _buildUserParametersWithUsernameOrEmail:usernameOrEmail password:password] responseCallback:responseCallback]; } /* * Logs in using access token -- this breaks the current convention, * but the method call is dependent on some of this class's variables * @param serviceName service name i.e facebook, google * @param accessToken short-lived one-time code received, or long-lived access token for Facebook login * For some logins, such as Facebook, login with OAuth may only work after customizing the accounts-x packages. This is because Facebook only returns long-lived access tokens for mobile clients * until meteor decides to change the packages themselves. * use https://github.com/jasper-lu/accounts-facebook-ddp and * https://github.com/jasper-lu/facebook-ddp for reference * * If an sdk only allows login returns long-lived token, modify your accounts-x package, * and add your package to the if(serviceName.compare("facebook")) in _buildOAuthRequestStringWithAccessToken */ - (void)logonWithOAuthAccessToken:(NSString *)accessToken serviceName:(NSString *)serviceName responseCallback:(MeteorClientMethodCallback)responseCallback { [self logonWithOAuthAccessToken:accessToken serviceName:serviceName optionsKey:@"oauth" responseCallback:responseCallback]; } // some meteor servers provide a custom login handler with a custom options key. Allow client to configure the key instead of always using "oauth" - (void)logonWithOAuthAccessToken:(NSString *)accessToken serviceName:(NSString *)serviceName optionsKey:(NSString *)key responseCallback:(MeteorClientMethodCallback)responseCallback { //generates random secret (credentialToken) NSString *url = [self _buildOAuthRequestStringWithAccessToken:accessToken serviceName: serviceName]; NSLog(@"%@", url); //callback gives an html page in string. credential token & credential secret are stored in a hidden element NSString *callback = [self _makeHTTPRequestAtUrl:url]; NSDictionary *jsonData = [self handleOAuthCallback:callback]; // setCredentialToken gets set to false if the call fails if (jsonData == nil || ![jsonData[@"setCredentialToken"] boolValue]) { NSError *logonError = [NSError errorWithDomain:MeteorClientTransportErrorDomain code:MeteorClientErrorLogonRejected userInfo:@{NSLocalizedDescriptionKey: @"Unable to authenticate"}]; if (responseCallback) { responseCallback(nil, logonError); } return; } NSDictionary* options = @{key: @{@"credentialToken": [jsonData objectForKey: @"credentialToken"], @"credentialSecret": [jsonData objectForKey:@"credentialSecret"]}}; [self logonWithUserParameters:options responseCallback:responseCallback]; } - (void)logonWithUserParameters:(NSDictionary *)userParameters responseCallback:(MeteorClientMethodCallback)responseCallback { if (self.authState == AuthStateLoggingIn) { NSString *errorDesc = [NSString stringWithFormat:@"You must wait for the current logon request to finish before sending another."]; NSError *logonError = [NSError errorWithDomain:MeteorClientTransportErrorDomain code:MeteorClientErrorLogonRejected userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; if (responseCallback) { responseCallback(nil, logonError); } return; } if ([self _rejectIfNotConnected:responseCallback]) { return; } [self _setAuthStateToLoggingIn]; NSMutableDictionary *mutableUserParameters = [userParameters mutableCopy]; [self callMethodName:@"login" parameters:@[mutableUserParameters] responseCallback:^(NSDictionary *response, NSError *error) { if (error) { [self _setAuthStatetoLoggedOut]; [self.authDelegate authenticationFailedWithError:error]; } else { // tokenExpires.$date : expiry date [self _setAuthStateToLoggedIn:response[@"result"][@"id"] withToken:response[@"result"][@"token"]]; [self.authDelegate authenticationWasSuccessful]; } if (responseCallback) { responseCallback(response, error); } }]; } - (void)signupWithUsernameAndEmail:(NSString *)username email:(NSString *)email password:(NSString *)password fullname:(NSString *)fullname responseCallback:(MeteorClientMethodCallback)responseCallback { [self signupWithUserParameters:[self _buildUserParametersSignup:username email:email password:password fullname:fullname] responseCallback:responseCallback]; } - (void)signupWithUsernameAndEmail:(NSString *)username email:(NSString *)email password:(NSString *)password userParameters:(NSDictionary *)userParameters responseCallback:(MeteorClientMethodCallback)responseCallback{ NSMutableDictionary *parameters = [[self _buildUserParametersSignup:username email:email password:password] mutableCopy]; [parameters addEntriesFromDictionary:userParameters]; [self signupWithUserParameters:parameters responseCallback:responseCallback]; } - (void)signupWithUsername:(NSString *)username password:(NSString *)password fullname:(NSString *)fullname responseCallback:(MeteorClientMethodCallback)responseCallback { [self signupWithUserParameters:[self _buildUserParametersSignup:username email:@"" password:password fullname:fullname] responseCallback:responseCallback]; } - (void)signupWithEmail:(NSString *)email password:(NSString *)password fullname:(NSString *)fullname responseCallback:(MeteorClientMethodCallback)responseCallback { [self signupWithUserParameters:[self _buildUserParametersSignup:@"" email:email password:password fullname:fullname] responseCallback:responseCallback]; } - (void)signupWithEmail:(NSString *)email password:(NSString *)password firstName:(NSString *)firstName lastName:(NSString *)lastName responseCallback:(MeteorClientMethodCallback)responseCallback { [self signupWithUserParameters:[self _buildUserParametersSignup:@"" email:email password:password firstName:firstName lastName:lastName] responseCallback:responseCallback]; } - (void)signupWithUserParameters:userParameters responseCallback:(MeteorClientMethodCallback) responseCallback { if (self.authState == AuthStateLoggingIn) { NSString *errorDesc = [NSString stringWithFormat:@"You must wait for the current signup request to finish before sending another."]; NSError *logonError = [NSError errorWithDomain:MeteorClientTransportErrorDomain code:MeteorClientErrorLogonRejected userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; [self.authDelegate authenticationFailedWithError:logonError]; if (responseCallback) { responseCallback(nil, logonError); } return; } [self _setAuthStateToLoggingIn]; NSMutableDictionary *mutableUserParameters = [userParameters mutableCopy]; [self callMethodName:@"createUser" parameters:@[mutableUserParameters] responseCallback:^(NSDictionary *response, NSError *error) { if (error) { [self _setAuthStatetoLoggedOut]; [self.authDelegate authenticationFailedWithError:error]; } else { [self _setAuthStateToLoggedIn:response[@"result"][@"id"] withToken:response[@"result"][@"token"]]; [self.authDelegate authenticationWasSuccessful]; } responseCallback(response, error); }]; } // move this to string category - (NSString *)sha256:(NSString *)clear { const char *s = [clear cStringUsingEncoding:NSUTF8StringEncoding]; NSData *keyData = [NSData dataWithBytes:s length:strlen(s)]; uint8_t digest[CC_SHA256_DIGEST_LENGTH] = {0}; CC_SHA256(keyData.bytes, (unsigned int)keyData.length, digest); NSData *digestData = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; NSString *hash = [digestData description]; // refactor this hash = [hash stringByReplacingOccurrencesOfString:@" " withString:@""]; hash = [hash stringByReplacingOccurrencesOfString:@"<" withString:@""]; hash = [hash stringByReplacingOccurrencesOfString:@">" withString:@""]; return hash; } - (void)logout { [self.ddp methodWithId:[BSONIdGenerator generate] method:@"logout" parameters:nil]; [self _setAuthStatetoLoggedOut]; } - (void)disconnect { _disconnecting = YES; [self.ddp disconnectWebSocket]; } - (void)reconnect { if (self.ddp.webSocket.readyState == SR_OPEN) { return; } [self.ddp connectWebSocket]; } - (void)ping { if (!self.connected) { return; } [self.ddp ping:[BSONIdGenerator generate]]; } #pragma mark - (void)didReceiveMessage:(NSDictionary *)message { NSString *msg = [message objectForKey:@"msg"]; if (!msg) return; NSString *messageId = message[@"id"]; [self _handleMethodResultMessageWithMessageId:messageId message:message msg:msg]; [self _handleAddedMessage:message msg:msg]; [self _handleAddedBeforeMessage:message msg:msg]; [self _handleMovedBeforeMessage:message msg:msg]; [self _handleRemovedMessage:message msg:msg]; [self _handleChangedMessage:message msg:msg]; if ([msg isEqualToString:@"ping"]) { [self.ddp pong:messageId]; } if ([msg isEqualToString:@"connected"]) { self.connected = YES; [[NSNotificationCenter defaultCenter] postNotificationName:MeteorClientConnectionReadyNotification object:self]; if (self.sessionToken) { //TODO check expiry date [self logonWithSessionToken:self.sessionToken responseCallback:nil]; } [self _makeMeteorDataSubscriptions]; } if ([msg isEqualToString:@"ready"]) { NSArray *subs = message[@"subs"]; for(NSString *readySubscription in subs) { for(NSString *subscriptionName in _subscriptions) { NSString *curSubId = _subscriptions[subscriptionName]; if([curSubId isEqualToString:readySubscription]) { NSString *notificationName = [NSString stringWithFormat:@"%@_ready", subscriptionName]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self]; break; } } } } else if ([msg isEqualToString:@"updated"]) { NSArray *methods = message[@"methods"]; for(NSString *updateMethod in methods) { for(NSString *methodId in _methodIds) { if([methodId isEqualToString:updateMethod]) { NSString *notificationName = [NSString stringWithFormat:@"%@_update", methodId]; [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self]; break; } } } } else if ([msg isEqualToString:@"addedBefore"]) { } else if ([msg isEqualToString:@"movedBefore"]) { } else if ([msg isEqualToString:@"nosub"]) { } else if ([msg isEqualToString:@"error"]) { } } - (void)didOpen { self.websocketReady = YES; [self _resetBackoff]; [self resetCollections]; [self.ddp connectWithSession:nil version:self.ddpVersion support:self.supportedVersions]; [[NSNotificationCenter defaultCenter] postNotificationName:MeteorClientDidConnectNotification object:self]; } - (void)didReceiveConnectionError:(NSError *)error { [self _handleConnectionError]; } - (void)didReceiveConnectionClose { [self _handleConnectionError]; } #pragma mark - Internal - (NSString *)_send:(BOOL)notify parameters:(NSArray *)parameters methodName:(NSString *)methodName { NSString *methodId = [BSONIdGenerator generate]; if(notify == YES) { [_methodIds addObject:methodId]; } [self.ddp methodWithId:methodId method:methodName parameters:parameters]; return methodId; } - (void)_resetBackoff { _tries = 1; } - (void)_handleConnectionError { self.websocketReady = NO; self.connected = NO; [self _invalidateUnresolvedMethods]; [[NSNotificationCenter defaultCenter] postNotificationName:MeteorClientDidDisconnectNotification object:self]; if (_disconnecting) { _disconnecting = NO; return; } // double timeInterval = 5.0 * _tries; if (_tries != _maxRetryIncrement) { _tries++; } [self performSelector:@selector(reconnect) withObject:self afterDelay:timeInterval]; } - (void)_invalidateUnresolvedMethods { for (NSString *methodId in _methodIds) { MeteorClientMethodCallback callback = _responseCallbacks[methodId]; if (callback) { callback(nil, [NSError errorWithDomain:MeteorClientTransportErrorDomain code:MeteorClientErrorDisconnectedBeforeCallbackComplete userInfo:@{NSLocalizedDescriptionKey: @"You were disconnected"}]); } } [_methodIds removeAllObjects]; [_responseCallbacks removeAllObjects]; } - (void)_makeMeteorDataSubscriptions { for (NSString *key in [_subscriptions allKeys]) { NSString *uid = [BSONIdGenerator generate]; [_subscriptions setObject:uid forKey:key]; NSArray *params = _subscriptionsParameters[key]; [self.ddp subscribeWith:uid name:key parameters:params]; } } - (BOOL)_rejectIfNotConnected:(MeteorClientMethodCallback)responseCallback { if (![self okToSend]) { NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"You are not connected"}; NSError *notConnectedError = [NSError errorWithDomain:MeteorClientTransportErrorDomain code:MeteorClientErrorNotConnected userInfo:userInfo]; if (responseCallback) { responseCallback(nil, notConnectedError); } return YES; } return NO; } - (void)_setAuthStateToLoggingIn { self.authState = AuthStateLoggingIn; } - (void)_setAuthStateToLoggedIn:(NSString *)userId withToken:()token { self.authState = AuthStateLoggedIn; self.userId = userId; self.sessionToken = token; } - (void)_setAuthStatetoLoggedOut { self.authState = AuthStateLoggedOut; self.userId = nil; } - (NSDictionary *)_buildUserParametersSignup:(NSString *)username email:(NSString *)email password:(NSString *)password fullname:(NSString *) fullname { return @{ @"username": username,@"email": email, @"password": @{ @"digest": [self sha256:password], @"algorithm": @"sha-256" }, @"profile": @{ @"fullname": fullname, @"signupToken": @"" } }; } - (NSDictionary *)_buildUserParametersSignup:(NSString *)username email:(NSString *)email password:(NSString *)password firstName:(NSString *)firstName lastName:(NSString*)lastName { return @{ @"username": username,@"email": email, @"password": @{ @"digest": [self sha256:password], @"algorithm": @"sha-256" }, @"profile": @{ @"first_name": firstName, @"last_name": lastName, @"signupToken": @"" } }; } - (NSDictionary *)_buildUserParametersSignup:(NSString *)username email:(NSString *)email password:(NSString *)password { return @{ @"username": username,@"email": email, @"password": @{ @"digest": [self sha256:password], @"algorithm": @"sha-256" } }; } - (NSDictionary *)_buildUserParametersWithUsername:(NSString *)username password:(NSString *)password { return @{ @"user": @{ @"username": username }, @"password": @{ @"digest": [self sha256:password], @"algorithm": @"sha-256" } }; } - (NSDictionary *)_buildUserParametersWithEmail:(NSString *)email password:(NSString *)password { return @{ @"user": @{ @"email": email }, @"password": @{ @"digest": [self sha256:password], @"algorithm": @"sha-256" } }; } - (NSDictionary *)_buildUserParametersWithUsernameOrEmail:(NSString *)usernameOrEmail password:(NSString *)password { if ([usernameOrEmail rangeOfString:@"@"].location == NSNotFound) { return [self _buildUserParametersWithUsername:usernameOrEmail password:password]; } else { return [self _buildUserParametersWithEmail:usernameOrEmail password:password]; } } - (NSString *)_buildOAuthRequestStringWithAccessToken:(NSString *)accessToken serviceName: (NSString *)serviceName { NSString* homeUrl = [[[self ddp] urlString] stringByReplacingOccurrencesOfString:@"/websocket" withString:@""]; //remove ws/wss and replace with http/https if ([homeUrl hasPrefix:@"ws"]) { homeUrl = [@"http" stringByAppendingString:[homeUrl substringFromIndex:[@"ws" length]]]; } else { homeUrl = [@"https" stringByAppendingString:[homeUrl substringFromIndex:[@"wss" length]]]; } NSString* tokenType = @""; //facebook sdk can only send access token, others send a one time code if ([serviceName isEqualToString: @"facebook"]) { tokenType = @"accessToken"; } else { tokenType = @"code"; } return [NSString stringWithFormat: @"%@/_oauth/%@/?%@=%@&state=%@", homeUrl, serviceName, tokenType, accessToken, [self _generateStateWithToken: [self _randomSecret]]]; } - (NSDictionary *)_buildUserParametersWithOAuthAccessToken:(NSString *)accessToken { return @{}; } //functions for OAuth //generates base64 string for json - (NSString *)_generateStateWithToken:(NSString *)credentialToken { NSData* jsonData = [NSJSONSerialization dataWithJSONObject:@{ @"credentialToken": credentialToken, @"loginStyle": @"popup" } options:0 error:NULL]; if(!jsonData) { //error return @""; } //set jsonString equal to base64 conversion NSString* base64String = [jsonData base64EncodedStringWithOptions: NSDataBase64EncodingEndLineWithLineFeed]; NSLog(@"%@", base64String); return base64String; } //generates random secret for credential token - (NSString *)_randomSecret { NSString *BASE64_CHARS = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; NSMutableString *s = [NSMutableString stringWithCapacity:20]; for (NSUInteger i = 0U; i < 20; i++) { u_int32_t r = arc4random() % [BASE64_CHARS length]; unichar c = [BASE64_CHARS characterAtIndex:r]; [s appendFormat:@"%C", c]; } return s; } - (NSString *)_makeHTTPRequestAtUrl:(NSString*)url { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; [request setHTTPMethod:@"GET"]; [request setURL:[NSURL URLWithString:url]]; NSLog(@"Url is %@", url); NSError *error = [[NSError alloc] init]; NSHTTPURLResponse *responseCode = nil; NSData *oResponseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&responseCode error:&error]; if([responseCode statusCode] != 200){ NSLog(@"Error getting %@, HTTP status code %li", url, (long)[responseCode statusCode]); return nil; } return [[NSString alloc] initWithData:oResponseData encoding:NSUTF8StringEncoding]; } - (NSDictionary*)handleOAuthCallback: (NSString *)callback { // it's possible callback is nil if (callback == nil) { return nil; } NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:@"
(.*?)
" options:0 error:nil]; callback = [callback substringWithRange:[[regex firstMatchInString:callback options:0 range:NSMakeRange(0, [callback length])] rangeAtIndex: 1]]; NSLog(@"callback is: %@", callback); NSDictionary* jsonData = [NSJSONSerialization JSONObjectWithData:[callback dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]; return jsonData; } @end ================================================ FILE: ObjectiveDDP/ObjectiveDDP-Prefix.pch ================================================ // // Prefix header for all source files of the 'ObjectiveDDP' target in the 'ObjectiveDDP' project // #ifdef __OBJC__ #import #import "DependencyProvider.h" #endif ================================================ FILE: ObjectiveDDP/ObjectiveDDP.h ================================================ #import @class SRWebSocket; @protocol SRWebSocketDelegate; @protocol ObjectiveDDPDelegate; @interface ObjectiveDDP : NSObject @property (nonatomic, copy) NSString *urlString; @property (nonatomic, assign) id delegate; @property (nonatomic, strong) SRWebSocket *webSocket; - (void)ping:(NSString *)id; - (void)pong:(NSString *)id; // Prevent user from start with this methods - (id)init __attribute__((unavailable("Must use initWithURLString:delegate: instead."))); + (instancetype)new __attribute__((unavailable("Must use initWithURLString:delegate: instead."))); - (id)initWithURLString:(NSString *)urlString delegate:(id )delegate; - (void)connectWebSocket; - (void)disconnectWebSocket; - (void)connectWithSession:(NSString *)session version:(NSString *)version support:(NSArray *)support; - (void)subscribeWith:(NSString *)id name:(NSString *)name parameters:(NSArray *)parameters; - (void)unsubscribeWith:(NSString *)id; - (void)methodWithId:(NSString *)id method:(NSString *)method parameters:(NSArray *)parameters; @end @protocol ObjectiveDDPDelegate - (void)didOpen; - (void)didReceiveMessage:(NSDictionary *)message; - (void)didReceiveConnectionError:(NSError *)error; - (void)didReceiveConnectionClose; @end ================================================ FILE: ObjectiveDDP/ObjectiveDDP.m ================================================ #import "ObjectiveDDP.h" #import "DependencyProvider.h" #import #import @implementation ObjectiveDDP - (id)initWithURLString:(NSString *)urlString delegate:(id )delegate { self = [super init]; if (self) { self.urlString = urlString; self.delegate = delegate; } return self; } #pragma mark - Public API // connect to the underlying websocket - (void)connectWebSocket { [self _closeConnection]; [self _setupWebSocket]; [self.webSocket open]; } // disconnect from the websocket - (void)disconnectWebSocket { [self _closeConnection]; } //ping (client -> server): // id: string (the id for the ping) - (void)ping:(NSString *)id { NSDictionary *fields = @{@"msg": @"ping"}; if (id) fields = @{@"msg": @"ping", @"id": id}; NSString *json = [self _buildJSONWithFields:fields parameters:nil]; [self.webSocket send:json]; } //pong (client -> server): // id: string (the id send with the ping) - (void)pong:(NSString *)id { NSDictionary *fields = @{@"msg": @"pong"}; if (id) fields = @{@"msg": @"pong", @"id": id}; NSString *json = [self _buildJSONWithFields:fields parameters:nil]; [self.webSocket send:json]; } //connect (client -> server) // session: string (if trying to connectWebSocket to an existing DDP session) // version: string (the proposed protocol version) // support: array of strings (protocol versions supported by the client, in order of preference) - (void)connectWithSession:(NSString *)session version:(NSString *)version support:(NSArray *)support { NSDictionary *fields = @{@"msg": @"connect", @"version": version, @"support": support}; NSString *json = [self _buildJSONWithFields:fields parameters:nil]; [self.webSocket send:json]; } //sub (client -> server): // id: string (an arbitrary client-determined identifier for this subscription) // name: string (the name of the subscription) // params: optional array of EJSON items (parameters to the subscription) - (void)subscribeWith:(NSString *)id name:(NSString *)name parameters:(NSArray *)parameters { NSDictionary *fields = @{@"msg": @"sub", @"name": name, @"id": id}; NSString *json = [self _buildJSONWithFields:fields parameters:parameters]; [self.webSocket send:json]; } //unsub (client -> server): // id: string (an arbitrary client-determined identifier for this subscription) - (void)unsubscribeWith:(NSString *)id { NSDictionary *fields = @{@"msg": @"unsub", @"id": id}; NSString *json = [self _buildJSONWithFields:fields parameters:nil]; [self.webSocket send:json]; } //method (client -> server): // method: string (method name) // params: optional array of EJSON items (parameters to the method) // id: string (an arbitrary client-determined identifier for this method call) - (void)methodWithId:(NSString *)id method:(NSString *)method parameters:(NSArray *)parameters { NSDictionary *fields = @{@"msg": @"method", @"method": method, @"id": id}; NSString *json = [self _buildJSONWithFields:fields parameters:parameters]; [self.webSocket send:json]; } #pragma mark - Internal - (NSString *)_buildJSONWithFields:(NSDictionary *)fields parameters:(NSArray *)parameters { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:fields]; if (parameters) [dict setObject:parameters forKey:@"params"]; NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:kNilOptions error:nil]; return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; } - (void)_setupWebSocket { NSURL *url = [NSURL URLWithString:self.urlString]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; self.webSocket = [[DependencyProvider sharedProvider] provideSRWebSocketWithRequest:request]; self.webSocket.delegate = self; } - (void)_closeConnection { [self.webSocket close]; self.webSocket.delegate = nil; self.webSocket = nil; } #pragma mark - - (void)webSocketDidOpen:(SRWebSocket *)webSocket { [self.delegate didOpen]; } - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { [self.delegate didReceiveConnectionError:error]; } - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { [self.delegate didReceiveConnectionClose]; } - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { // TODO: write test case for parse error (handle) NSData *data = [(NSString *)message dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; [self.delegate didReceiveMessage:dictionary]; } @end ================================================ FILE: ObjectiveDDP.podspec ================================================ Pod::Spec.new do |s| s.name = 'ObjectiveDDP' s.ios.deployment_target = '7.1' s.osx.deployment_target = '10.8' s.version = '0.2.0' s.license = 'MIT' s.summary = 'Facilitates communication between iOS clients and meteor.js servers' s.homepage = 'https://github.com/boundsj/ObjectiveDDP.git' s.author = 'Jesse Bounds' s.source = { :git => 'https://github.com/boundsj/ObjectiveDDP.git', :tag => 'v0.2.0' } s.source_files = 'ObjectiveDDP/*.{h,m,c}' s.requires_arc = true s.dependency 'SocketRocket', '0.4.1' s.dependency 'M13OrderedDictionary' end ================================================ FILE: ObjectiveDDP.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 4000D63A170E880400296229 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4000D639170E880400296229 /* Foundation.framework */; }; 4000D63F170E880400296229 /* ObjectiveDDP.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4000D63E170E880400296229 /* ObjectiveDDP.h */; }; 4000D641170E880400296229 /* ObjectiveDDP.m in Sources */ = {isa = PBXBuildFile; fileRef = 4000D640170E880400296229 /* ObjectiveDDP.m */; }; 4011C80417BD2AA6002AA28E /* DependencyProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 4011C80317BD2AA6002AA28E /* DependencyProvider.m */; }; 4011C80517BD2AA6002AA28E /* DependencyProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 4011C80317BD2AA6002AA28E /* DependencyProvider.m */; }; 4011C80817BD2C06002AA28E /* FakeDependencyProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 4011C80717BD2C06002AA28E /* FakeDependencyProvider.m */; }; 4011C80B17BD2CA1002AA28E /* DDPSpecHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4011C80A17BD2CA1002AA28E /* DDPSpecHelper.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 4011C80E17BD2E14002AA28E /* DependencyProvider+Spec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4011C80D17BD2E14002AA28E /* DependencyProvider+Spec.m */; }; 401AEB7E170E9EC7008B38EA /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 401AEB7D170E9EC7008B38EA /* libicucore.dylib */; }; 401AEB80170E9ECC008B38EA /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 401AEB7F170E9ECC008B38EA /* SystemConfiguration.framework */; }; 401AEB82170E9ED2008B38EA /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 401AEB81170E9ED2008B38EA /* CFNetwork.framework */; }; 401AEB84170E9EEB008B38EA /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 401AEB83170E9EEB008B38EA /* Security.framework */; }; 4079F9DB170EB31D00194B4A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4079F9DA170EB31D00194B4A /* UIKit.framework */; }; 4079F9DC170EB31D00194B4A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4000D639170E880400296229 /* Foundation.framework */; }; 4079F9DE170EB31D00194B4A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4079F9DD170EB31D00194B4A /* CoreGraphics.framework */; }; 4079F9E4170EB31D00194B4A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4079F9E2170EB31D00194B4A /* InfoPlist.strings */; }; 4079F9E6170EB31D00194B4A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4079F9E5170EB31D00194B4A /* main.m */; }; 4079F9F1170EB35200194B4A /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 401AEB83170E9EEB008B38EA /* Security.framework */; }; 4079F9F2170EB35800194B4A /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 401AEB81170E9ED2008B38EA /* CFNetwork.framework */; }; 4079F9F3170EB36000194B4A /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 401AEB7F170E9ECC008B38EA /* SystemConfiguration.framework */; }; 4079F9F4170EB36700194B4A /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 401AEB7D170E9EC7008B38EA /* libicucore.dylib */; }; 4079F9F5170EB39200194B4A /* ObjectiveDDP.m in Sources */ = {isa = PBXBuildFile; fileRef = 4000D640170E880400296229 /* ObjectiveDDP.m */; }; 4079F9F7170EB3B000194B4A /* ObjectiveDDPSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4079F9F6170EB3B000194B4A /* ObjectiveDDPSpec.mm */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 40A305F3178D27E800546C1F /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 40A305F2178D27E800546C1F /* Default-568h@2x.png */; }; 40BAB8CD181EFE550098524C /* NSObject+Spec.m in Sources */ = {isa = PBXBuildFile; fileRef = 40BAB8CC181EFE550098524C /* NSObject+Spec.m */; }; 40BAB8D0182051500098524C /* MeteorClient+Parsing.m in Sources */ = {isa = PBXBuildFile; fileRef = 40BAB8CF182051500098524C /* MeteorClient+Parsing.m */; }; 40BAB8D1182051500098524C /* MeteorClient+Parsing.m in Sources */ = {isa = PBXBuildFile; fileRef = 40BAB8CF182051500098524C /* MeteorClient+Parsing.m */; }; 40F8370C1741C59700AF2340 /* BSONIdGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 888B6DFE173B66EA001EED80 /* BSONIdGenerator.m */; }; 40F8370D1741C59700AF2340 /* MeteorClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 888B6DFB173B6508001EED80 /* MeteorClient.m */; }; 40F8370F1741C62800AF2340 /* MeteorClientSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 40F8370E1741C62800AF2340 /* MeteorClientSpec.mm */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 430E014E41C9666E08613FCE /* MockSRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E02ACD0BC78CC53F31D72 /* MockSRWebSocket.m */; }; 430E08B6E86CA801EE954D0F /* MockObjectiveDDPDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E092B95390D2D558E5F3B /* MockObjectiveDDPDelegate.m */; }; 570DE62F4BD74C18AE51ED8C /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C1F0C9084EA4B2DBD349673 /* libPods.a */; }; 888B6DFC173B6508001EED80 /* MeteorClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 888B6DFB173B6508001EED80 /* MeteorClient.m */; }; 888B6DFF173B66EA001EED80 /* BSONIdGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 888B6DFE173B66EA001EED80 /* BSONIdGenerator.m */; }; 9D3C062D54114FB1AC5700F0 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 22BE0B3F1B814D98AAAE26CF /* libPods.a */; }; BC2C3115142F54C55A92F9FD /* libPods-ObjectiveDDP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 39ACA448F5A7B3CDD0FC5858 /* libPods-ObjectiveDDP.a */; }; FD7BC8FE2078D7295BEFE6FA /* libPods-Specs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 320AA851EDF28E77008B96B8 /* libPods-Specs.a */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 4000D634170E880400296229 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "include/${PRODUCT_NAME}"; dstSubfolderSpec = 16; files = ( 4000D63F170E880400296229 /* ObjectiveDDP.h in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 052BE033183F8655005CBD6E /* MeteorClient+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MeteorClient+Private.h"; sourceTree = ""; }; 15654C36C786305476C43A3A /* Pods-Specs.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Specs.release.xcconfig"; path = "Pods/Target Support Files/Pods-Specs/Pods-Specs.release.xcconfig"; sourceTree = ""; }; 194F89D9560FD1856AE79721 /* Pods-ObjectiveDDP.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ObjectiveDDP.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ObjectiveDDP/Pods-ObjectiveDDP.debug.xcconfig"; sourceTree = ""; }; 22BE0B3F1B814D98AAAE26CF /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 2C1F0C9084EA4B2DBD349673 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 320AA851EDF28E77008B96B8 /* libPods-Specs.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Specs.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 39ACA448F5A7B3CDD0FC5858 /* libPods-ObjectiveDDP.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ObjectiveDDP.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 4000D636170E880400296229 /* libObjectiveDDP.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libObjectiveDDP.a; sourceTree = BUILT_PRODUCTS_DIR; }; 4000D639170E880400296229 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 4000D63D170E880400296229 /* ObjectiveDDP-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ObjectiveDDP-Prefix.pch"; sourceTree = ""; }; 4000D63E170E880400296229 /* ObjectiveDDP.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjectiveDDP.h; sourceTree = ""; }; 4000D640170E880400296229 /* ObjectiveDDP.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjectiveDDP.m; sourceTree = ""; }; 4011C80217BD2AA6002AA28E /* DependencyProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DependencyProvider.h; sourceTree = ""; }; 4011C80317BD2AA6002AA28E /* DependencyProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DependencyProvider.m; sourceTree = ""; }; 4011C80617BD2C06002AA28E /* FakeDependencyProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FakeDependencyProvider.h; sourceTree = ""; }; 4011C80717BD2C06002AA28E /* FakeDependencyProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FakeDependencyProvider.m; sourceTree = ""; }; 4011C80A17BD2CA1002AA28E /* DDPSpecHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDPSpecHelper.m; sourceTree = ""; }; 4011C80D17BD2E14002AA28E /* DependencyProvider+Spec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "DependencyProvider+Spec.m"; sourceTree = ""; }; 4011C80F17BD310D002AA28E /* DDPSpecHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDPSpecHelper.h; sourceTree = ""; }; 401AEB7D170E9EC7008B38EA /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; 401AEB7F170E9ECC008B38EA /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 401AEB81170E9ED2008B38EA /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 401AEB83170E9EEB008B38EA /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 4079F9D9170EB31D00194B4A /* Specs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Specs.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4079F9DA170EB31D00194B4A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 4079F9DD170EB31D00194B4A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 4079F9E1170EB31D00194B4A /* Specs-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Specs-Info.plist"; sourceTree = ""; }; 4079F9E3170EB31D00194B4A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 4079F9E5170EB31D00194B4A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 4079F9E7170EB31D00194B4A /* Specs-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Specs-Prefix.pch"; sourceTree = ""; }; 4079F9F6170EB3B000194B4A /* ObjectiveDDPSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ObjectiveDDPSpec.mm; sourceTree = ""; }; 40A305F2178D27E800546C1F /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 40BAB8CC181EFE550098524C /* NSObject+Spec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Spec.m"; sourceTree = ""; }; 40BAB8CF182051500098524C /* MeteorClient+Parsing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MeteorClient+Parsing.m"; sourceTree = ""; }; 40E901D1183809DF00CFD98C /* ObjectiveDDP.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ObjectiveDDP.podspec; sourceTree = SOURCE_ROOT; }; 40F8370E1741C62800AF2340 /* MeteorClientSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MeteorClientSpec.mm; sourceTree = ""; }; 430E02ACD0BC78CC53F31D72 /* MockSRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MockSRWebSocket.m; sourceTree = ""; }; 430E092B95390D2D558E5F3B /* MockObjectiveDDPDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MockObjectiveDDPDelegate.m; sourceTree = ""; }; 430E0AEA2F4C48D01B0BC222 /* MockSRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MockSRWebSocket.h; sourceTree = ""; }; 430E0F1E6C3E7422A8791E24 /* MockObjectiveDDPDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MockObjectiveDDPDelegate.h; sourceTree = ""; }; 888B6DFA173B6508001EED80 /* MeteorClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MeteorClient.h; sourceTree = ""; }; 888B6DFB173B6508001EED80 /* MeteorClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MeteorClient.m; sourceTree = ""; }; 888B6DFD173B66EA001EED80 /* BSONIdGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSONIdGenerator.h; sourceTree = ""; }; 888B6DFE173B66EA001EED80 /* BSONIdGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSONIdGenerator.m; sourceTree = ""; }; D05E1D000A1228EEFAD5C7A9 /* Pods-ObjectiveDDP.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ObjectiveDDP.release.xcconfig"; path = "Pods/Target Support Files/Pods-ObjectiveDDP/Pods-ObjectiveDDP.release.xcconfig"; sourceTree = ""; }; F50CB09B9E9DA61FFDCC00AF /* Pods-Specs.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Specs.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Specs/Pods-Specs.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 4000D633170E880400296229 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 401AEB84170E9EEB008B38EA /* Security.framework in Frameworks */, 401AEB82170E9ED2008B38EA /* CFNetwork.framework in Frameworks */, 401AEB80170E9ECC008B38EA /* SystemConfiguration.framework in Frameworks */, 4000D63A170E880400296229 /* Foundation.framework in Frameworks */, 401AEB7E170E9EC7008B38EA /* libicucore.dylib in Frameworks */, 9D3C062D54114FB1AC5700F0 /* libPods.a in Frameworks */, BC2C3115142F54C55A92F9FD /* libPods-ObjectiveDDP.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 4079F9D6170EB31D00194B4A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 4079F9F4170EB36700194B4A /* libicucore.dylib in Frameworks */, 4079F9F3170EB36000194B4A /* SystemConfiguration.framework in Frameworks */, 4079F9F2170EB35800194B4A /* CFNetwork.framework in Frameworks */, 4079F9F1170EB35200194B4A /* Security.framework in Frameworks */, 4079F9DB170EB31D00194B4A /* UIKit.framework in Frameworks */, 4079F9DC170EB31D00194B4A /* Foundation.framework in Frameworks */, 4079F9DE170EB31D00194B4A /* CoreGraphics.framework in Frameworks */, 570DE62F4BD74C18AE51ED8C /* libPods.a in Frameworks */, FD7BC8FE2078D7295BEFE6FA /* libPods-Specs.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 4000D62D170E880400296229 = { isa = PBXGroup; children = ( 40A305F2178D27E800546C1F /* Default-568h@2x.png */, 4000D63B170E880400296229 /* ObjectiveDDP */, 4079F9DF170EB31D00194B4A /* Specs */, 4000D638170E880400296229 /* Frameworks */, 4000D637170E880400296229 /* Products */, 95540A0277F183BFDC9822E6 /* Pods */, ); sourceTree = ""; }; 4000D637170E880400296229 /* Products */ = { isa = PBXGroup; children = ( 4000D636170E880400296229 /* libObjectiveDDP.a */, 4079F9D9170EB31D00194B4A /* Specs.app */, ); name = Products; sourceTree = ""; }; 4000D638170E880400296229 /* Frameworks */ = { isa = PBXGroup; children = ( 401AEB7D170E9EC7008B38EA /* libicucore.dylib */, 4079F9DD170EB31D00194B4A /* CoreGraphics.framework */, 401AEB83170E9EEB008B38EA /* Security.framework */, 401AEB81170E9ED2008B38EA /* CFNetwork.framework */, 401AEB7F170E9ECC008B38EA /* SystemConfiguration.framework */, 4000D639170E880400296229 /* Foundation.framework */, 4079F9DA170EB31D00194B4A /* UIKit.framework */, 2C1F0C9084EA4B2DBD349673 /* libPods.a */, 39ACA448F5A7B3CDD0FC5858 /* libPods-ObjectiveDDP.a */, 320AA851EDF28E77008B96B8 /* libPods-Specs.a */, ); name = Frameworks; sourceTree = ""; }; 4000D63B170E880400296229 /* ObjectiveDDP */ = { isa = PBXGroup; children = ( 4011C80217BD2AA6002AA28E /* DependencyProvider.h */, 4011C80317BD2AA6002AA28E /* DependencyProvider.m */, 888B6DFD173B66EA001EED80 /* BSONIdGenerator.h */, 888B6DFE173B66EA001EED80 /* BSONIdGenerator.m */, 888B6DFA173B6508001EED80 /* MeteorClient.h */, 888B6DFB173B6508001EED80 /* MeteorClient.m */, 052BE033183F8655005CBD6E /* MeteorClient+Private.h */, 40BAB8CF182051500098524C /* MeteorClient+Parsing.m */, 4000D63E170E880400296229 /* ObjectiveDDP.h */, 4000D640170E880400296229 /* ObjectiveDDP.m */, 4000D63C170E880400296229 /* Supporting Files */, ); path = ObjectiveDDP; sourceTree = ""; }; 4000D63C170E880400296229 /* Supporting Files */ = { isa = PBXGroup; children = ( 40E901D1183809DF00CFD98C /* ObjectiveDDP.podspec */, 4000D63D170E880400296229 /* ObjectiveDDP-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; 4079F9DF170EB31D00194B4A /* Specs */ = { isa = PBXGroup; children = ( 4011C80D17BD2E14002AA28E /* DependencyProvider+Spec.m */, 40BAB8CC181EFE550098524C /* NSObject+Spec.m */, 4011C80F17BD310D002AA28E /* DDPSpecHelper.h */, 4011C80A17BD2CA1002AA28E /* DDPSpecHelper.m */, 40F8370E1741C62800AF2340 /* MeteorClientSpec.mm */, 4079F9F6170EB3B000194B4A /* ObjectiveDDPSpec.mm */, 430E092A40EC4FAED3B6C76B /* Mocks */, 4079F9E0170EB31D00194B4A /* Supporting Files */, ); path = Specs; sourceTree = ""; }; 4079F9E0170EB31D00194B4A /* Supporting Files */ = { isa = PBXGroup; children = ( 4079F9E1170EB31D00194B4A /* Specs-Info.plist */, 4079F9E2170EB31D00194B4A /* InfoPlist.strings */, 4079F9E5170EB31D00194B4A /* main.m */, 4079F9E7170EB31D00194B4A /* Specs-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; 430E092A40EC4FAED3B6C76B /* Mocks */ = { isa = PBXGroup; children = ( 430E092B95390D2D558E5F3B /* MockObjectiveDDPDelegate.m */, 430E0F1E6C3E7422A8791E24 /* MockObjectiveDDPDelegate.h */, 4011C80617BD2C06002AA28E /* FakeDependencyProvider.h */, 430E02ACD0BC78CC53F31D72 /* MockSRWebSocket.m */, 430E0AEA2F4C48D01B0BC222 /* MockSRWebSocket.h */, 4011C80717BD2C06002AA28E /* FakeDependencyProvider.m */, ); path = Mocks; sourceTree = ""; }; 95540A0277F183BFDC9822E6 /* Pods */ = { isa = PBXGroup; children = ( 194F89D9560FD1856AE79721 /* Pods-ObjectiveDDP.debug.xcconfig */, D05E1D000A1228EEFAD5C7A9 /* Pods-ObjectiveDDP.release.xcconfig */, F50CB09B9E9DA61FFDCC00AF /* Pods-Specs.debug.xcconfig */, 15654C36C786305476C43A3A /* Pods-Specs.release.xcconfig */, ); name = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 4000D635170E880400296229 /* ObjectiveDDP */ = { isa = PBXNativeTarget; buildConfigurationList = 4000D644170E880400296229 /* Build configuration list for PBXNativeTarget "ObjectiveDDP" */; buildPhases = ( 04C9623BA89EC364FD90CD3D /* Check Pods Manifest.lock */, 4000D632170E880400296229 /* Sources */, 4000D633170E880400296229 /* Frameworks */, 4000D634170E880400296229 /* CopyFiles */, 648CA590930A4D87BE530DBD /* Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = ObjectiveDDP; productName = ObjectiveDDP; productReference = 4000D636170E880400296229 /* libObjectiveDDP.a */; productType = "com.apple.product-type.library.static"; }; 4079F9D8170EB31D00194B4A /* Specs */ = { isa = PBXNativeTarget; buildConfigurationList = 4079F9EC170EB31E00194B4A /* Build configuration list for PBXNativeTarget "Specs" */; buildPhases = ( CE13F78A2A3541BFB51C1FA9 /* Check Pods Manifest.lock */, 4079F9D5170EB31D00194B4A /* Sources */, 4079F9D6170EB31D00194B4A /* Frameworks */, 4079F9D7170EB31D00194B4A /* Resources */, 063296B82B464E808F5E3E74 /* Copy Pods Resources */, B4B4E8EB045D267FC8470639 /* Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Specs; productName = Specs; productReference = 4079F9D9170EB31D00194B4A /* Specs.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 4000D62E170E880400296229 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0460; ORGANIZATIONNAME = Rebounds; }; buildConfigurationList = 4000D631170E880400296229 /* Build configuration list for PBXProject "ObjectiveDDP" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 4000D62D170E880400296229; productRefGroup = 4000D637170E880400296229 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 4000D635170E880400296229 /* ObjectiveDDP */, 4079F9D8170EB31D00194B4A /* Specs */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 4079F9D7170EB31D00194B4A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 4079F9E4170EB31D00194B4A /* InfoPlist.strings in Resources */, 40A305F3178D27E800546C1F /* Default-568h@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 04C9623BA89EC364FD90CD3D /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 063296B82B464E808F5E3E74 /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Specs/Pods-Specs-resources.sh\"\n"; showEnvVarsInLog = 0; }; 648CA590930A4D87BE530DBD /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ObjectiveDDP/Pods-ObjectiveDDP-resources.sh\"\n"; showEnvVarsInLog = 0; }; B4B4E8EB045D267FC8470639 /* Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Specs/Pods-Specs-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; CE13F78A2A3541BFB51C1FA9 /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 4000D632170E880400296229 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 4000D641170E880400296229 /* ObjectiveDDP.m in Sources */, 888B6DFC173B6508001EED80 /* MeteorClient.m in Sources */, 40BAB8D0182051500098524C /* MeteorClient+Parsing.m in Sources */, 888B6DFF173B66EA001EED80 /* BSONIdGenerator.m in Sources */, 4011C80417BD2AA6002AA28E /* DependencyProvider.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 4079F9D5170EB31D00194B4A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 40F8370C1741C59700AF2340 /* BSONIdGenerator.m in Sources */, 40F8370D1741C59700AF2340 /* MeteorClient.m in Sources */, 40BAB8D1182051500098524C /* MeteorClient+Parsing.m in Sources */, 4079F9F5170EB39200194B4A /* ObjectiveDDP.m in Sources */, 4079F9E6170EB31D00194B4A /* main.m in Sources */, 4079F9F7170EB3B000194B4A /* ObjectiveDDPSpec.mm in Sources */, 430E08B6E86CA801EE954D0F /* MockObjectiveDDPDelegate.m in Sources */, 40BAB8CD181EFE550098524C /* NSObject+Spec.m in Sources */, 430E014E41C9666E08613FCE /* MockSRWebSocket.m in Sources */, 40F8370F1741C62800AF2340 /* MeteorClientSpec.mm in Sources */, 4011C80517BD2AA6002AA28E /* DependencyProvider.m in Sources */, 4011C80817BD2C06002AA28E /* FakeDependencyProvider.m in Sources */, 4011C80B17BD2CA1002AA28E /* DDPSpecHelper.m in Sources */, 4011C80E17BD2E14002AA28E /* DependencyProvider+Spec.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 4079F9E2170EB31D00194B4A /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 4079F9E3170EB31D00194B4A /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 4000D642170E880400296229 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 6.1; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; }; 4000D643170E880400296229 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 6.1; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 4000D645170E880400296229 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 194F89D9560FD1856AE79721 /* Pods-ObjectiveDDP.debug.xcconfig */; buildSettings = { DSTROOT = /tmp/ObjectiveDDP.dst; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ObjectiveDDP/ObjectiveDDP-Prefix.pch"; HEADER_SEARCH_PATHS = "${PODS_HEADERS_SEARCH_PATHS}"; IPHONEOS_DEPLOYMENT_TARGET = 7.1; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\\\"$(SDKROOT)/usr/lib/system\\\"", "\\\"$(SRCROOT)/ObjectiveDDP\\\"", /Users/boundsj/workspace/ios/ObjectiveDDP/ObjectiveDDP, ); OTHER_LDFLAGS = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; name = Debug; }; 4000D646170E880400296229 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = D05E1D000A1228EEFAD5C7A9 /* Pods-ObjectiveDDP.release.xcconfig */; buildSettings = { DSTROOT = /tmp/ObjectiveDDP.dst; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "ObjectiveDDP/ObjectiveDDP-Prefix.pch"; HEADER_SEARCH_PATHS = "${PODS_HEADERS_SEARCH_PATHS}"; IPHONEOS_DEPLOYMENT_TARGET = 7.1; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\\\"$(SDKROOT)/usr/lib/system\\\"", "\\\"$(SRCROOT)/ObjectiveDDP\\\"", /Users/boundsj/workspace/ios/ObjectiveDDP/ObjectiveDDP, ); OTHER_LDFLAGS = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; name = Release; }; 4079F9ED170EB31E00194B4A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = F50CB09B9E9DA61FFDCC00AF /* Pods-Specs.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Specs/Specs-Prefix.pch"; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Specs/Specs-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 7.1; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/lib/system\"", "\"$(SRCROOT)/ObjectiveDDP\"", ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "$(inherited)", ); OTHER_LDFLAGS = "$(inherited)"; PRODUCT_NAME = "$(TARGET_NAME)"; VALID_ARCHS = "arm64 armv7 arm7s"; WRAPPER_EXTENSION = app; }; name = Debug; }; 4079F9EE170EB31E00194B4A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 15654C36C786305476C43A3A /* Pods-Specs.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Specs/Specs-Prefix.pch"; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Specs/Specs-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 7.1; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/lib/system\"", "\"$(SRCROOT)/ObjectiveDDP\"", ); OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_NAME = "$(TARGET_NAME)"; VALID_ARCHS = "arm64 armv7 arm7s"; WRAPPER_EXTENSION = app; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 4000D631170E880400296229 /* Build configuration list for PBXProject "ObjectiveDDP" */ = { isa = XCConfigurationList; buildConfigurations = ( 4000D642170E880400296229 /* Debug */, 4000D643170E880400296229 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 4000D644170E880400296229 /* Build configuration list for PBXNativeTarget "ObjectiveDDP" */ = { isa = XCConfigurationList; buildConfigurations = ( 4000D645170E880400296229 /* Debug */, 4000D646170E880400296229 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 4079F9EC170EB31E00194B4A /* Build configuration list for PBXNativeTarget "Specs" */ = { isa = XCConfigurationList; buildConfigurations = ( 4079F9ED170EB31E00194B4A /* Debug */, 4079F9EE170EB31E00194B4A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 4000D62E170E880400296229 /* Project object */; } ================================================ FILE: ObjectiveDDP.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: ObjectiveDDP.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Podfile ================================================ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '7.1' platform :osx, '10.7' target :ObjectiveDDP do pod 'SocketRocket', '= 0.3.1-beta2' pod 'M13OrderedDictionary' end target :Specs do pod 'Cedar' end ================================================ FILE: README.md ================================================ ObjectiveDDP ============ [![Join the chat at https://gitter.im/boundsj/ObjectiveDDP](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/boundsj/ObjectiveDDP?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/boundsj/ObjectiveDDP.png)](https://travis-ci.org/boundsj/ObjectiveDDP) Connect your iOS/OSX applications written in Objective-C or Swift to server applications that communicate with the [DDP protocol created by Meteor](https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md) and, if required by your server, authenticate with it. ``` Note: supports meteor 0.8.2 and above. If you need to migrate please see the srp-upgrade-support branch ``` ###### Now with support for OAuth with Facebook and other services [experiemental] This is unsupported so don't come crying if it eats your cat! or destroys any data. ``` (void)logonWithOAuthAccessToken: (NSString *)accessToken serviceName: (NSString *) serviceName responseCallback: (MeteorClientMethodCallback)responseCallback; ``` Please require the following project to your meteor server for this to work. ``` https://github.com/jasper-lu/facebook-ddp ``` Hopefully meteor will support this natively in the future https://github.com/meteor/meteor/pull/3515 What's Inside ------------- ObjectiveDDP should run well with iOS projects using ARC and iOS 7.1 or above. __**Check out the [example application](https://github.com/boundsj/ObjectiveDDP/wiki/Example-Application) and the [project wiki](https://github.com/boundsj/ObjectiveDDP/wiki) for more information.**__ Here is a sneak peak: ##### Integrate it with your project using CocoaPods: ``` pod 'ObjectiveDDP', '~> 0.2.0' ``` For more information about this, check out [Linking and Building](https://github.com/boundsj/ObjectiveDDP/wiki/Linking-and-using-ObjectiveDDP) in the wiki. ##### Load the library and connect to a meteor server: ```objective-c - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.meteorClient = [[MeteorClient alloc] init]; [self.meteorClient addSubscription:@"awesome_server_mongo_collection"]; ObjectiveDDP *ddp = [[ObjectiveDDP alloc] initWithURLString:@"wss://awesomeapp.meteor.com/websocket" delegate:self.meteorClient]; self.meteorClient.ddp = ddp; [self.meteorClient.ddp connectWebSocket]; } ``` ##### Signup with username: ```objective-c [self.meteor signupWithUsername:self.username.text password:self.password.text fullname:self.fullname responseCallback:^(NSDictionary *response, NSError *error) { if (error) { [self handleFailedAuth:error]; return; } [self handleSuccessfulAuth]; }]; ``` or with email ```objective-c [self.meteor signupWithEmail:self.email.text password:self.password.text fullname:self.fullname.text responseCallback:^(NSDictionary *response, NSError *error) { if (error) { [self handleFailedAuth:error]; return; } [self handleSuccessfulAuth]; }]; ``` or with both ```objective-c [self.meteor signupWithUsernameAndEmail:self.username.text email:self.email.text password:self.password.text fullname:self.fullname.text responseCallback:^(NSDictionary *response, NSError *error) { if (error) { [self handleFailedAuth:error]; return; } [self handleSuccessfulAuth]; }]; ``` ##### Logon using authentication: ```objective-c [self.meteor logonWithUsername:self.username.text password:self.password.text responseCallback:^(NSDictionary *response, NSError *error) { if (error) { [self handleFailedAuth:error]; return; } [self handleSuccessfulAuth]; }]; ``` or with email ```objective-c [self.meteor logonWithEmail:self.email.text password:self.password.text responseCallback:^(NSDictionary *response, NSError *error) { if (error) { [self handleFailedAuth:error]; return; } [self handleSuccessfulAuth]; }]; ``` or if you accept both ```objective-c [self.meteor logonWithUsernameOrEmail:self.usernameOrEmail.text password:self.password.text responseCallback:^(NSDictionary *response, NSError *error) { if (error) { [self handleFailedAuth:error]; return; } [self handleSuccessfulAuth]; }]; ``` ##### Call a remote function on the server: ```objecctive-c [self.meteor callMethodName:@"sayHelloTo" parameters:@[self.username.text] responseCallback:^(NSDictionary *response, NSError *error) { NSString *message = response[@"result"]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Meteor Todos" message:message delegate:nil cancelButtonTitle:@"Great" otherButtonTitles:nil]; [alert show]; }]; ``` ##### Listen for updates that meteor sends regarding the collection previously subscribed to: ```objective-c - (void)viewWillAppear:(BOOL)animated { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveAddedUpdate:) name:@"awesome_server_mongo_collection_added" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveRemovedUpdate:) name:@"awesome_server_mongo_collection_removed" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveChangeUpdate:) name:@"awesome_server_mongo_collection_changed" object:nil]; } ``` ##### Send CRUD updates to the meteor server to change the collection: ```objective-c NSString *message = @"I am the walrus"; NSString *anId = [[NSUUID UUID] UUIDString]; NSArray *parameters = @[@{@"_id": anId, @"msg": message, @"owner": self.userId, @"info": self.importantInformation}]; // add a document [self.meteor callMethodName:@"/awesome_server_mongo_collection/insert" parameters:parameters responseCallback:nil]; // then remove it [self.meteor callMethodName:@"/awesome_server_mongo_collection/remove" parameters:@[@{@"_id": anId}] responseCallback:nil]; ``` ##### Listen for notifications: MeteorClientConnectionReadyNotification - When the server responds as accepting the DDP protocol version to communicate on, you won't be able to call any methods to meteor until this happens ```objective-c [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reportConnection) name:MeteorClientDidConnectNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reportConnectionReady) name:MeteorClientConnectionReadyNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reportDisconnection) name:MeteorClientDidDisconnectNotification object:nil]; ``` License -------------- **[MIT]** [MIT]: http://opensource.org/licenses/MIT ================================================ FILE: Rakefile ================================================ require 'thrust/tasks' require 'thrust/tasks' ================================================ FILE: Specs/DDPSpecHelper.h ================================================ #import #import "FakeDependencyProvider.h" extern FakeDependencyProvider *fakeProvider; @interface DDPSpecHelper : NSObject @end ================================================ FILE: Specs/DDPSpecHelper.m ================================================ #import "DDPSpecHelper.h" #import "FakeDependencyProvider.h" FakeDependencyProvider *fakeProvider = nil; @implementation DDPSpecHelper + (void)beforeEach { fakeProvider = [[[FakeDependencyProvider alloc] init] autorelease]; } @end ================================================ FILE: Specs/DependencyProvider+Spec.m ================================================ #import "DependencyProvider.h" @implementation DependencyProvider (Spec) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" // This will create a linker warning for the Specs target but it is harmless under test + (DependencyProvider *)sharedProvider { return fakeProvider; } #pragma clang diagnostic pop @end ================================================ FILE: Specs/MeteorClientSpec.mm ================================================ #import "MeteorClient+Private.h" #import "ObjectiveDDP.h" #import using namespace Cedar::Matchers; using namespace Cedar::Doubles; using namespace Arguments; SPEC_BEGIN(MeteorClientSpec) describe(@"MeteorClient", ^{ __block MeteorClient *meteorClient; __block ObjectiveDDP *ddp; __block NSString *bestDDPVersion; beforeEach(^{ bestDDPVersion = @"best_version_ever"; ddp = nice_fake_for([ObjectiveDDP class]); meteorClient = [[[MeteorClient alloc] initWithDDPVersion:bestDDPVersion] autorelease]; ddp.delegate = meteorClient; meteorClient.ddp = ddp; meteorClient.authDelegate = nice_fake_for(@protocol(DDPAuthDelegate)); spy_on(ddp); }); it(@"is correctly initialized", ^{ meteorClient.websocketReady should_not be_truthy; meteorClient.connected should_not be_truthy; meteorClient.collections should_not be_nil; meteorClient->_subscriptions should_not be_nil; meteorClient.authState should equal(AuthStateNoAuth); meteorClient.ddpVersion should equal(bestDDPVersion); }); describe(@"#disconnect", ^{ beforeEach(^{ [meteorClient disconnect]; }); it(@"tells ddp to disconnect", ^{ ddp should have_received(@selector(disconnectWebSocket)); }); }); // describe(@"#signupWithEmail:password:fullname:responseCallback:", ^{ // // context(@"when connected", ^{ // beforeEach(^{ // meteorClient.connected = YES; // [meteorClient signupWithEmail:@"mrt@ateam.com" password:@"password" fullname:@"mr bean" responseCallback:nil]; // }); // // it(@"sends signup message correctly", ^{ // NSArray *sentMessages = [(id)ddp sent_messages]; // NSInvocation *invocation = sentMessages[1]; // NSArray *sentParameters; // [invocation getArgument:&sentParameters atIndex:4]; // ddp should have_received(@selector(methodWithId:method:parameters:)).with(@"1").and_with(@"createUser").and_with(@[@{ @"username": @"", // @"email": @"mrt@ateam.com", @"password": @{ @"digest": [meteorClient sha256:@"password"], @"algorithm": @"sha-256" }, // @"profile": @{ @"fullname": @"mr bean",@"signupToken": @"" } }]); // }); // // }); // // afterEach(^{ // [meteorClient logout]; // meteorClient.connected = NO; // // }); // }); // describe(@"#signupWithUsername:password:fullname:responseCallback:", ^{ // // context(@"with username", ^{ // beforeEach(^{ // meteorClient.connected = YES; // [meteorClient signupWithUsername:@"fox" password:@"fool" fullname:@"mr fox" responseCallback:nil]; // }); // // // it(@"sends signup message correctly", ^{ // NSArray *sentMessages = [(id)ddp sent_messages]; // NSInvocation *invocation = sentMessages[1]; // NSArray *sentParameters; // [invocation getArgument:&sentParameters atIndex:4]; //// NSLog(@"%@", [sentParameters debugDescription]); // ddp should have_received(@selector(methodWithId:method:parameters:)).with(@"1").and_with(@"createUser").and_with(@[@{ @"username": @"fox", // @"email": @"", @"password":@{ @"digest": [meteorClient sha256:@"fool"], @"algorithm": @"sha-256" }, // @"profile": @{ @"fullname": @"mr fox",@"signupToken": @"" }}]); // }); // // }); // }); describe(@"#logonWithUserParameters:", ^{ context(@"when connected", ^{ beforeEach(^{ meteorClient.connected = YES; [meteorClient logonWithUserParameters:@{ @"user": @{ @"email": @"mrt@ateam.com" }, @"password": @{ @"digest": [meteorClient sha256:@"fool"], @"algorithm": @"sha-256" } } responseCallback:nil]; }); it(@"sends logon message correctly", ^{ NSArray *sentMessages = [(id)ddp sent_messages]; NSInvocation *invocation = sentMessages[1]; NSArray *sentParameters; [invocation getArgument:&sentParameters atIndex:4]; ddp should have_received(@selector(methodWithId:method:parameters:)).with(@"1").and_with(@"login").and_with(@[@{ @"user": @{ @"email": @"mrt@ateam.com" }, @"password": @{ @"digest": [meteorClient sha256:@"fool"], @"algorithm": @"sha-256" } }]); }); }); }); describe(@"#logonWithUsername:password:responseCallback:", ^{ __block NSDictionary *successResponse = nil; __block NSError *errorResponse = nil; context(@"when connected", ^{ beforeEach(^{ meteorClient.connected = YES; [meteorClient logonWithUsername:@"fox" password:@"wh4tdo1Say?" responseCallback:^(NSDictionary *response, NSError *error) { successResponse = response; errorResponse = error; }]; }); context(@"when the user is not already logging in", ^{ it(@"sends logon message correctly", ^{ ddp should have_received(@selector(methodWithId:method:parameters:)).with(anything).and_with(@"login").and_with(anything); }); context(@"if the user tries to logon before current attempt finishes", ^{ beforeEach(^{ [(id)ddp reset_sent_messages]; [meteorClient logonWithUsername:@"fox" password:@"wh4tdo1Say?" responseCallback:^(NSDictionary *response, NSError *error) { successResponse = response; errorResponse = error; }]; }); it(@"does not allow the second attempt", ^{ ddp should_not have_received(@selector(methodWithId:method:parameters:)); }); it(@"rejects the callback with the correct error", ^{ NSString *errorDesc = [NSString stringWithFormat:@"You must wait for the current logon request to finish before sending another."]; NSError *expectedError = [NSError errorWithDomain:MeteorClientTransportErrorDomain code:MeteorClientErrorLogonRejected userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; errorResponse should equal(expectedError); successResponse should be_nil; }); }); context(@"when the logon request succeeds", ^{ beforeEach(^{ NSDictionary *message = @{@"id": @"blarg"}; NSDictionary *logonSuccessfulResponse = @{@"id": @"5", @"msg": @"result", @"result": message}; [meteorClient didReceiveMessage:logonSuccessfulResponse]; }); it(@"calls the response callback correctly", ^{ successResponse should_not be_nil; errorResponse should be_nil; meteorClient.userId should equal(@"blarg"); }); }); context(@"when the logon request fails", ^{ beforeEach(^{ NSDictionary *loginErrorMessage = @{@"error": @403, @"message": @"you suck", @"errorType": @"screwed"}; NSDictionary *logonSuccessfulResponse = @{@"id": @"6", @"msg": @"result", @"error": loginErrorMessage}; [meteorClient didReceiveMessage:logonSuccessfulResponse]; }); // it(@"calls the response callback correctly", ^{ // NSError *expectedError = [NSError errorWithDomain:@"screwed" code:403 userInfo:@{NSLocalizedDescriptionKey: @"you suck"}]; // errorResponse should equal(expectedError); // successResponse should be_nil; // }); }); }); }); context(@"when not connected", ^{ beforeEach(^{ meteorClient.connected = NO; [meteorClient logonWithUsername:@"fox" password:@"wh4tdo1Say?" responseCallback:^(NSDictionary *response, NSError *error) { successResponse = response; errorResponse = error; }]; }); it(@"does not send", ^{ ddp should_not have_received(@selector(methodWithId:method:parameters:)); }); it(@"rejects the callback with the correct error", ^{ NSString *errorDesc = [NSString stringWithFormat:@"You are not connected"]; NSError *expectedError = [NSError errorWithDomain:MeteorClientTransportErrorDomain code:MeteorClientErrorNotConnected userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; errorResponse should equal(expectedError); successResponse should be_nil; }); }); }); describe(@"#addSubscription:", ^{ context(@"when connected", ^{ beforeEach(^{ meteorClient.connected = YES; [meteorClient addSubscription:@"a fancy subscription"]; }); it(@"should call ddp subscribe method", ^{ ddp should have_received("subscribeWith:name:parameters:").with(anything) .and_with(@"a fancy subscription") .and_with(nil); }); }); context(@"when not connected", ^{ beforeEach(^{ meteorClient.connected = NO; [meteorClient addSubscription:@"a fancy subscription"]; }); it(@"should not call ddp subscribe method", ^{ ddp should_not have_received("subscribeWith:name:parameters:"); }); }); }); describe(@"#removeSubscription:", ^{ context(@"when not connected", ^{ beforeEach(^{ meteorClient.connected = YES; [meteorClient->_subscriptions setObject:@"id1" forKey:@"fancySubscriptionName"]; [meteorClient->_subscriptions count] should equal(1); [meteorClient removeSubscription:@"fancySubscriptionName"]; }); it(@"removes subscription correctly", ^{ ddp should have_received(@selector(unsubscribeWith:)); [meteorClient->_subscriptions count] should equal(0); }); }); context(@"when not connected", ^{ beforeEach(^{ meteorClient.connected = NO; [meteorClient->_subscriptions setObject:@"id1" forKey:@"fancySubscriptionName"]; [meteorClient->_subscriptions count] should equal(1); [meteorClient removeSubscription:@"fancySubscriptionName"]; }); it(@"does not remove subscription", ^{ ddp should_not have_received(@selector(unsubscribeWith:)); [meteorClient->_subscriptions count] should equal(1); }); }); }); describe(@"#didOpen", ^{ beforeEach(^{ spy_on([NSNotificationCenter defaultCenter]); M13MutableOrderedDictionary *array = [[[M13MutableOrderedDictionary alloc] init] autorelease]; meteorClient.collections = [NSMutableDictionary dictionaryWithDictionary:@{@"col1": array}]; [meteorClient.collections count] should equal(1); [meteorClient didOpen]; }); it(@"sets the web socket state to ready", ^{ meteorClient.websocketReady should be_truthy; [meteorClient.collections count] should equal(0); ddp should have_received(@selector(connectWithSession:version:support:)); }); it(@"sends a notification", ^{ [NSNotificationCenter defaultCenter] should have_received(@selector(postNotificationName:object:)) .with(MeteorClientDidConnectNotification) .and_with(meteorClient); }); }); describe(@"#didReceiveConnectionClose", ^{ beforeEach(^{ meteorClient.websocketReady = YES; meteorClient.connected = YES; }); context(@"when websocket is not disconnecting", ^{ beforeEach(^{ [meteorClient didReceiveConnectionClose]; }); it(@"resets collections and reconnects web socket", ^{ meteorClient.websocketReady should_not be_truthy; meteorClient.connected should_not be_truthy; ddp should have_received(@selector(connectWebSocket)); }); }); context(@"when websocket is disconnecting", ^{ beforeEach(^{ [meteorClient disconnect]; [meteorClient didReceiveConnectionClose]; }); it(@"does not attempt to reconnect", ^{ ddp should_not have_received(@selector(connectWebSocket)); }); }); }); describe(@"#didReceiveConnectionError", ^{ __block NSError *rejectError; beforeEach(^{ spy_on([NSNotificationCenter defaultCenter]); meteorClient.websocketReady = YES; meteorClient.connected = YES; [meteorClient callMethodName:@"robots" parameters:nil responseCallback:^(NSDictionary *response, NSError *error) { rejectError = error; }]; meteorClient->_methodIds.count should equal(1); meteorClient->_responseCallbacks.count should equal(1); }); context(@"when websocket is not disconnecting", ^{ beforeEach(^{ [meteorClient didReceiveConnectionError:nil]; [meteorClient callMethodName:@"robots" parameters:nil responseCallback:^(NSDictionary *response, NSError *error) { rejectError = error; }]; }); it(@"resets collections and reconnects web socket", ^{ meteorClient.websocketReady should_not be_truthy; meteorClient.connected should_not be_truthy; meteorClient->_methodIds.count should equal(0); meteorClient->_responseCallbacks.count should equal(0); ddp should have_received(@selector(connectWebSocket)); }); it(@"rejects unresolved callbacks", ^{ NSError *expectedError = [NSError errorWithDomain:MeteorClientTransportErrorDomain code:MeteorClientErrorDisconnectedBeforeCallbackComplete userInfo:@{NSLocalizedDescriptionKey: @"You were disconnected"}]; rejectError should equal(expectedError); }); it(@"sends a notification", ^{ [NSNotificationCenter defaultCenter] should have_received(@selector(postNotificationName:object:)) .with(MeteorClientDidDisconnectNotification) .and_with(meteorClient); }); }); context(@"when the websocket is disconnecting", ^{ beforeEach(^{ [meteorClient disconnect]; [meteorClient didReceiveConnectionError:nil]; }); it(@"does not attempt to reconnect", ^{ ddp should_not have_received(@selector(connectWebSocket)); }); }); }); describe(@"#didReceiveMessage", ^{ __block NSString *key; describe(@"RPC async method response", ^{ __block NSDictionary *returnedResponse; __block NSError *returnedError; beforeEach(^{ meteorClient.connected = YES; }); context(@"when the response is successful", ^{ beforeEach(^{ key = [meteorClient callMethodName:@"robots" parameters:nil responseCallback:^(NSDictionary *response, NSError *error) { returnedResponse = response; }]; [meteorClient didReceiveMessage:@{@"msg": @"result", @"result": @"rule", @"id": key }]; }); it(@"has the correct returned response", ^{ returnedResponse[@"result"] should equal(@"rule"); }); }); context(@"when the response fails", ^{ beforeEach(^{ key = [meteorClient callMethodName:@"robots" parameters:nil responseCallback:^(NSDictionary *response, NSError *error) { returnedError = error; }]; [meteorClient didReceiveMessage:@{@"msg": @"result", @"error": @{@"errorType": @"lamesauce", @"error": @500, @"message": @"you suck"}, @"id": key}]; }); it(@"has the correct returned response", ^{ NSDictionary *errorDic = @{@"errorType": @"lamesauce", @"error": @500, @"message": @"you suck"}; NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorDic[@"message"]}; NSError *expectedError = [NSError errorWithDomain:errorDic[@"errorType"] code:[errorDic[@"error"]integerValue] userInfo:userInfo]; returnedError should equal(expectedError); }); }); }); context(@"when subscription is ready", ^{ beforeEach(^{ [meteorClient->_subscriptions setObject:@"subid" forKey:@"subscriptionName"]; NSDictionary *readyMessage = @{@"msg": @"ready", @"subs": @[@"subid"]}; [meteorClient didReceiveMessage:readyMessage]; }); it(@"processes the message correctly", ^{ SEL postSel = @selector(postNotificationName:object:); [NSNotificationCenter defaultCenter] should have_received(postSel) .with(@"subscriptionName_ready") .and_with(meteorClient); }); }); context(@"when called with an 'added' message", ^{ beforeEach(^{ NSDictionary *addedMessage = @{ @"msg": @"added", @"id": @"id1", @"collection": @"phrases", @"fields": @{@"text": @"this is ridiculous"} }; [meteorClient didReceiveMessage:addedMessage]; }); it(@"processes the message correctly", ^{ [meteorClient.collections[@"phrases"] count] should equal(1); NSDictionary *phrase = meteorClient.collections[@"phrases"][0]; phrase[@"text"] should equal(@"this is ridiculous"); SEL postSel = @selector(postNotificationName:object:userInfo:); [NSNotificationCenter defaultCenter] should have_received(postSel).with(@"added") .and_with(meteorClient) .and_with(phrase); [NSNotificationCenter defaultCenter] should have_received(postSel).with(@"phrases_added") .and_with(meteorClient) .and_with(phrase); }); context(@"when called with a changed message", ^{ beforeEach(^{ NSDictionary *changedMessage = @{ @"msg": @"changed", @"id": @"id1", @"collection": @"phrases", @"fields": @{@"text": @"this is really ridiculous"} }; [meteorClient didReceiveMessage:changedMessage]; }); it(@"processes the message correctly", ^{ [meteorClient.collections[@"phrases"] count] should equal(1); NSDictionary *phrase = meteorClient.collections[@"phrases"][0]; phrase[@"text"] should equal(@"this is really ridiculous"); SEL postSel = @selector(postNotificationName:object:userInfo:); [NSNotificationCenter defaultCenter] should have_received(postSel).with(@"changed") .and_with(meteorClient) .and_with(phrase); [NSNotificationCenter defaultCenter] should have_received(postSel).with(@"phrases_changed") .and_with(meteorClient) .and_with(phrase); }); }); context(@"when called with a removed message", ^{ beforeEach(^{ NSDictionary *removedMessage = @{ @"msg": @"removed", @"id": @"id1", @"collection": @"phrases", }; [meteorClient didReceiveMessage:removedMessage]; }); it(@"processes the message correctly", ^{ [meteorClient.collections[@"phrases"] count] should equal(0); SEL postSel = @selector(postNotificationName:object:); SEL postObjSel = @selector(postNotificationName:object:userInfo:); [NSNotificationCenter defaultCenter] should have_received(postSel).with(@"removed") .and_with(meteorClient); [NSNotificationCenter defaultCenter] should have_received(postObjSel).with(@"phrases_removed") .and_with(meteorClient) .and_with(@{@"_id": @"id1"}); }); }); }); context(@"when called with an 'addedBefore' message", ^{ beforeEach(^{ NSDictionary *addedMessage = @{ @"msg": @"added", @"id": @"id0", @"collection": @"phrases", @"fields": @{@"text": @"this is ridiculous"} }; [meteorClient didReceiveMessage:addedMessage]; NSDictionary *addedBeforeMessage = @{ @"msg": @"addedBefore", @"id": @"id1", @"collection": @"phrases", @"fields": @{@"text": @"this is before ridiculous"}, @"before":@"id0" }; [meteorClient didReceiveMessage:addedBeforeMessage]; }); it(@"processes the message correctly", ^{ [meteorClient.collections[@"phrases"] count] should equal(2); //check the order NSDictionary *phrase = meteorClient.collections[@"phrases"][0]; phrase[@"text"] should equal(@"this is before ridiculous"); SEL postSel = @selector(postNotificationName:object:userInfo:); [NSNotificationCenter defaultCenter] should have_received(postSel).with(@"addedBefore") .and_with(meteorClient) .and_with(phrase); [NSNotificationCenter defaultCenter] should have_received(postSel).with(@"phrases_addedBefore") .and_with(meteorClient) .and_with(phrase); }); }); context(@"when called with an 'movedBefore' message", ^{ beforeEach(^{ NSDictionary *addedMessage = @{ @"msg": @"added", @"id": @"id0", @"collection": @"phrases", @"fields": @{@"text": @"this is ridiculous"} }; [meteorClient didReceiveMessage:addedMessage]; NSDictionary *addedNextMessage = @{ @"msg": @"added", @"id": @"id1", @"collection": @"phrases", @"fields": @{@"text": @"this is before ridiculous"} }; [meteorClient didReceiveMessage:addedNextMessage]; NSDictionary *movedBeforeMessage = @{ @"msg": @"movedBefore", @"id": @"id1", @"collection": @"phrases", @"before":@"id0" }; [meteorClient didReceiveMessage:movedBeforeMessage]; }); it(@"processes the message correctly", ^{ [meteorClient.collections[@"phrases"] count] should equal(2); //check the order NSDictionary *phrase = meteorClient.collections[@"phrases"][0]; phrase[@"text"] should equal(@"this is before ridiculous"); SEL postSel = @selector(postNotificationName:object:userInfo:); [NSNotificationCenter defaultCenter] should have_received(postSel).with(@"movedBefore") .and_with(meteorClient) .and_with(phrase); [NSNotificationCenter defaultCenter] should have_received(postSel).with(@"phrases_movedBefore") .and_with(meteorClient) .and_with(phrase); }); }); }); }); SPEC_END ================================================ FILE: Specs/Mocks/FakeDependencyProvider.h ================================================ #import "DependencyProvider.h" @class MockSRWebSocket; @interface FakeDependencyProvider : DependencyProvider @property (nonatomic, assign) MockSRWebSocket *fakeSRWebSocket; @end ================================================ FILE: Specs/Mocks/FakeDependencyProvider.m ================================================ #import "FakeDependencyProvider.h" #import "MockSRWebSocket.h" @implementation FakeDependencyProvider - (SRWebSocket *)provideSRWebSocketWithRequest:(NSURLRequest *)request { if (self.fakeSRWebSocket) { return self.fakeSRWebSocket; } return [super provideSRWebSocketWithRequest:request]; } @end ================================================ FILE: Specs/Mocks/MockObjectiveDDPDelegate.h ================================================ #import #import "ObjectiveDDP.h" @interface MockObjectiveDDPDelegate : NSObject @end ================================================ FILE: Specs/Mocks/MockObjectiveDDPDelegate.m ================================================ #import "MockObjectiveDDPDelegate.h" @implementation MockObjectiveDDPDelegate - (void)didOpen { } - (void)didReceiveMessage:(NSDictionary *)message { } - (void)didReceiveConnectionError:(NSError *)error { } - (void)didReceiveConnectionClose { } @end ================================================ FILE: Specs/Mocks/MockSRWebSocket.h ================================================ #import #import @interface MockSRWebSocket : SRWebSocket - (void)connectionSuccess; - (void)connectionFailure; - (void)respondWithJSONString:(NSString *)json; @property (nonatomic, assign) id delegate; @end ================================================ FILE: Specs/Mocks/MockSRWebSocket.m ================================================ #import "MockSRWebSocket.h" @implementation MockSRWebSocket - (void)connectionSuccess { [self.delegate webSocketDidOpen:self]; } - (void)connectionFailure { NSError *error = [[NSError alloc] initWithDomain:@"domain" code:42 userInfo:nil]; [self.delegate webSocket:self didFailWithError:error]; } - (void)respondWithJSONString:(NSString *)json { [self.delegate webSocket:self didReceiveMessage:json]; } - (void)open { /* mock no op */ } - (void)send:(id)data { /* mock no op */ } - (void)close { /* mock no op */ } @end ================================================ FILE: Specs/NSObject+Spec.m ================================================ @implementation NSObject (Spec) - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay { [self performSelectorOnMainThread:aSelector withObject:self waitUntilDone:YES]; } @end ================================================ FILE: Specs/ObjectiveDDPSpec.mm ================================================ #import "ObjectiveDDP.h" #import "MockObjectiveDDPDelegate.h" #import "MockSRWebSocket.h" using namespace Cedar::Matchers; using namespace Cedar::Doubles; SPEC_BEGIN(ObjectiveDDPSpec) describe(@"ObjectiveDDP", ^{ __block ObjectiveDDP *ddp; __block MockSRWebSocket *fakeSRWebSocket; __block MockObjectiveDDPDelegate *fakeDDPDelegate; describe(@"when the framework is initialized", ^{ beforeEach(^{ fakeSRWebSocket = [[MockSRWebSocket alloc] init]; fakeProvider.fakeSRWebSocket = fakeSRWebSocket; fakeDDPDelegate = nice_fake_for(@protocol(ObjectiveDDPDelegate)); spy_on(fakeSRWebSocket); spy_on(fakeDDPDelegate); ddp = [[ObjectiveDDP alloc] initWithURLString:@"websocket" delegate:fakeDDPDelegate]; fakeSRWebSocket.delegate = ddp; }); it(@"should have the correct url string", ^{ ddp.urlString should equal(@"websocket"); }); describe(@"when the web socket closes", ^{ beforeEach(^{ [ddp webSocket:nil didCloseWithCode:0 reason:@"reason" wasClean:YES]; }); it(@"tells its delegate", ^{ fakeDDPDelegate should have_received("didReceiveConnectionClose"); }); }); describe(@"when connectWebSocket is called ", ^{ beforeEach(^{ [ddp connectWebSocket]; }); it(@"should open the websocket", ^{ fakeSRWebSocket should have_received("open"); }); describe(@"when the websocket is opened successfully", ^{ beforeEach(^{ [fakeSRWebSocket connectionSuccess]; }); it(@"should notify its delegate", ^{ fakeDDPDelegate should have_received("didOpen"); }); }); describe(@"when the websocket open fails", ^{ beforeEach(^{ [fakeSRWebSocket connectionFailure]; }); it(@"should notify its delegate", ^{ fakeDDPDelegate should have_received("didReceiveConnectionError:"); }); }); describe(@"when disconnectWebSocket is called", ^{ beforeEach(^{ [ddp disconnectWebSocket]; }); it(@"closes the web socket", ^{ fakeSRWebSocket should have_received(@selector(close)); }); }); }); describe(@"when connect is called with no session or support", ^{ beforeEach(^{ [ddp connectWebSocket]; [fakeSRWebSocket connectionSuccess]; [ddp connectWithSession:nil version:@"smersion" support:@[@"smersion"]]; }); it(@"should call the web socket with correct JSON", ^{ NSString *expected = @"{\"msg\":\"connect\",\"version\":\"smersion\",\"support\":[\"smersion\"]}"; fakeSRWebSocket should have_received("send:").with(expected); }); describe(@"when the websocket is opened successfully", ^{ beforeEach(^{ [fakeSRWebSocket connectionSuccess]; }); it(@"should notify its delegate", ^{ fakeDDPDelegate should have_received("didOpen"); }); }); describe(@"when the call is successful", ^{ beforeEach(^{ [fakeSRWebSocket respondWithJSONString:@"{\"msg\":\"connected\",\"session\":\"SERVER-GEN-SESSION-ID-VAL\"}"]; }); it(@"it should notify its delegate", ^{ NSDictionary *expected = @{@"msg": @"connected", @"session":@"SERVER-GEN-SESSION-ID-VAL"}; fakeDDPDelegate should have_received("didReceiveMessage:").with(expected); }); }); describe(@"when the call is not successful", ^{ beforeEach(^{ [fakeSRWebSocket respondWithJSONString:@"{\"msg\":\"failed\",\"version\":\"smersion2\"}"]; }); it(@"should notify its delegate", ^{ NSDictionary *expected = @{@"msg": @"failed", @"version":@"smersion2"}; fakeDDPDelegate should have_received("didReceiveMessage:").with(expected); }); }); }); describe(@"when subscribe is called with nil parameters", ^{ beforeEach(^{ [ddp connectWebSocket]; [fakeSRWebSocket connectionSuccess]; [ddp subscribeWith:@"id1" name:@"publishedCollection" parameters:nil]; }); it(@"should call the websocket correctly", ^{ NSString *expected = @"{\"msg\":\"sub\",\"name\":\"publishedCollection\",\"id\":\"id1\"}"; fakeSRWebSocket should have_received("send:").with(expected); }); }); describe(@"when method is called", ^{ beforeEach(^{ [ddp connectWebSocket]; [fakeSRWebSocket connectionSuccess]; NSArray *params = @[@{@"_id": @"abc", @"msg": @"ohai"}]; [ddp methodWithId:@"id" method:@"/do/something" parameters:params]; }); it(@"should call the websocket correctly", ^{ NSString *expected = @"{\"method\":\"\\/do\\/something\",\"id\":\"id\",\"params\":[{\"_id\":\"abc\",\"msg\":\"ohai\"}],\"msg\":\"method\"}"; fakeSRWebSocket should have_received("send:").with(expected); }); }); describe(@"when unsubscribeWith is called", ^{ beforeEach(^{ [ddp connectWebSocket]; [fakeSRWebSocket connectionSuccess]; [ddp unsubscribeWith:@"id1"]; }); it(@"calls the websocket correctly", ^{ NSString *expected = @"{\"msg\":\"unsub\",\"id\":\"id1\"}"; fakeSRWebSocket should have_received("send:").with(expected); }); }); }); }); SPEC_END ================================================ FILE: Specs/Specs-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.rebounds.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UIRequiredDeviceCapabilities armv7 ================================================ FILE: Specs/Specs-Prefix.pch ================================================ // // Prefix header for all source files of the 'Specs' target in the 'Specs' project // #ifdef __OBJC__ #import #import #import #import "DDPSpecHelper.h" #endif ================================================ FILE: Specs/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: Specs/main.m ================================================ // // main.m // Specs // // Created by Jesse Bounds on 4/5/13. // Copyright (c) 2013 Rebounds. All rights reserved. // #import int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, @"CedarApplicationDelegate"); } } ================================================ FILE: thrust.yml ================================================ thrust_version: 0.5 workspace_name: ObjectiveDDP # use if building with an xcode workspace app_name: ObjectiveDDP ios_sim_binary: 'ios-sim' # or wax-sim. iOS only. testflight: api_token: 'testflight api token' # To find your App Token, follow the instructions at: http://help.testflightapp.com/customer/portal/articles/829956-what-does-the-api-token-do- team_token: 'testflight team token' # To find your Team Token, follow the instructions at: http://help.testflightapp.com/customer/portal/articles/829942-how-do-i-find-my-team-token- deployment_targets: staging: distribution_list: Developers # This is the name of a TestFlight distribution list notify: true # Whether to notify people on the distribution list about this deployment note_generation_method: autotag # If you set this value, it will auto-generate the deploy notes from the commit history. Optional. ios_target: MyGreatAppTarget # Name of the build target. Optional, defaults to app name. iOS only. ios_build_configuration: Release # iOS only ios_provisioning_search_query: 'query to find Provisioning Profile' # iOS only. Optional. demo: distribution_list: Beta Testers notify: true ios_spec_targets: specs: # This is the name of the rake task: `rake specs` target: Specs # name of the build target scheme: Specs # use in addition to target when you want to use a scheme (necessary if you are building with an xcode workspace) build_configuration: Debug # name of the build configuration build_sdk: iphonesimulator # SDK used to build the target. Optional, defaults to latest iphonesimulator. runtime_sdk: 7.1 # SDK used to run the target. Not optional. device: iPhone # Device to run the specs on. Optional, defaults to iPhone. #integration: # target: IntegrationSpecs # # scheme: IntegrationSpecs (My Great App) # use in addition to target when you want to use a scheme (necessary if you are building with an xcode workspace) # build_configuration: Release # build_sdk: macosx # runtime_sdk: macosx