react-native-davoice-tts 1.0.307 → 1.0.309

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.
Files changed (32) hide show
  1. package/TTSRNBridge.podspec +1 -1
  2. package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar +0 -0
  3. package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar.md5 +1 -1
  4. package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar.sha1 +1 -1
  5. package/android/src/main/java/com/davoice/stt/rn/STTModule.kt +27 -0
  6. package/android/src/main/java/com/davoice/tts/rn/DaVoiceTTSBridge.java +27 -0
  7. package/ios/STTRNBridge/STTBridge.m +27 -0
  8. package/ios/SpeechBridge/SpeechBridge.m +135 -0
  9. package/ios/TTSRNBridge/DaVoiceTTSBridge.m +30 -0
  10. package/ios/TTSRNBridge/DavoiceTTS.xcframework/Info.plist +5 -5
  11. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/DavoiceTTS +0 -0
  12. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Headers/DavoiceTTS-Swift.h +10 -1
  13. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.abi.json +8652 -8215
  14. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.private.swiftinterface +45 -36
  15. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.swiftinterface +45 -36
  16. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/DavoiceTTS +0 -0
  17. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Headers/DavoiceTTS-Swift.h +20 -2
  18. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.abi.json +8456 -8019
  19. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +80 -71
  20. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftinterface +80 -71
  21. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.abi.json +8456 -8019
  22. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +80 -71
  23. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +80 -71
  24. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeDirectory +0 -0
  25. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeRequirements-1 +0 -0
  26. package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeResources +27 -42
  27. package/package.json +1 -1
  28. package/speech/index.ts +53 -0
  29. package/stt/index.d.ts +2 -0
  30. package/stt/index.ts +20 -0
  31. package/tts/DaVoiceTTSBridge.d.ts +2 -0
  32. package/tts/DaVoiceTTSBridge.js +8 -0
@@ -2,7 +2,7 @@ require 'json'
2
2
 
3
3
  Pod::Spec.new do |s|
4
4
  s.name = "TTSRNBridge"
5
- s.version = "1.0.180" # Update to your package version
5
+ s.version = "1.0.182" # Update to your package version
6
6
  s.summary = "TTS for React Native."
7
7
  s.description = <<-DESC
8
8
  A React Native module for tts .
@@ -1 +1 @@
1
- 9fa81740346cc323634537859a7a2cb6 tts-1.0.0.aar
1
+ dd2b50d07ff256989600f071c66d4227 tts-1.0.0.aar
@@ -1 +1 @@
1
- e088a037b4b602225ba1029a317b9b74e1f6fa35 tts-1.0.0.aar
1
+ f351faff177e5d8cba9faacd5bf97da0accb10b0 tts-1.0.0.aar
@@ -5,6 +5,7 @@ import com.facebook.react.module.annotations.ReactModule
5
5
  import com.facebook.react.modules.core.DeviceEventManagerModule
6
6
  import com.davoice.stt.STT
7
7
  import com.davoice.stt.STTDelegate
8
+ import com.davoice.tts.LicenseManager
8
9
 
9
10
  @ReactModule(name = STTModule.NAME)
10
11
  class STTModule(private val rc: ReactApplicationContext)
@@ -36,6 +37,32 @@ class STTModule(private val rc: ReactApplicationContext)
36
37
 
37
38
  // ===== JS API =====
38
39
 
40
+ @ReactMethod
41
+ fun setLicense(licenseKey: String?, promise: Promise) {
42
+ try {
43
+ if (licenseKey.isNullOrBlank()) {
44
+ promise.reject("invalid_args", "Missing licenseKey")
45
+ return
46
+ }
47
+ promise.resolve(ensure().setLicenseKey(licenseKey))
48
+ } catch (t: Throwable) {
49
+ promise.reject("LicenseError", t.message, t)
50
+ }
51
+ }
52
+
53
+ @ReactMethod
54
+ fun isLicenseValid(licenseKey: String?, promise: Promise) {
55
+ try {
56
+ if (licenseKey.isNullOrBlank()) {
57
+ promise.reject("invalid_args", "Missing licenseKey")
58
+ return
59
+ }
60
+ promise.resolve(LicenseManager.isLicenseValid(licenseKey))
61
+ } catch (t: Throwable) {
62
+ promise.reject("LicenseError", t.message, t)
63
+ }
64
+ }
65
+
39
66
  // ANDROID expects (locale, options, callback) like @react-native-voice
40
67
  @ReactMethod
41
68
  fun startSpeech(locale: String?, options: ReadableMap, cb: Callback) {
@@ -27,6 +27,7 @@ import java.net.URL;
27
27
  import java.net.URLConnection;
28
28
 
29
29
  import com.davoice.tts.DaVoiceTTSInterface;
30
+ import com.davoice.tts.LicenseManager;
30
31
 
31
32
 
32
33
  public class DaVoiceTTSBridge extends ReactContextBaseJavaModule {
@@ -52,6 +53,32 @@ public class DaVoiceTTSBridge extends ReactContextBaseJavaModule {
52
53
  );
53
54
  }
54
55
 
56
+ @ReactMethod
57
+ public void setLicense(String licenseKey, Promise promise) {
58
+ try {
59
+ if (licenseKey == null || licenseKey.trim().isEmpty()) {
60
+ promise.reject("invalid_args", "Missing licenseKey");
61
+ return;
62
+ }
63
+ promise.resolve(tts.setLicenseKey(licenseKey));
64
+ } catch (Exception e) {
65
+ promise.reject("LicenseError", e.getMessage(), e);
66
+ }
67
+ }
68
+
69
+ @ReactMethod
70
+ public void isLicenseValid(String licenseKey, Promise promise) {
71
+ try {
72
+ if (licenseKey == null || licenseKey.trim().isEmpty()) {
73
+ promise.reject("invalid_args", "Missing licenseKey");
74
+ return;
75
+ }
76
+ promise.resolve(LicenseManager.isLicenseValid(licenseKey));
77
+ } catch (Exception e) {
78
+ promise.reject("LicenseError", e.getMessage(), e);
79
+ }
80
+ }
81
+
55
82
  @ReactMethod
56
83
  public void initTTS(ReadableMap config, Promise promise) {
57
84
  // DEBUG CODE
@@ -53,6 +53,33 @@ RCT_EXPORT_MODULE(STT)
53
53
  }
54
54
 
55
55
  #pragma mark - API
56
+ RCT_EXPORT_METHOD(setLicense:(NSString *)licenseKey
57
+ resolver:(RCTPromiseResolveBlock)resolve
58
+ rejecter:(RCTPromiseRejectBlock)reject)
59
+ {
60
+ if (licenseKey == nil || (id)licenseKey == [NSNull null] || licenseKey.length == 0) {
61
+ reject(@"invalid_args", @"Missing licenseKey", nil);
62
+ return;
63
+ }
64
+
65
+ [self ensureSTT];
66
+ BOOL ok = [self.stt setLicenseWithLicenseKey:licenseKey];
67
+ resolve(@(ok));
68
+ }
69
+
70
+ RCT_EXPORT_METHOD(isLicenseValid:(NSString *)licenseKey
71
+ resolver:(RCTPromiseResolveBlock)resolve
72
+ rejecter:(RCTPromiseRejectBlock)reject)
73
+ {
74
+ if (licenseKey == nil || (id)licenseKey == [NSNull null] || licenseKey.length == 0) {
75
+ reject(@"invalid_args", @"Missing licenseKey", nil);
76
+ return;
77
+ }
78
+
79
+ BOOL ok = [LicenseManager isLicenseValidWithLicenseKey:licenseKey];
80
+ resolve(@(ok));
81
+ }
82
+
56
83
  RCT_EXPORT_METHOD(startSpeech:(NSString *)locale
57
84
  callback:(RCTResponseSenderBlock)callback)
58
85
  {
@@ -8,12 +8,23 @@
8
8
  #import <DaVoiceTTS/DaVoiceTTS-Swift.h> // DaVoiceTTS + STT live here in your setup
9
9
 
10
10
  #import <AVFAudio/AVFAudio.h>
11
+ #import <Speech/Speech.h>
11
12
 
12
13
  static NSData *SB_Base64Decode(NSString *b64) {
13
14
  if (!b64 || (id)b64 == [NSNull null]) return nil;
14
15
  return [[NSData alloc] initWithBase64EncodedString:b64 options:0];
15
16
  }
16
17
 
18
+ static BOOL SBHasMicPermission(void) {
19
+ AVAudioSessionRecordPermission permission = [[AVAudioSession sharedInstance] recordPermission];
20
+ return permission == AVAudioSessionRecordPermissionGranted;
21
+ }
22
+
23
+ static BOOL SBHasSpeechRecognitionPermission(void) {
24
+ SFSpeechRecognizerAuthorizationStatus status = [SFSpeechRecognizer authorizationStatus];
25
+ return status == SFSpeechRecognizerAuthorizationStatusAuthorized;
26
+ }
27
+
17
28
  // Make a mono Float32 AVAudioPCMBuffer from raw PCM payload (i16 or f32).
18
29
  // We accept either interleaved or non-interleaved input and mixdown to mono
19
30
  // (DaVoiceTTS.playBuffer will resample / normalize as needed).
@@ -236,8 +247,132 @@ RCT_EXPORT_MODULE(SpeechBridge)
236
247
  };
237
248
  }
238
249
 
250
+ RCT_EXPORT_METHOD(hasMicPermissions:(RCTPromiseResolveBlock)resolve
251
+ rejecter:(RCTPromiseRejectBlock)reject)
252
+ {
253
+ resolve(@(SBHasMicPermission()));
254
+ }
255
+
256
+ RCT_EXPORT_METHOD(requestMicPermissions:(nonnull NSNumber *)wait_timeout
257
+ resolver:(RCTPromiseResolveBlock)resolve
258
+ rejecter:(RCTPromiseRejectBlock)reject)
259
+ {
260
+ dispatch_async(dispatch_get_main_queue(), ^{
261
+ AVAudioSession *audioSession = [AVAudioSession sharedInstance];
262
+ AVAudioSessionRecordPermission permission = audioSession.recordPermission;
263
+ if (permission == AVAudioSessionRecordPermissionGranted) {
264
+ resolve(@YES);
265
+ return;
266
+ }
267
+
268
+ if (permission == AVAudioSessionRecordPermissionDenied) {
269
+ resolve(@NO);
270
+ return;
271
+ }
272
+
273
+ NSTimeInterval timeoutSeconds = MAX(wait_timeout.doubleValue, 0.0) / 1000.0;
274
+ __block BOOL didResolve = NO;
275
+ void (^finish)(BOOL) = ^(BOOL granted) {
276
+ if (didResolve) return;
277
+ didResolve = YES;
278
+ resolve(@(granted));
279
+ };
280
+
281
+ if (timeoutSeconds > 0.0) {
282
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutSeconds * NSEC_PER_SEC)),
283
+ dispatch_get_main_queue(), ^{
284
+ finish(SBHasMicPermission());
285
+ });
286
+ }
287
+
288
+ [audioSession requestRecordPermission:^(BOOL granted) {
289
+ dispatch_async(dispatch_get_main_queue(), ^{
290
+ finish(granted);
291
+ });
292
+ }];
293
+ });
294
+ }
295
+
296
+ RCT_EXPORT_METHOD(hasSpeechRecognitionPermissions:(RCTPromiseResolveBlock)resolve
297
+ rejecter:(RCTPromiseRejectBlock)reject)
298
+ {
299
+ resolve(@(SBHasSpeechRecognitionPermission()));
300
+ }
301
+
302
+ RCT_EXPORT_METHOD(requestSpeechRecognitionPermissions:(nonnull NSNumber *)wait_timeout
303
+ resolver:(RCTPromiseResolveBlock)resolve
304
+ rejecter:(RCTPromiseRejectBlock)reject)
305
+ {
306
+ dispatch_async(dispatch_get_main_queue(), ^{
307
+ SFSpeechRecognizerAuthorizationStatus status = [SFSpeechRecognizer authorizationStatus];
308
+ if (status == SFSpeechRecognizerAuthorizationStatusAuthorized) {
309
+ resolve(@YES);
310
+ return;
311
+ }
312
+
313
+ if (status == SFSpeechRecognizerAuthorizationStatusDenied ||
314
+ status == SFSpeechRecognizerAuthorizationStatusRestricted) {
315
+ resolve(@NO);
316
+ return;
317
+ }
318
+
319
+ NSTimeInterval timeoutSeconds = MAX(wait_timeout.doubleValue, 0.0) / 1000.0;
320
+ __block BOOL didResolve = NO;
321
+ void (^finish)(BOOL) = ^(BOOL granted) {
322
+ if (didResolve) return;
323
+ didResolve = YES;
324
+ resolve(@(granted));
325
+ };
326
+
327
+ if (timeoutSeconds > 0.0) {
328
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutSeconds * NSEC_PER_SEC)),
329
+ dispatch_get_main_queue(), ^{
330
+ finish(SBHasSpeechRecognitionPermission());
331
+ });
332
+ }
333
+
334
+ [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus updatedStatus) {
335
+ dispatch_async(dispatch_get_main_queue(), ^{
336
+ finish(updatedStatus == SFSpeechRecognizerAuthorizationStatusAuthorized);
337
+ });
338
+ }];
339
+ });
340
+ }
341
+
239
342
  #pragma mark - Unified API
240
343
 
344
+ RCT_EXPORT_METHOD(setLicense:(NSString *)licenseKey
345
+ resolver:(RCTPromiseResolveBlock)resolve
346
+ rejecter:(RCTPromiseRejectBlock)reject)
347
+ {
348
+ if (licenseKey == nil || (id)licenseKey == [NSNull null] || licenseKey.length == 0) {
349
+ reject(@"invalid_args", @"Missing licenseKey", nil);
350
+ return;
351
+ }
352
+
353
+ dispatch_async(dispatch_get_main_queue(), ^{
354
+ BOOL ttsOk = [DaVoiceTTS activateLicenseWithLicenseKey:licenseKey];
355
+ [self ensureSTT];
356
+ BOOL sttOk = [self.stt setLicenseWithLicenseKey:licenseKey];
357
+ resolve(@(ttsOk && sttOk));
358
+ });
359
+ }
360
+
361
+ RCT_EXPORT_METHOD(isLicenseValid:(NSString *)licenseKey
362
+ resolver:(RCTPromiseResolveBlock)resolve
363
+ rejecter:(RCTPromiseRejectBlock)reject)
364
+ {
365
+ if (licenseKey == nil || (id)licenseKey == [NSNull null] || licenseKey.length == 0) {
366
+ reject(@"invalid_args", @"Missing licenseKey", nil);
367
+ return;
368
+ }
369
+
370
+ dispatch_async(dispatch_get_main_queue(), ^{
371
+ BOOL ok = [LicenseManager isLicenseValidWithLicenseKey:licenseKey];
372
+ resolve(@(ok));
373
+ });
374
+ }
375
+
241
376
  /// initAll({ locale: "en-US", model: "/path/model.onnx", timeoutMs?: 8000 })
242
377
  RCT_EXPORT_METHOD(initAll:(NSDictionary *)opts
243
378
  resolver:(RCTPromiseResolveBlock)resolve
@@ -56,6 +56,36 @@ RCT_EXPORT_MODULE();
56
56
  return self;
57
57
  }
58
58
 
59
+ RCT_EXPORT_METHOD(setLicense:(NSString *)licenseKey
60
+ resolver:(RCTPromiseResolveBlock)resolve
61
+ rejecter:(RCTPromiseRejectBlock)reject)
62
+ {
63
+ if (licenseKey == nil || (id)licenseKey == [NSNull null] || licenseKey.length == 0) {
64
+ reject(@"invalid_args", @"Missing licenseKey", nil);
65
+ return;
66
+ }
67
+
68
+ dispatch_async(dispatch_get_main_queue(), ^{
69
+ BOOL ok = [DaVoiceTTS activateLicenseWithLicenseKey:licenseKey];
70
+ resolve(@(ok));
71
+ });
72
+ }
73
+
74
+ RCT_EXPORT_METHOD(isLicenseValid:(NSString *)licenseKey
75
+ resolver:(RCTPromiseResolveBlock)resolve
76
+ rejecter:(RCTPromiseRejectBlock)reject)
77
+ {
78
+ if (licenseKey == nil || (id)licenseKey == [NSNull null] || licenseKey.length == 0) {
79
+ reject(@"invalid_args", @"Missing licenseKey", nil);
80
+ return;
81
+ }
82
+
83
+ dispatch_async(dispatch_get_main_queue(), ^{
84
+ BOOL ok = [LicenseManager isLicenseValidWithLicenseKey:licenseKey];
85
+ resolve(@(ok));
86
+ });
87
+ }
88
+
59
89
  RCT_EXPORT_METHOD(initTTS:(NSDictionary *)configDict
60
90
  resolver:(RCTPromiseResolveBlock)resolve
61
91
  rejecter:(RCTPromiseRejectBlock)reject)
@@ -8,32 +8,32 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>DavoiceTTS.framework/DavoiceTTS</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64</string>
11
+ <string>ios-arm64_x86_64-simulator</string>
12
12
  <key>LibraryPath</key>
13
13
  <string>DavoiceTTS.framework</string>
14
14
  <key>SupportedArchitectures</key>
15
15
  <array>
16
16
  <string>arm64</string>
17
+ <string>x86_64</string>
17
18
  </array>
18
19
  <key>SupportedPlatform</key>
19
20
  <string>ios</string>
21
+ <key>SupportedPlatformVariant</key>
22
+ <string>simulator</string>
20
23
  </dict>
21
24
  <dict>
22
25
  <key>BinaryPath</key>
23
26
  <string>DavoiceTTS.framework/DavoiceTTS</string>
24
27
  <key>LibraryIdentifier</key>
25
- <string>ios-arm64_x86_64-simulator</string>
28
+ <string>ios-arm64</string>
26
29
  <key>LibraryPath</key>
27
30
  <string>DavoiceTTS.framework</string>
28
31
  <key>SupportedArchitectures</key>
29
32
  <array>
30
33
  <string>arm64</string>
31
- <string>x86_64</string>
32
34
  </array>
33
35
  <key>SupportedPlatform</key>
34
36
  <string>ios</string>
35
- <key>SupportedPlatformVariant</key>
36
- <string>simulator</string>
37
37
  </dict>
38
38
  </array>
39
39
  <key>CFBundlePackageType</key>
@@ -307,13 +307,15 @@ typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4)));
307
307
 
308
308
  #if defined(__OBJC__)
309
309
 
310
- @class NSURL;
311
310
  @class NSString;
311
+ @class NSURL;
312
312
  @class NSUUID;
313
313
  @class AVAudioPCMBuffer;
314
314
  SWIFT_CLASS("_TtC10DavoiceTTS10DaVoiceTTS")
315
315
  @interface DaVoiceTTS : NSObject
316
316
  @property (nonatomic, copy) void (^ _Nullable onLastUtteranceFinished)(void);
317
+ + (BOOL)activateLicenseWithLicenseKey:(NSString * _Nonnull)licenseKey SWIFT_WARN_UNUSED_RESULT;
318
+ - (BOOL)setLicenseWithLicenseKey:(NSString * _Nonnull)licenseKey SWIFT_WARN_UNUSED_RESULT;
317
319
  - (nullable instancetype)initWithModel:(NSURL * _Nonnull)model error:(NSError * _Nullable * _Nullable)error OBJC_DESIGNATED_INITIALIZER;
318
320
  /// Immediately stop speaking: cancels future synthesis work and flushes all playback.
319
321
  - (void)stopSpeaking;
@@ -334,6 +336,12 @@ SWIFT_CLASS("_TtC10DavoiceTTS10DaVoiceTTS")
334
336
  + (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
335
337
  @end
336
338
 
339
+ SWIFT_CLASS("_TtC10DavoiceTTS14LicenseManager")
340
+ @interface LicenseManager : NSObject
341
+ + (BOOL)isLicenseValidWithLicenseKey:(NSString * _Nonnull)licenseKey SWIFT_WARN_UNUSED_RESULT;
342
+ - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
343
+ @end
344
+
337
345
  @protocol STTDelegate;
338
346
  @class NSNumber;
339
347
  @class SFSpeechRecognizer;
@@ -351,6 +359,7 @@ SWIFT_CLASS("_TtC10DavoiceTTS3STT")
351
359
  @property (nonatomic) double speakerPreRollFlushMaxSeconds;
352
360
  SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSArray<NSString *> * _Nonnull supportedEvents;)
353
361
  + (NSArray<NSString *> * _Nonnull)supportedEvents SWIFT_WARN_UNUSED_RESULT;
362
+ - (BOOL)setLicenseWithLicenseKey:(NSString * _Nonnull)licenseKey SWIFT_WARN_UNUSED_RESULT;
354
363
  - (void)pauseSpeechRecognitionLite;
355
364
  - (void)unPauseSpeechRecognitionLite:(NSNumber * _Nonnull)times;
356
365
  - (void)pauseMicrophoneAndWait:(NSNumber * _Nonnull)timeoutMs completion:(void (^ _Nonnull)(BOOL, NSString * _Nullable))completion;