1 import ABI49_0_0ExpoModulesCore
2 import MessageUI
3 import CoreServices
4 import MobileCoreServices
5 import UniformTypeIdentifiers
6 
7 struct ExpoSMSContext {
8   let promise: Promise
9   let smsDelegate: SMSDelegate
10 }
11 
12 public class ExpoSMSModule: Module, SMSResultHandler {
13   private var smsContext: ExpoSMSContext?
14   private lazy var utils = appContext?.utilities
15 
definitionnull16   public func definition() -> ModuleDefinition {
17     Name("ExpoSMS")
18 
19     AsyncFunction("isAvailableAsync") {
20       return MFMessageComposeViewController.canSendText()
21     }
22 
23     AsyncFunction("sendSMSAsync") { (addresses: [String], message: String, options: SMSOptions, promise: Promise) in
24       try sendSMSAsync(addresses: addresses, message: message, options: options, promise: promise)
25     }.runOnQueue(.main)
26   }
27 
sendSMSAsyncnull28   private func sendSMSAsync(addresses: [String], message: String, options: SMSOptions, promise: Promise) throws {
29     if !MFMessageComposeViewController.canSendText() {
30       throw SMSUnavailableException()
31     }
32 
33     if smsContext != nil {
34       throw SMSPendingException()
35     }
36 
37     let smsDelegate = SMSDelegate(handler: self)
38     let context = ExpoSMSContext(promise: promise, smsDelegate: smsDelegate)
39 
40     let messageComposeViewController = MFMessageComposeViewController()
41     messageComposeViewController.messageComposeDelegate = context.smsDelegate
42     messageComposeViewController.recipients = addresses
43     messageComposeViewController.body = message
44 
45     for attachment in options.attachments {
46       let utiRef = UTTypeCreatePreferredIdentifierForTag(
47         kUTTagClassMIMEType, attachment.mimeType as CFString, nil)
48 
49       if utiRef == nil {
50         throw SMSMimeTypeException(attachment.mimeType)
51       }
52 
53       guard let url = URL(string: attachment.uri) else {
54         throw SMSUriException(attachment.uri)
55       }
56 
57       do {
58         let data = try Data(contentsOf: url, options: .mappedIfSafe)
59         let attached = messageComposeViewController.addAttachmentData(
60           data,
61           typeIdentifier: attachment.mimeType,
62           filename: attachment.filename)
63         if !attached {
64           throw SMSFileException(attachment.uri)
65         }
66       } catch {
67         context.promise.reject(error)
68         return
69       }
70     }
71 
72     smsContext = context
73     utils?.currentViewController()?.present(messageComposeViewController, animated: true, completion: nil)
74   }
75 
onSuccessnull76   func onSuccess(_ data: [String: String]) {
77     guard let promise = smsContext?.promise else {
78       log.error("SMS context has been lost")
79       return
80     }
81     smsContext = nil
82     promise.resolve(data)
83   }
84 
onFailurenull85   func onFailure(_ error: String) {
86     guard let promise = smsContext?.promise else {
87       log.error("SMS context has been lost")
88       return
89     }
90     smsContext = nil
91     promise.reject(SMSSendingException(error))
92   }
93 }
94