expo-sms 11.0.0 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 11.2.0 — 2023-02-03
14
+
15
+ ### 💡 Others
16
+
17
+ - On Android bump `compileSdkVersion` and `targetSdkVersion` to `33`. ([#20721](https://github.com/expo/expo/pull/20721) by [@lukmccall](https://github.com/lukmccall))
18
+
19
+ ## 11.1.0 — 2022-12-30
20
+
21
+ ### 🎉 New features
22
+
23
+ - Migrated to Expo Modules API. ([#19996](https://github.com/expo/expo/pull/19996) and ([#19967](https://github.com/expo/expo/pull/19967) by [@alanhughes](https://github.com/alanjhughes))
24
+
13
25
  ## 11.0.0 — 2022-10-25
14
26
 
15
27
  ### 🛠 Breaking changes
package/README.md CHANGED
@@ -4,7 +4,7 @@ Provides access to the system's UI/app for sending SMS messages.
4
4
 
5
5
  # API documentation
6
6
 
7
- - [Documentation for the main branch](https://github.com/expo/expo/blob/main/docs/pages/versions/unversioned/sdk/sms.md)
7
+ - [Documentation for the main branch](https://github.com/expo/expo/blob/main/docs/pages/versions/unversioned/sdk/sms.mdx)
8
8
  - [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/sms/)
9
9
 
10
10
  # Installation in managed Expo projects
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
3
3
  apply plugin: 'maven-publish'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '11.0.0'
6
+ version = '11.2.0'
7
7
 
8
8
  buildscript {
9
9
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
@@ -59,7 +59,7 @@ afterEvaluate {
59
59
  }
60
60
 
61
61
  android {
62
- compileSdkVersion safeExtGet("compileSdkVersion", 31)
62
+ compileSdkVersion safeExtGet("compileSdkVersion", 33)
63
63
 
64
64
  compileOptions {
65
65
  sourceCompatibility JavaVersion.VERSION_11
@@ -72,9 +72,9 @@ android {
72
72
 
73
73
  defaultConfig {
74
74
  minSdkVersion safeExtGet("minSdkVersion", 21)
75
- targetSdkVersion safeExtGet("targetSdkVersion", 31)
75
+ targetSdkVersion safeExtGet("targetSdkVersion", 33)
76
76
  versionCode 28
77
- versionName "11.0.0"
77
+ versionName "11.2.0"
78
78
  }
79
79
  lintOptions {
80
80
  abortOnError false
@@ -0,0 +1,9 @@
1
+ package expo.modules.sms
2
+
3
+ import expo.modules.kotlin.exception.CodedException
4
+
5
+ internal class MissingSMSAppException :
6
+ CodedException("No messaging application available")
7
+
8
+ internal class MissingCurrentActivityException :
9
+ CodedException("Activity which was provided during module initialization is no longer available")
@@ -2,75 +2,60 @@ package expo.modules.sms
2
2
 
3
3
  import android.content.Context
4
4
  import android.content.Intent
5
+ import android.content.pm.PackageManager
5
6
  import android.net.Uri
6
7
  import android.provider.Telephony
7
- import android.content.pm.PackageManager
8
- import android.os.Bundle
9
-
10
- import java.util.ArrayList
11
-
12
- import expo.modules.core.ExportedModule
8
+ import androidx.core.os.bundleOf
13
9
  import expo.modules.core.interfaces.LifecycleEventListener
14
- import expo.modules.core.ModuleRegistry
15
- import expo.modules.core.Promise
16
10
  import expo.modules.core.interfaces.services.UIManager
17
- import expo.modules.core.interfaces.ExpoMethod
18
- import expo.modules.core.interfaces.ActivityProvider
19
-
20
- private const val TAG = "ExpoSMS"
21
- private const val ERROR_TAG = "E_SMS"
22
- private const val OPTIONS_ATTACHMENTS_KEY = "attachments"
23
-
24
- class SMSModule(context: Context, private val smsPackage: String? = null) : ExportedModule(context), LifecycleEventListener {
25
- private lateinit var mModuleRegistry: ModuleRegistry
26
- private var mPendingPromise: Promise? = null
27
- private var mSMSComposerOpened = false
28
-
29
- override fun getName(): String {
30
- return TAG
31
- }
32
-
33
- override fun onCreate(moduleRegistry: ModuleRegistry) {
34
- mModuleRegistry = moduleRegistry
35
- mModuleRegistry.getModule(UIManager::class.java)?.registerLifecycleEventListener(this)
36
- }
37
-
38
- override fun onDestroy() {
39
- // Unregister from old UIManager
40
- mModuleRegistry.getModule(UIManager::class.java)?.unregisterLifecycleEventListener(this)
41
- }
11
+ import expo.modules.kotlin.Promise
12
+ import expo.modules.kotlin.exception.Exceptions
13
+ import expo.modules.kotlin.modules.Module
14
+ import expo.modules.kotlin.modules.ModuleDefinition
15
+
16
+ class SMSModule : Module(), LifecycleEventListener {
17
+ private var pendingPromise: Promise? = null
18
+ private var smsComposerOpened = false
19
+
20
+ private val context: Context
21
+ get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
22
+ private val currentActivity
23
+ get() = appContext.activityProvider?.currentActivity
24
+ ?: throw MissingCurrentActivityException()
25
+
26
+ override fun definition() = ModuleDefinition {
27
+ Name("ExpoSMS")
28
+
29
+ OnCreate {
30
+ val uiManager = appContext.legacyModule<UIManager>()
31
+ uiManager?.registerLifecycleEventListener(this@SMSModule)
32
+ }
42
33
 
43
- private fun getAttachment(attachment: Map<String?, String?>?, key: String): String? {
44
- return attachment?.get(key)
45
- }
34
+ AsyncFunction("sendSMSAsync") { addresses: List<String>, message: String, options: SMSOptions, promise: Promise ->
35
+ sendSMSAsync(addresses, message, options, promise)
36
+ }
46
37
 
47
- @ExpoMethod
48
- fun sendSMSAsync(
49
- addresses: ArrayList<String>,
50
- message: String,
51
- options: Map<String?, Any?>?,
52
- promise: Promise
53
- ) {
54
- if (mPendingPromise != null) {
55
- promise.reject(
56
- ERROR_TAG + "_SENDING_IN_PROGRESS",
57
- "Different SMS sending in progress. Await the old request and then try again."
58
- )
59
- return
38
+ AsyncFunction("isAvailableAsync") {
39
+ return@AsyncFunction context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
60
40
  }
61
41
 
62
- val attachments = options?.get(OPTIONS_ATTACHMENTS_KEY) as? List<*>
42
+ OnDestroy {
43
+ val uiManager = appContext.legacyModule<UIManager>()
44
+ uiManager?.unregisterLifecycleEventListener(this@SMSModule)
45
+ }
46
+ }
63
47
 
48
+ private fun sendSMSAsync(addresses: List<String>, message: String, options: SMSOptions, promise: Promise) {
64
49
  // ACTION_SEND causes a weird flicker on Android 10 devices if the messaging app is not already
65
50
  // open in the background, but it seems to be the only intent type that works for including
66
51
  // attachments, so we use it if there are attachments and fall back to ACTION_SENDTO otherwise.
67
- val smsIntent = if (attachments?.isNotEmpty() == true) {
52
+ val smsIntent = if (options.attachments.isNotEmpty()) {
68
53
  Intent(Intent.ACTION_SEND).apply {
69
54
  type = "text/plain"
70
55
  putExtra("address", addresses.joinToString(separator = ";"))
71
- val attachment = attachments[0] as? Map<String?, String?>
72
- putExtra(Intent.EXTRA_STREAM, Uri.parse(getAttachment(attachment, "uri")))
73
- type = getAttachment(attachment, "mimeType")
56
+ val attachment = options.attachments[0]
57
+ putExtra(Intent.EXTRA_STREAM, Uri.parse(attachment.uri))
58
+ type = attachment.mimeType
74
59
  addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
75
60
  }
76
61
  } else {
@@ -79,49 +64,34 @@ class SMSModule(context: Context, private val smsPackage: String? = null) : Expo
79
64
  }
80
65
  }
81
66
 
82
- val defaultSMSPackage: String?
83
- if (smsPackage != null) {
84
- defaultSMSPackage = smsPackage
85
- } else {
86
- defaultSMSPackage = Telephony.Sms.getDefaultSmsPackage(context)
87
- }
67
+ val defaultSMSPackage = Telephony.Sms.getDefaultSmsPackage(context)
68
+ defaultSMSPackage?.let {
69
+ smsIntent.setPackage(it)
70
+ } ?: throw MissingSMSAppException()
88
71
 
89
- if (defaultSMSPackage != null) {
90
- smsIntent.setPackage(defaultSMSPackage)
91
- } else {
92
- promise.reject(ERROR_TAG + "_NO_SMS_APP", "No messaging application available")
93
- return
72
+ smsIntent.apply {
73
+ putExtra("exit_on_sent", true)
74
+ putExtra("compose_mode", true)
75
+ putExtra("sms_body", message)
94
76
  }
95
- smsIntent.putExtra("exit_on_sent", true)
96
- smsIntent.putExtra("compose_mode", true)
97
- smsIntent.putExtra("sms_body", message)
98
- mPendingPromise = promise
99
- val activityProvider = mModuleRegistry.getModule(
100
- ActivityProvider::class.java
101
- )
102
- activityProvider.currentActivity.startActivity(smsIntent)
103
- mSMSComposerOpened = true
104
- }
105
77
 
106
- @ExpoMethod
107
- fun isAvailableAsync(promise: Promise) {
108
- promise.resolve(context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY))
78
+ pendingPromise = promise
79
+ currentActivity.startActivity(smsIntent)
80
+ smsComposerOpened = true
109
81
  }
110
82
 
111
83
  override fun onHostResume() {
112
- val promise = mPendingPromise
113
- if (mSMSComposerOpened && promise != null) {
84
+ val promise = pendingPromise
85
+ if (smsComposerOpened && promise != null) {
114
86
  // the only way to check the status of the message is to query the device's SMS database
115
87
  // but this requires READ_SMS permission, which Google is heavily restricting beginning Jan 2019
116
88
  // so we just resolve with an unknown value
117
89
  promise.resolve(
118
- Bundle().apply {
119
- putString("result", "unknown")
120
- }
90
+ bundleOf(Pair("result", "unknown"))
121
91
  )
122
- mPendingPromise = null
92
+ pendingPromise = null
123
93
  }
124
- mSMSComposerOpened = false
94
+ smsComposerOpened = false
125
95
  }
126
96
 
127
97
  override fun onHostPause() = Unit
@@ -0,0 +1,14 @@
1
+ package expo.modules.sms
2
+
3
+ import expo.modules.kotlin.records.Field
4
+ import expo.modules.kotlin.records.Record
5
+
6
+ data class SMSOptions(
7
+ @Field val attachments: List<SMSAttachment> = emptyList()
8
+ ) : Record
9
+
10
+ data class SMSAttachment(
11
+ @Field val uri: String,
12
+ @Field val mimeType: String,
13
+ @Field val filename: String,
14
+ ) : Record
@@ -1,3 +1,3 @@
1
- declare const _default: import("expo-modules-core").ProxyNativeModule;
1
+ declare const _default: any;
2
2
  export default _default;
3
3
  //# sourceMappingURL=ExpoSMS.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoSMS.d.ts","sourceRoot":"","sources":["../src/ExpoSMS.ts"],"names":[],"mappings":";AACA,wBAA0C"}
1
+ {"version":3,"file":"ExpoSMS.d.ts","sourceRoot":"","sources":["../src/ExpoSMS.ts"],"names":[],"mappings":";AACA,wBAA8C"}
package/build/ExpoSMS.js CHANGED
@@ -1,3 +1,3 @@
1
- import { NativeModulesProxy } from 'expo-modules-core';
2
- export default NativeModulesProxy.ExpoSMS;
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ export default requireNativeModule('ExpoSMS');
3
3
  //# sourceMappingURL=ExpoSMS.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoSMS.js","sourceRoot":"","sources":["../src/ExpoSMS.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,eAAe,kBAAkB,CAAC,OAAO,CAAC","sourcesContent":["import { NativeModulesProxy } from 'expo-modules-core';\nexport default NativeModulesProxy.ExpoSMS;\n"]}
1
+ {"version":3,"file":"ExpoSMS.js","sourceRoot":"","sources":["../src/ExpoSMS.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,eAAe,mBAAmB,CAAC,SAAS,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\nexport default requireNativeModule('ExpoSMS');\n"]}
package/build/SMS.d.ts CHANGED
@@ -14,7 +14,7 @@ export { SMSAttachment, SMSResponse, SMSOptions };
14
14
  * - If the status of the SMS message cannot be determined: `{ result: 'unknown' }`.
15
15
  *
16
16
  * Android does not provide information about the status of the SMS message, so on Android devices
17
- * the Promise will always resolve with { result: 'unknown' }.
17
+ * the Promise will always resolve with `{ result: 'unknown' }`.
18
18
  *
19
19
  * > Note: The only feedback collected by this module is whether any message has been sent. That
20
20
  * means we do not check actual content of message nor recipients list.
package/build/SMS.js CHANGED
@@ -29,7 +29,7 @@ function processAttachments(attachments) {
29
29
  * - If the status of the SMS message cannot be determined: `{ result: 'unknown' }`.
30
30
  *
31
31
  * Android does not provide information about the status of the SMS message, so on Android devices
32
- * the Promise will always resolve with { result: 'unknown' }.
32
+ * the Promise will always resolve with `{ result: 'unknown' }`.
33
33
  *
34
34
  * > Note: The only feedback collected by this module is whether any message has been sent. That
35
35
  * means we do not check actual content of message nor recipients list.
package/build/SMS.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"SMS.js","sourceRoot":"","sources":["../src/SMS.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,OAAO,MAAM,WAAW,CAAC;AAKhC,SAAS,kBAAkB,CACzB,WAAwD;IAExD,IAAI,CAAC,WAAW,EAAE;QAChB,OAAO,IAAI,CAAC;KACb;IACD,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACvE,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE;QACvD,IAAI,OAAO,EAAE;YACX,OAAO,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;SAC1F;QACD,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;KACvC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,cAAc;AACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,SAA4B,EAC5B,OAAe,EACf,OAAoB;IAEpB,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE;QACzB,MAAM,IAAI,mBAAmB,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;KAC3D;IACD,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC1E,cAAc,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QACjC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,EAAE;YAC7C,MAAM,IAAI,SAAS,CAAC,2BAA2B,CAAC,CAAC;SAClD;IACH,CAAC,CAAC,CAAC;IACH,MAAM,YAAY,GAAG;QACnB,GAAG,OAAO;KACG,CAAC;IAChB,IAAI,OAAO,EAAE,WAAW,EAAE;QACxB,YAAY,CAAC,WAAW,GAAG,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,SAAS,CAAC;KAClF;IACD,OAAO,OAAO,CAAC,YAAY,CAAC,cAAc,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;AACrE,CAAC;AAED,cAAc;AACd;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,OAAO,OAAO,CAAC,gBAAgB,EAAE,CAAC;AACpC,CAAC","sourcesContent":["/* eslint-disable no-unused-expressions */\nimport { UnavailabilityError, Platform } from 'expo-modules-core';\n\nimport ExpoSMS from './ExpoSMS';\nimport { SMSAttachment, SMSResponse, SMSOptions } from './SMS.types';\n\nexport { SMSAttachment, SMSResponse, SMSOptions };\n\nfunction processAttachments(\n attachments: SMSAttachment | SMSAttachment[] | undefined\n): SMSAttachment[] | null {\n if (!attachments) {\n return null;\n }\n attachments = Array.isArray(attachments) ? attachments : [attachments];\n if (Platform.OS === 'android' && attachments.length > 1) {\n if (__DEV__) {\n console.warn('Android only supports a single attachment. The first array item is used.');\n }\n attachments = attachments.slice(0, 1);\n }\n return attachments;\n}\n\n// @needsAudit\n/**\n * Opens the default UI/app for sending SMS messages with prefilled addresses and message.\n *\n * @param addresses An array of addresses (phone numbers) or single address passed as strings. Those\n * would appear as recipients of the prepared message.\n * @param message Message to be sent.\n * @param options A `SMSOptions` object defining additional SMS configuration options.\n *\n * @return Returns a Promise that fulfils with the SMS action is invoked by the user, with corresponding result:\n * - If the user cancelled the SMS sending process: `{ result: 'cancelled' }`.\n * - If the user has sent/scheduled message for sending: `{ result: 'sent' }`.\n * - If the status of the SMS message cannot be determined: `{ result: 'unknown' }`.\n *\n * Android does not provide information about the status of the SMS message, so on Android devices\n * the Promise will always resolve with { result: 'unknown' }.\n *\n * > Note: The only feedback collected by this module is whether any message has been sent. That\n * means we do not check actual content of message nor recipients list.\n *\n * @example\n * ```ts\n * const { result } = await SMS.sendSMSAsync(\n * ['0123456789', '9876543210'],\n * 'My sample HelloWorld message',\n * {\n * attachments: {\n * uri: 'path/myfile.png',\n * mimeType: 'image/png',\n * filename: 'myfile.png',\n * },\n * }\n * );\n * ```\n */\nexport async function sendSMSAsync(\n addresses: string | string[],\n message: string,\n options?: SMSOptions\n): Promise<SMSResponse> {\n if (!ExpoSMS.sendSMSAsync) {\n throw new UnavailabilityError('expo-sms', 'sendSMSAsync');\n }\n const finalAddresses = Array.isArray(addresses) ? addresses : [addresses];\n finalAddresses.forEach((address) => {\n if (address === null || address === undefined) {\n throw new TypeError('undefined or null address');\n }\n });\n const finalOptions = {\n ...options,\n } as SMSOptions;\n if (options?.attachments) {\n finalOptions.attachments = processAttachments(options?.attachments) || undefined;\n }\n return ExpoSMS.sendSMSAsync(finalAddresses, message, finalOptions);\n}\n\n// @needsAudit\n/**\n * Determines whether SMS is available. Always returns `false` in the iOS simulator, and in browser.\n *\n * @return Returns a promise that fulfils with a `boolean`, indicating whether SMS is available on this device.\n *\n * @example\n * ```ts\n * const isAvailable = await SMS.isAvailableAsync();\n * if (isAvailable) {\n * // do your SMS stuff here\n * } else {\n * // misfortune... there's no SMS available on this device\n * }\n * ```\n */\nexport async function isAvailableAsync(): Promise<boolean> {\n return ExpoSMS.isAvailableAsync();\n}\n"]}
1
+ {"version":3,"file":"SMS.js","sourceRoot":"","sources":["../src/SMS.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,OAAO,MAAM,WAAW,CAAC;AAKhC,SAAS,kBAAkB,CACzB,WAAwD;IAExD,IAAI,CAAC,WAAW,EAAE;QAChB,OAAO,IAAI,CAAC;KACb;IACD,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACvE,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE;QACvD,IAAI,OAAO,EAAE;YACX,OAAO,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;SAC1F;QACD,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;KACvC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,cAAc;AACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,SAA4B,EAC5B,OAAe,EACf,OAAoB;IAEpB,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE;QACzB,MAAM,IAAI,mBAAmB,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;KAC3D;IACD,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC1E,cAAc,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QACjC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,EAAE;YAC7C,MAAM,IAAI,SAAS,CAAC,2BAA2B,CAAC,CAAC;SAClD;IACH,CAAC,CAAC,CAAC;IACH,MAAM,YAAY,GAAG;QACnB,GAAG,OAAO;KACG,CAAC;IAChB,IAAI,OAAO,EAAE,WAAW,EAAE;QACxB,YAAY,CAAC,WAAW,GAAG,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,SAAS,CAAC;KAClF;IACD,OAAO,OAAO,CAAC,YAAY,CAAC,cAAc,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;AACrE,CAAC;AAED,cAAc;AACd;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,OAAO,OAAO,CAAC,gBAAgB,EAAE,CAAC;AACpC,CAAC","sourcesContent":["/* eslint-disable no-unused-expressions */\nimport { UnavailabilityError, Platform } from 'expo-modules-core';\n\nimport ExpoSMS from './ExpoSMS';\nimport { SMSAttachment, SMSResponse, SMSOptions } from './SMS.types';\n\nexport { SMSAttachment, SMSResponse, SMSOptions };\n\nfunction processAttachments(\n attachments: SMSAttachment | SMSAttachment[] | undefined\n): SMSAttachment[] | null {\n if (!attachments) {\n return null;\n }\n attachments = Array.isArray(attachments) ? attachments : [attachments];\n if (Platform.OS === 'android' && attachments.length > 1) {\n if (__DEV__) {\n console.warn('Android only supports a single attachment. The first array item is used.');\n }\n attachments = attachments.slice(0, 1);\n }\n return attachments;\n}\n\n// @needsAudit\n/**\n * Opens the default UI/app for sending SMS messages with prefilled addresses and message.\n *\n * @param addresses An array of addresses (phone numbers) or single address passed as strings. Those\n * would appear as recipients of the prepared message.\n * @param message Message to be sent.\n * @param options A `SMSOptions` object defining additional SMS configuration options.\n *\n * @return Returns a Promise that fulfils with the SMS action is invoked by the user, with corresponding result:\n * - If the user cancelled the SMS sending process: `{ result: 'cancelled' }`.\n * - If the user has sent/scheduled message for sending: `{ result: 'sent' }`.\n * - If the status of the SMS message cannot be determined: `{ result: 'unknown' }`.\n *\n * Android does not provide information about the status of the SMS message, so on Android devices\n * the Promise will always resolve with `{ result: 'unknown' }`.\n *\n * > Note: The only feedback collected by this module is whether any message has been sent. That\n * means we do not check actual content of message nor recipients list.\n *\n * @example\n * ```ts\n * const { result } = await SMS.sendSMSAsync(\n * ['0123456789', '9876543210'],\n * 'My sample HelloWorld message',\n * {\n * attachments: {\n * uri: 'path/myfile.png',\n * mimeType: 'image/png',\n * filename: 'myfile.png',\n * },\n * }\n * );\n * ```\n */\nexport async function sendSMSAsync(\n addresses: string | string[],\n message: string,\n options?: SMSOptions\n): Promise<SMSResponse> {\n if (!ExpoSMS.sendSMSAsync) {\n throw new UnavailabilityError('expo-sms', 'sendSMSAsync');\n }\n const finalAddresses = Array.isArray(addresses) ? addresses : [addresses];\n finalAddresses.forEach((address) => {\n if (address === null || address === undefined) {\n throw new TypeError('undefined or null address');\n }\n });\n const finalOptions = {\n ...options,\n } as SMSOptions;\n if (options?.attachments) {\n finalOptions.attachments = processAttachments(options?.attachments) || undefined;\n }\n return ExpoSMS.sendSMSAsync(finalAddresses, message, finalOptions);\n}\n\n// @needsAudit\n/**\n * Determines whether SMS is available. Always returns `false` in the iOS simulator, and in browser.\n *\n * @return Returns a promise that fulfils with a `boolean`, indicating whether SMS is available on this device.\n *\n * @example\n * ```ts\n * const isAvailable = await SMS.isAvailableAsync();\n * if (isAvailable) {\n * // do your SMS stuff here\n * } else {\n * // misfortune... there's no SMS available on this device\n * }\n * ```\n */\nexport async function isAvailableAsync(): Promise<boolean> {\n return ExpoSMS.isAvailableAsync();\n}\n"]}
@@ -1,4 +1,4 @@
1
- export declare type SMSResponse = {
1
+ export type SMSResponse = {
2
2
  /**
3
3
  * Status of SMS action invoked by the user.
4
4
  */
@@ -7,7 +7,7 @@ export declare type SMSResponse = {
7
7
  /**
8
8
  * An object that is used to describe an attachment that is included with a SMS message.
9
9
  */
10
- export declare type SMSAttachment = {
10
+ export type SMSAttachment = {
11
11
  /**
12
12
  * The content URI of the attachment. The URI needs be a content URI so that it can be accessed by
13
13
  * other applications outside of Expo. See [FileSystem.getContentUriAsync](./filesystem/#filesystemgetcontenturiasyncfileuri)).
@@ -22,7 +22,7 @@ export declare type SMSAttachment = {
22
22
  */
23
23
  filename: string;
24
24
  };
25
- export declare type SMSOptions = {
25
+ export type SMSOptions = {
26
26
  attachments?: SMSAttachment | SMSAttachment[] | undefined;
27
27
  };
28
28
  //# sourceMappingURL=SMS.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SMS.types.d.ts","sourceRoot":"","sources":["../src/SMS.types.ts"],"names":[],"mappings":"AACA,oBAAY,WAAW,GAAG;IACxB;;OAEG;IACH,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC;CAC1C,CAAC;AAGF;;GAEG;AACH,oBAAY,aAAa,GAAG;IAC1B;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAGF,oBAAY,UAAU,GAAG;IACvB,WAAW,CAAC,EAAE,aAAa,GAAG,aAAa,EAAE,GAAG,SAAS,CAAC;CAC3D,CAAC"}
1
+ {"version":3,"file":"SMS.types.d.ts","sourceRoot":"","sources":["../src/SMS.types.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,WAAW,GAAG;IACxB;;OAEG;IACH,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC;CAC1C,CAAC;AAGF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAGF,MAAM,MAAM,UAAU,GAAG;IACvB,WAAW,CAAC,EAAE,aAAa,GAAG,aAAa,EAAE,GAAG,SAAS,CAAC;CAC3D,CAAC"}
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "expo-sms",
3
+ "platforms": ["ios", "android"],
4
+ "ios": {
5
+ "modules": ["ExpoSMSModule"]
6
+ },
7
+ "android": {
8
+ "modules": ["expo.modules.sms.SMSModule"]
9
+ }
10
+ }
@@ -3,7 +3,7 @@ require 'json'
3
3
  package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
4
 
5
5
  Pod::Spec.new do |s|
6
- s.name = 'EXSMS'
6
+ s.name = 'ExpoSMS'
7
7
  s.version = package['version']
8
8
  s.summary = package['description']
9
9
  s.description = package['description']
@@ -11,15 +11,16 @@ Pod::Spec.new do |s|
11
11
  s.author = package['author']
12
12
  s.homepage = package['homepage']
13
13
  s.platform = :ios, '13.0'
14
+ s.swift_version = '5.4'
14
15
  s.source = { git: 'https://github.com/expo/expo.git' }
15
16
  s.static_framework = true
16
17
 
17
18
  s.dependency 'ExpoModulesCore'
18
19
 
19
20
  if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
20
- s.source_files = "#{s.name}/**/*.h"
21
+ s.source_files = "**/*.h"
21
22
  s.vendored_frameworks = "#{s.name}.xcframework"
22
23
  else
23
- s.source_files = "#{s.name}/**/*.{h,m}"
24
+ s.source_files = "**/*.{h,m,swift}"
24
25
  end
25
26
  end
@@ -0,0 +1,93 @@
1
+ import ExpoModulesCore
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
+
16
+ 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
+
28
+ 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
+
76
+ 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
+
85
+ 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
+ }
@@ -0,0 +1,37 @@
1
+ import MessageUI
2
+
3
+ protocol SMSResultHandler {
4
+ func onSuccess(_ data: [String: String])
5
+ func onFailure(_ error: String)
6
+ }
7
+
8
+ class SMSDelegate: NSObject, MFMessageComposeViewControllerDelegate {
9
+ private let handler: SMSResultHandler
10
+
11
+ init(handler: SMSResultHandler) {
12
+ self.handler = handler
13
+ }
14
+
15
+ func messageComposeViewController(
16
+ _ controller: MFMessageComposeViewController,
17
+ didFinishWith result: MessageComposeResult
18
+ ) {
19
+ controller.dismiss(animated: true) {
20
+ switch result {
21
+ case .sent, .cancelled:
22
+ self.handler.onSuccess([
23
+ "result": result == .sent ? "sent" : "cancelled"
24
+ ])
25
+ case .failed:
26
+ self.handler.onFailure(
27
+ """
28
+ User's attempt to save or send an SMS was unsuccessful.
29
+ This can occur when the device loses connection to WiFi or Cellular
30
+ """
31
+ )
32
+ default:
33
+ self.handler.onFailure("SMS message sending failed with unknown error")
34
+ }
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,37 @@
1
+ import ExpoModulesCore
2
+
3
+ internal class SMSUnavailableException: Exception {
4
+ override var reason: String {
5
+ "SMS service is not available"
6
+ }
7
+ }
8
+
9
+ internal class SMSPendingException: Exception {
10
+ override var reason: String {
11
+ "SMS sending in progress, await the old request and then try again"
12
+ }
13
+ }
14
+
15
+ internal class SMSSendingException: GenericException<String> {
16
+ override var reason: String {
17
+ param
18
+ }
19
+ }
20
+
21
+ internal class SMSFileException: GenericException<String> {
22
+ override var reason: String {
23
+ "Failed to attach file: \(param)"
24
+ }
25
+ }
26
+
27
+ internal class SMSMimeTypeException: GenericException<String> {
28
+ override var reason: String {
29
+ "Failed to find UTI for mimeType: \(param)"
30
+ }
31
+ }
32
+
33
+ internal class SMSUriException: GenericException<String> {
34
+ override var reason: String {
35
+ "Invalid file uri: \(param)"
36
+ }
37
+ }
@@ -0,0 +1,11 @@
1
+ import ExpoModulesCore
2
+
3
+ struct SMSOptions: Record {
4
+ @Field var attachments: [SMSAttachment]
5
+ }
6
+
7
+ struct SMSAttachment: Record {
8
+ @Field var uri: String
9
+ @Field var mimeType: String
10
+ @Field var filename: String
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-sms",
3
- "version": "11.0.0",
3
+ "version": "11.2.0",
4
4
  "description": "Provides access to the system's UI/app for sending SMS messages.",
5
5
  "main": "build/SMS.js",
6
6
  "types": "build/SMS.d.ts",
@@ -40,5 +40,5 @@
40
40
  "peerDependencies": {
41
41
  "expo": "*"
42
42
  },
43
- "gitHead": "eab2b09c735fb0fc2bf734a3f29a6593adba3838"
43
+ "gitHead": "1815e2eaad8c753588c7b1eb74420174a28e01f4"
44
44
  }
package/src/ExpoSMS.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { NativeModulesProxy } from 'expo-modules-core';
2
- export default NativeModulesProxy.ExpoSMS;
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ export default requireNativeModule('ExpoSMS');
package/src/SMS.ts CHANGED
@@ -37,7 +37,7 @@ function processAttachments(
37
37
  * - If the status of the SMS message cannot be determined: `{ result: 'unknown' }`.
38
38
  *
39
39
  * Android does not provide information about the status of the SMS message, so on Android devices
40
- * the Promise will always resolve with { result: 'unknown' }.
40
+ * the Promise will always resolve with `{ result: 'unknown' }`.
41
41
  *
42
42
  * > Note: The only feedback collected by this module is whether any message has been sent. That
43
43
  * means we do not check actual content of message nor recipients list.
@@ -1,11 +0,0 @@
1
- package expo.modules.sms
2
-
3
- import android.content.Context
4
- import expo.modules.core.BasePackage
5
- import expo.modules.core.ExportedModule
6
-
7
- class SMSPackage : BasePackage() {
8
- override fun createExportedModules(reactContext: Context): List<ExportedModule> {
9
- return listOf(SMSModule(reactContext))
10
- }
11
- }
@@ -1,7 +0,0 @@
1
- // Copyright © 2018 650 Industries. All rights reserved.
2
-
3
- #import <ExpoModulesCore/EXExportedModule.h>
4
- #import <ExpoModulesCore/EXModuleRegistryConsumer.h>
5
-
6
- @interface EXSMSModule : EXExportedModule <EXModuleRegistryConsumer>
7
- @end
@@ -1,132 +0,0 @@
1
- // Copyright © 2018 650 Industries. All rights reserved.
2
-
3
- #import <MessageUI/MessageUI.h>
4
- #import <EXSMS/EXSMSModule.h>
5
- #import <ExpoModulesCore/EXUtilities.h>
6
- #if SD_MAC
7
- #import <CoreServices/CoreServices.h>
8
- #else
9
- #import <MobileCoreServices/MobileCoreServices.h>
10
- #endif
11
-
12
- @interface EXSMSModule () <MFMessageComposeViewControllerDelegate>
13
-
14
- @property (nonatomic, weak) id<EXUtilitiesInterface> utils;
15
- @property (nonatomic, strong) EXPromiseResolveBlock resolve;
16
- @property (nonatomic, strong) EXPromiseRejectBlock reject;
17
-
18
- @end
19
-
20
- @implementation EXSMSModule
21
-
22
- EX_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:(EXModuleRegistry *)moduleRegistry
32
- {
33
- _utils = [moduleRegistry getModuleImplementingProtocol:@protocol(EXUtilitiesInterface)];
34
- }
35
-
36
- EX_EXPORT_METHOD_AS(isAvailableAsync,
37
- isAvailable:(EXPromiseResolveBlock)resolve
38
- rejecter:(EXPromiseRejectBlock)reject)
39
- {
40
- resolve(@([MFMessageComposeViewController canSendText]));
41
- }
42
-
43
- EX_EXPORT_METHOD_AS(sendSMSAsync,
44
- sendSMS:(NSArray<NSString *> *)addresses
45
- message:(NSString *)message
46
- options:(NSDictionary *)options
47
- resolver:(EXPromiseResolveBlock)resolve
48
- rejecter:(EXPromiseRejectBlock)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
- EX_WEAKIFY(self);
120
- [controller dismissViewControllerAnimated:YES completion:^{
121
- EX_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
package/unimodule.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "name": "expo-sms",
3
- "platforms": ["ios", "android"]
4
- }