1// Copyright © 2018 650 Industries. All rights reserved.
2
3#import <MessageUI/MessageUI.h>
4#import <ABI47_0_0EXSMS/ABI47_0_0EXSMSModule.h>
5#import <ABI47_0_0ExpoModulesCore/ABI47_0_0EXUtilities.h>
6#if SD_MAC
7#import <CoreServices/CoreServices.h>
8#else
9#import <MobileCoreServices/MobileCoreServices.h>
10#endif
11
12@interface ABI47_0_0EXSMSModule () <MFMessageComposeViewControllerDelegate>
13
14@property (nonatomic, weak) id<ABI47_0_0EXUtilitiesInterface> utils;
15@property (nonatomic, strong) ABI47_0_0EXPromiseResolveBlock resolve;
16@property (nonatomic, strong) ABI47_0_0EXPromiseRejectBlock reject;
17
18@end
19
20@implementation ABI47_0_0EXSMSModule
21
22ABI47_0_0EX_EXPORT_MODULE(ExpoSMS);
23
24- (dispatch_queue_t)methodQueue
25{
26  // Everything in this module uses `MFMessageComposeViewController` which is a subclass of UIViewController,
27  // so everything should be called from main thread.
28  return dispatch_get_main_queue();
29}
30
31- (void)setModuleRegistry:(ABI47_0_0EXModuleRegistry *)moduleRegistry
32{
33  _utils = [moduleRegistry getModuleImplementingProtocol:@protocol(ABI47_0_0EXUtilitiesInterface)];
34}
35
36ABI47_0_0EX_EXPORT_METHOD_AS(isAvailableAsync,
37                    isAvailable:(ABI47_0_0EXPromiseResolveBlock)resolve
38                    rejecter:(ABI47_0_0EXPromiseRejectBlock)reject)
39{
40  resolve(@([MFMessageComposeViewController canSendText]));
41}
42
43ABI47_0_0EX_EXPORT_METHOD_AS(sendSMSAsync,
44                    sendSMS:(NSArray<NSString *> *)addresses
45                    message:(NSString *)message
46                    options:(NSDictionary *)options
47                    resolver:(ABI47_0_0EXPromiseResolveBlock)resolve
48                    rejecter:(ABI47_0_0EXPromiseRejectBlock)reject)
49{
50  if (![MFMessageComposeViewController canSendText]) {
51    reject(@"E_SMS_UNAVAILABLE", @"SMS service not available", nil);
52    return;
53  }
54
55  if (_resolve != nil || _reject != nil) {
56    reject(@"E_SMS_SENDING_IN_PROGRESS", @"Different SMS sending in progress. Await the old request and then try again.", nil);
57    return;
58  }
59
60  _resolve = resolve;
61  _reject = reject;
62
63  MFMessageComposeViewController *messageComposeViewController = [[MFMessageComposeViewController alloc] init];
64  messageComposeViewController.messageComposeDelegate = self;
65  messageComposeViewController.recipients = addresses;
66  messageComposeViewController.body = message;
67
68  if (options) {
69    if (options[@"attachments"]) {
70      NSArray *attachments = (NSArray *) [options objectForKey:@"attachments"];
71      for (NSDictionary* attachment in attachments) {
72        NSString *mimeType = attachment[@"mimeType"];
73        CFStringRef mimeTypeRef = (__bridge CFStringRef)mimeType;
74        CFStringRef utiRef = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeTypeRef, NULL);
75        if (utiRef == NULL) {
76          reject(@"E_SMS_ATTACHMENT", [NSString stringWithFormat:@"Failed to find UTI for mimeType: %@", mimeType], nil);
77          _resolve = nil;
78          _reject = nil;
79          return;
80        }
81        NSString *typeIdentifier = (__bridge_transfer NSString *)utiRef;
82        NSString *uri = attachment[@"uri"];
83        NSString *filename = attachment[@"filename"];
84        NSError *error;
85        NSData *attachmentData = [NSData dataWithContentsOfURL:[NSURL URLWithString:uri] options:(NSDataReadingOptions)0 error:&error];
86        bool attached = [messageComposeViewController addAttachmentData:attachmentData typeIdentifier:typeIdentifier filename:filename];
87        if (!attached) {
88          reject(@"E_SMS_ATTACHMENT", [NSString stringWithFormat:@"Failed to attach file: %@", uri], nil);
89          _resolve = nil;
90          _reject = nil;
91          return;
92        }
93      }
94    }
95  }
96
97  [self.utils.currentViewController presentViewController:messageComposeViewController animated:YES completion:nil];
98}
99
100- (void)messageComposeViewController:(MFMessageComposeViewController *)controller
101                 didFinishWithResult:(MessageComposeResult)result
102{
103  NSDictionary *resolveData;
104  NSString *rejectMessage;
105  switch (result) {
106    case MessageComposeResultCancelled:
107      resolveData = @{@"result": @"cancelled"};
108      break;
109    case MessageComposeResultSent:
110      resolveData = @{@"result": @"sent"};
111      break;
112    case MessageComposeResultFailed:
113      rejectMessage = @"User's attempt to save or send an SMS was unsuccessful. This can occur when the device loses connection to Wifi or Cellular.";
114      break;
115    default:
116      rejectMessage = @"SMS message sending failed with unknown error";
117      break;
118  }
119  ABI47_0_0EX_WEAKIFY(self);
120  [controller dismissViewControllerAnimated:YES completion:^{
121    ABI47_0_0EX_ENSURE_STRONGIFY(self);
122    if (rejectMessage) {
123      self->_reject(@"E_SMS_SENDING_FAILED", rejectMessage, nil);
124    } else {
125      self->_resolve(resolveData);
126    }
127    self->_reject = nil;
128    self->_resolve = nil;
129  }];
130}
131
132@end
133