expo-modules-core 0.4.8 → 0.5.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.
Files changed (129) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/android/build.gradle +30 -2
  3. package/android/src/main/java/expo/modules/adapters/react/ModuleRegistryAdapter.java +27 -5
  4. package/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java +49 -5
  5. package/android/src/main/java/expo/modules/core/BasePackage.java +6 -0
  6. package/android/src/main/java/expo/modules/core/ModulePriorities.kt +25 -0
  7. package/android/src/main/java/expo/modules/core/interfaces/ActivityEventListener.java +3 -1
  8. package/android/src/main/java/expo/modules/core/interfaces/Package.java +4 -0
  9. package/android/src/main/java/expo/modules/core/interfaces/ReactActivityHandler.kt +18 -0
  10. package/android/src/main/java/expo/modules/core/interfaces/ReactNativeHostHandler.kt +14 -0
  11. package/android/src/main/java/expo/modules/core/utilities/KotlinUtilities.kt +23 -0
  12. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +166 -0
  13. package/android/src/main/java/expo/modules/kotlin/DynamicExtenstions.kt +9 -0
  14. package/android/src/main/java/expo/modules/kotlin/ExpoModulesHelper.kt +18 -0
  15. package/android/src/main/java/expo/modules/kotlin/KPromiseWrapper.kt +23 -0
  16. package/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt +98 -0
  17. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +41 -0
  18. package/android/src/main/java/expo/modules/kotlin/ModuleRegistry.kt +56 -0
  19. package/android/src/main/java/expo/modules/kotlin/ModulesProvider.kt +7 -0
  20. package/android/src/main/java/expo/modules/kotlin/Promise.kt +13 -0
  21. package/android/src/main/java/expo/modules/kotlin/ReactLifecycleDelegate.kt +39 -0
  22. package/android/src/main/java/expo/modules/kotlin/ReadableArrayIterator.kt +14 -0
  23. package/android/src/main/java/expo/modules/kotlin/ReadableTypeExtensions.kt +18 -0
  24. package/android/src/main/java/expo/modules/kotlin/allocators/ObjectConstructor.kt +5 -0
  25. package/android/src/main/java/expo/modules/kotlin/allocators/ObjectConstructorFactory.kt +31 -0
  26. package/android/src/main/java/expo/modules/kotlin/allocators/UnsafeAllocator.kt +49 -0
  27. package/android/src/main/java/expo/modules/kotlin/events/EventListener.kt +39 -0
  28. package/android/src/main/java/expo/modules/kotlin/events/EventName.kt +31 -0
  29. package/android/src/main/java/expo/modules/kotlin/events/EventsDefinition.kt +3 -0
  30. package/android/src/main/java/expo/modules/kotlin/events/KEventEmitterWrapper.kt +26 -0
  31. package/android/src/main/java/expo/modules/kotlin/events/OnActivityResultPayload.kt +8 -0
  32. package/android/src/main/java/expo/modules/kotlin/exception/CodedException.kt +70 -0
  33. package/android/src/main/java/expo/modules/kotlin/methods/AnyMethod.kt +50 -0
  34. package/android/src/main/java/expo/modules/kotlin/methods/Method.kt +14 -0
  35. package/android/src/main/java/expo/modules/kotlin/methods/PromiseMethod.kt +15 -0
  36. package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +24 -0
  37. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +227 -0
  38. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionData.kt +16 -0
  39. package/android/src/main/java/expo/modules/kotlin/records/Field.kt +5 -0
  40. package/android/src/main/java/expo/modules/kotlin/records/Record.kt +3 -0
  41. package/android/src/main/java/expo/modules/kotlin/records/RecordTypeConverter.kt +55 -0
  42. package/android/src/main/java/expo/modules/kotlin/types/AnyType.kt +14 -0
  43. package/android/src/main/java/expo/modules/kotlin/types/ArrayTypeConverter.kt +44 -0
  44. package/android/src/main/java/expo/modules/kotlin/types/BasicTypeConverters.kt +60 -0
  45. package/android/src/main/java/expo/modules/kotlin/types/EnumTypeConverter.kt +84 -0
  46. package/android/src/main/java/expo/modules/kotlin/types/ListTypeConverter.kt +25 -0
  47. package/android/src/main/java/expo/modules/kotlin/types/MapTypeConverter.kt +39 -0
  48. package/android/src/main/java/expo/modules/kotlin/types/PairTypeConverter.kt +28 -0
  49. package/android/src/main/java/expo/modules/kotlin/types/TypeConverter.kt +19 -0
  50. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +107 -0
  51. package/android/src/main/java/expo/modules/kotlin/views/AnyViewProp.kt +10 -0
  52. package/android/src/main/java/expo/modules/kotlin/views/ConcreteViewProp.kt +17 -0
  53. package/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt +22 -0
  54. package/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt +21 -0
  55. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinition.kt +36 -0
  56. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinitionBuilder.kt +40 -0
  57. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +21 -0
  58. package/android/src/main/java/expo/modules/kotlin/views/ViewWrapperDelegateHolder.kt +5 -0
  59. package/build/NativeModulesProxy.native.d.ts +4 -0
  60. package/build/NativeModulesProxy.native.js +14 -1
  61. package/build/NativeModulesProxy.native.js.map +1 -1
  62. package/build/NativeModulesProxy.types.d.ts +3 -0
  63. package/build/NativeModulesProxy.types.js.map +1 -1
  64. package/ios/AppDelegates/EXAppDelegateWrapper.h +16 -0
  65. package/ios/AppDelegates/EXAppDelegateWrapper.m +42 -0
  66. package/ios/AppDelegates/EXAppDelegatesLoader.h +15 -0
  67. package/ios/AppDelegates/EXAppDelegatesLoader.m +29 -0
  68. package/ios/AppDelegates/EXLegacyAppDelegateWrapper.h +16 -0
  69. package/ios/{EXAppDelegateWrapper.m → AppDelegates/EXLegacyAppDelegateWrapper.m} +2 -2
  70. package/ios/AppDelegates/ExpoAppDelegate.swift +264 -0
  71. package/ios/AppDelegates/ExpoAppDelegateSubscriber.swift +24 -0
  72. package/ios/ExpoModulesCore.podspec +7 -2
  73. package/ios/JSI/ExpoModulesProxySpec.h +24 -0
  74. package/ios/JSI/ExpoModulesProxySpec.mm +135 -0
  75. package/ios/JSI/JSIConversions.h +42 -0
  76. package/ios/JSI/JSIConversions.mm +164 -0
  77. package/ios/JSI/JSIInstaller.h +19 -0
  78. package/ios/JSI/JSIInstaller.mm +22 -0
  79. package/ios/ModuleRegistryAdapter/EXModuleRegistryAdapter.m +1 -6
  80. package/ios/NativeModulesProxy/EXNativeModulesProxy.h +6 -0
  81. package/ios/NativeModulesProxy/{EXNativeModulesProxy.m → EXNativeModulesProxy.mm} +45 -12
  82. package/ios/Services/EXReactNativeEventEmitter.h +6 -0
  83. package/ios/Services/EXReactNativeEventEmitter.m +15 -0
  84. package/ios/Swift/AppContext.swift +14 -1
  85. package/ios/Swift/Arguments/AnyArgument.swift +14 -0
  86. package/ios/Swift/Arguments/AnyArgumentType.swift +13 -0
  87. package/ios/Swift/Arguments/ArgumentType.swift +24 -0
  88. package/ios/Swift/Arguments/ConvertibleArgument.swift +15 -0
  89. package/ios/Swift/Arguments/Convertibles.swift +93 -0
  90. package/ios/Swift/Arguments/Types/ArrayArgumentType.swift +42 -0
  91. package/ios/Swift/Arguments/Types/ConvertibleArgumentType.swift +16 -0
  92. package/ios/Swift/Arguments/Types/EnumArgumentType.swift +105 -0
  93. package/ios/Swift/Arguments/Types/OptionalArgumentType.swift +49 -0
  94. package/ios/Swift/Arguments/Types/PromiseArgumentType.swift +15 -0
  95. package/ios/Swift/Arguments/Types/RawArgumentType.swift +25 -0
  96. package/ios/Swift/Conversions.swift +199 -7
  97. package/ios/Swift/EventListener.swift +37 -5
  98. package/ios/Swift/Functions/AnyFunction.swift +42 -0
  99. package/ios/Swift/{Methods/ConcreteMethod.swift → Functions/ConcreteFunction.swift} +32 -34
  100. package/ios/Swift/ModuleHolder.swift +75 -20
  101. package/ios/Swift/ModuleRegistry.swift +19 -8
  102. package/ios/Swift/Modules/AnyModule.swift +8 -8
  103. package/ios/Swift/Modules/Module.swift +7 -0
  104. package/ios/Swift/Modules/ModuleDefinition.swift +52 -8
  105. package/ios/Swift/Modules/ModuleDefinitionBuilder.swift +1 -1
  106. package/ios/Swift/Modules/ModuleDefinitionComponents.swift +140 -52
  107. package/ios/Swift/ModulesProvider.swift +9 -0
  108. package/ios/Swift/Promise.swift +1 -1
  109. package/ios/Swift/Records/Field.swift +1 -1
  110. package/ios/Swift/Records/Record.swift +8 -1
  111. package/ios/Swift/SwiftInteropBridge.swift +45 -16
  112. package/ios/Swift/Views/AnyViewProp.swift +2 -2
  113. package/ios/Swift/Views/ConcreteViewProp.swift +37 -10
  114. package/ios/Swift/Views/ViewModuleWrapper.swift +9 -4
  115. package/ios/Swift.h +9 -0
  116. package/ios/Tests/ArgumentTypeSpec.swift +145 -0
  117. package/ios/Tests/ConvertiblesSpec.swift +231 -0
  118. package/ios/Tests/{MethodSpec.swift → FunctionSpec.swift} +69 -54
  119. package/ios/Tests/FunctionWithConvertiblesSpec.swift +66 -0
  120. package/ios/Tests/Mocks/ModuleMocks.swift +21 -7
  121. package/ios/Tests/ModuleEventListenersSpec.swift +17 -16
  122. package/ios/Tests/ModuleRegistrySpec.swift +4 -7
  123. package/package.json +3 -3
  124. package/src/NativeModulesProxy.native.ts +22 -2
  125. package/src/NativeModulesProxy.types.ts +8 -0
  126. package/ios/EXAppDelegateWrapper.h +0 -13
  127. package/ios/Swift/Methods/AnyArgumentType.swift +0 -48
  128. package/ios/Swift/Methods/AnyMethod.swift +0 -31
  129. package/ios/Swift/Methods/AnyMethodArgument.swift +0 -13
@@ -0,0 +1,15 @@
1
+ // Copyright 2021-present 650 Industries. All rights reserved.
2
+
3
+ /**
4
+ An argument type that represents the `Promise` argument.
5
+ */
6
+ internal struct PromiseArgumentType: AnyArgumentType {
7
+ func cast<ArgType>(_ value: ArgType) throws -> Any {
8
+ if let value = value as? Promise {
9
+ return value
10
+ }
11
+ throw Conversions.CastingError<Promise>(value: value)
12
+ }
13
+
14
+ var description: String = "Promise"
15
+ }
@@ -0,0 +1,25 @@
1
+ // Copyright 2021-present 650 Industries. All rights reserved.
2
+
3
+ /**
4
+ An argument type that can wrap any type, but it casts only type-compatible values using `as?` keyword.
5
+ The innermost type of other argument types like `ArrayArgumentType` and `OptionalArgumentType`.
6
+ */
7
+ internal struct RawArgumentType<InnerType>: AnyArgumentType {
8
+ let innerType: InnerType.Type
9
+
10
+ func cast<ArgType>(_ value: ArgType) throws -> Any {
11
+ if let value = value as? InnerType {
12
+ return value
13
+ }
14
+ // Raw arguments are always non-optional, but they may receive `nil` values.
15
+ // Let's throw more specific error in this case.
16
+ if Optional.isNil(value) {
17
+ throw Conversions.NullCastError<InnerType>()
18
+ }
19
+ throw Conversions.CastingError<InnerType>(value: value)
20
+ }
21
+
22
+ var description: String {
23
+ String(describing: innerType.self)
24
+ }
25
+ }
@@ -1,5 +1,5 @@
1
1
 
2
- internal class Conversions {
2
+ internal final class Conversions {
3
3
  /**
4
4
  Converts an array to tuple. Because of tuples nature, it's not possible to convert an array of any size, so we can support only up to some fixed size.
5
5
  */
@@ -31,12 +31,204 @@ internal class Conversions {
31
31
  throw TooManyArgumentsError(count: array.count, limit: 10)
32
32
  }
33
33
  }
34
- }
35
34
 
36
- internal struct TooManyArgumentsError: CodedError {
37
- let count: Int
38
- let limit: Int
39
- var description: String {
40
- "A number of arguments `\(count)` exceeds the limit of `\(limit)`"
35
+ static func fromNSObject(_ object: Any) -> Any {
36
+ switch object {
37
+ case let object as NSArray:
38
+ return object.map { Conversions.fromNSObject($0) }
39
+ case let object as NSDictionary:
40
+ let keyValuePairs: [(String, Any)] = object.map { ($0 as! String, Conversions.fromNSObject($1)) }
41
+ return Dictionary(uniqueKeysWithValues: keyValuePairs)
42
+ case is NSNull:
43
+ return Optional<Any>.none as Any
44
+ default:
45
+ return object
46
+ }
47
+ }
48
+
49
+ /**
50
+ Picks values under given keys from the dictionary, casted to a specific type. Can throw errors when
51
+ - The dictionary is missing some of the given keys (`MissingKeysError`)
52
+ - Some of the values cannot be casted to specified type (`CastingValuesError`)
53
+ */
54
+ static func pickValues<ValueType>(from dict: [String: Any], byKeys keys: [String], as type: ValueType.Type) throws -> [ValueType] {
55
+ var result = (
56
+ values: [ValueType](),
57
+ missingKeys: [String](),
58
+ invalidKeys: [String]()
59
+ )
60
+
61
+ for key in keys {
62
+ if dict[key] == nil {
63
+ result.missingKeys.append(key)
64
+ }
65
+ if let value = dict[key] as? ValueType {
66
+ result.values.append(value)
67
+ } else {
68
+ result.invalidKeys.append(key)
69
+ }
70
+ }
71
+ if result.missingKeys.count > 0 {
72
+ throw MissingKeysError<ValueType>(keys: result.missingKeys)
73
+ }
74
+ if result.invalidKeys.count > 0 {
75
+ throw CastingValuesError<ValueType>(keys: result.invalidKeys)
76
+ }
77
+ return result.values
78
+ }
79
+
80
+ /**
81
+ Converts hex string to `UIColor` or throws an error if the string is corrupted.
82
+ */
83
+ static func toColor(hexString hex: String) throws -> UIColor {
84
+ var hexStr = hex
85
+ .trimmingCharacters(in: .whitespacesAndNewlines)
86
+ .replacingOccurrences(of: "#", with: "")
87
+
88
+ // If just RGB, set alpha to maximum
89
+ if hexStr.count == 6 { hexStr += "FF" }
90
+ if hexStr.count == 3 { hexStr += "F" }
91
+
92
+ // Expand short form (supported by Web)
93
+ if hexStr.count == 4 {
94
+ let chars = Array(hexStr)
95
+ hexStr = [
96
+ String(repeating: chars[0], count: 2),
97
+ String(repeating: chars[1], count: 2),
98
+ String(repeating: chars[2], count: 2),
99
+ String(repeating: chars[3], count: 2)
100
+ ].joined(separator: "")
101
+ }
102
+
103
+ var rgba: UInt64 = 0
104
+
105
+ guard hexStr.range(of: #"^[0-9a-fA-F]{8}$"#, options: .regularExpression) != nil,
106
+ Scanner(string: hexStr).scanHexInt64(&rgba) else {
107
+ throw InvalidHexColorError(hex: hex)
108
+ }
109
+ return try toColor(rgba: rgba)
110
+ }
111
+
112
+ /**
113
+ Converts an integer for ARGB color to `UIColor`. Since the alpha channel is represented by first 8 bits,
114
+ it's optional out of the box. React Native converts colors to such format.
115
+ */
116
+ static func toColor(argb: UInt64) throws -> UIColor {
117
+ guard argb <= UInt32.max else {
118
+ throw HexColorOverflowError(hex: argb)
119
+ }
120
+ let alpha = CGFloat((argb >> 24) & 0xff) / 255.0
121
+ let red = CGFloat((argb >> 16) & 0xff) / 255.0
122
+ let green = CGFloat((argb >> 8) & 0xff) / 255.0
123
+ let blue = CGFloat(argb & 0xff) / 255.0
124
+ return UIColor(red: red, green: green, blue: blue, alpha: alpha)
125
+ }
126
+
127
+ /**
128
+ Converts an integer for RGBA color to `UIColor`.
129
+ */
130
+ static func toColor(rgba: UInt64) throws -> UIColor {
131
+ guard rgba <= UInt32.max else {
132
+ throw HexColorOverflowError(hex: rgba)
133
+ }
134
+ let red = CGFloat((rgba >> 24) & 0xff) / 255.0
135
+ let green = CGFloat((rgba >> 16) & 0xff) / 255.0
136
+ let blue = CGFloat((rgba >> 8) & 0xff) / 255.0
137
+ let alpha = CGFloat(rgba & 0xff) / 255.0
138
+ return UIColor(red: red, green: green, blue: blue, alpha: alpha)
139
+ }
140
+
141
+ /**
142
+ Formats an array of keys to the string with keys in apostrophes separated by commas.
143
+ */
144
+ static func formatKeys(_ keys: [String]) -> String {
145
+ return keys.map { "`\($0)`" }.joined(separator: ", ")
146
+ }
147
+
148
+ // MARK: - Errors
149
+
150
+ /**
151
+ An error meaning that the number of arguments exceeds the limit.
152
+ */
153
+ internal struct TooManyArgumentsError: CodedError {
154
+ let count: Int
155
+ let limit: Int
156
+ var description: String {
157
+ "A number of arguments `\(count)` exceeds the limit of `\(limit)`"
158
+ }
159
+ }
160
+
161
+ /**
162
+ An error that can be thrown by convertible types, when given value cannot be converted.
163
+ */
164
+ internal struct ConvertingError<TargetType>: CodedError {
165
+ let value: Any?
166
+ var code: String = "ERR_CONVERTING_FAILED"
167
+ var description: String {
168
+ "Cannot convert `\(String(describing: value))` to `\(TargetType.self)`"
169
+ }
170
+ }
171
+
172
+ /**
173
+ An error that is thrown when given value cannot be casted.
174
+ */
175
+ internal struct CastingError<TargetType>: CodedError {
176
+ let value: Any
177
+ var code: String = "ERR_CASTING_FAILED"
178
+ var description: String {
179
+ "Cannot cast `\(String(describing: value))` to `\(TargetType.self)`"
180
+ }
181
+ }
182
+
183
+ /**
184
+ An error that can be thrown by convertible types,
185
+ when the values in given dictionary cannot be casted to specific type.
186
+ */
187
+ internal struct CastingValuesError<ValueType>: CodedError {
188
+ let keys: [String]
189
+ var code: String = "ERR_CASTING_VALUES_FAILED"
190
+ var description: String {
191
+ "Cannot cast keys \(formatKeys(keys)) to `\(ValueType.self)`"
192
+ }
193
+ }
194
+
195
+ /**
196
+ An error that can be throw by convertible types,
197
+ when given dictionary is missing some required keys.
198
+ */
199
+ internal struct MissingKeysError<ValueType>: CodedError {
200
+ let keys: [String]
201
+ var description: String {
202
+ "Missing keys \(formatKeys(keys)) of type `\(ValueType.self)`"
203
+ }
204
+ }
205
+
206
+ /**
207
+ An error that is thrown when null value is tried to be casted to non-optional type.
208
+ */
209
+ internal struct NullCastError<TargetType>: CodedError {
210
+ var description: String {
211
+ "Cannot cast null value to non-optional `\(TargetType.self)`"
212
+ }
213
+ }
214
+
215
+ /**
216
+ An error used when the hex color string is invalid (e.g. contains non-hex characters).
217
+ */
218
+ internal struct InvalidHexColorError: CodedError {
219
+ let hex: String
220
+ var description: String {
221
+ "Provided hex color `\(hex)` is invalid"
222
+ }
223
+ }
224
+
225
+ /**
226
+ An error used when the integer value of the color would result in an overflow of `UInt32`.
227
+ */
228
+ internal struct HexColorOverflowError: CodedError {
229
+ let hex: UInt64
230
+ var description: String {
231
+ "Provided hex color `\(hex)` would result in an overflow"
232
+ }
41
233
  }
42
234
  }
@@ -3,20 +3,52 @@
3
3
  */
4
4
  internal struct EventListener: AnyDefinition {
5
5
  let name: EventName
6
- let call: (Any?) -> Void
6
+ let call: (Any?, Any?) throws -> Void
7
7
 
8
+ /**
9
+ Listener initializer for events without sender and payload.
10
+ */
8
11
  init(_ name: EventName, _ listener: @escaping () -> Void) {
9
12
  self.name = name
10
- self.call = { payload in listener() }
13
+ self.call = { (sender, payload) in listener() }
11
14
  }
12
15
 
13
- init<PayloadType>(_ name: EventName, _ listener: @escaping (PayloadType?) -> Void) {
16
+ /**
17
+ Listener initializer for events with no payload.
18
+ */
19
+ init<Sender>(_ name: EventName, _ listener: @escaping (Sender) -> Void) {
14
20
  self.name = name
15
- self.call = { payload in listener(payload as? PayloadType) }
21
+ self.call = { (sender, payload) in
22
+ guard let sender = sender as? Sender else {
23
+ throw InvalidSenderTypeError(eventName: name, senderType: Sender.self)
24
+ }
25
+ listener(sender)
26
+ }
27
+ }
28
+
29
+ /**
30
+ Listener initializer for events that specify the payload.
31
+ */
32
+ init<Sender, PayloadType>(_ name: EventName, _ listener: @escaping (Sender, PayloadType?) -> Void) {
33
+ self.name = name
34
+ self.call = { (sender, payload) in
35
+ guard let sender = sender as? Sender else {
36
+ throw InvalidSenderTypeError(eventName: name, senderType: Sender.self)
37
+ }
38
+ listener(sender, payload as? PayloadType)
39
+ }
40
+ }
41
+ }
42
+
43
+ struct InvalidSenderTypeError: CodedError {
44
+ var eventName: EventName
45
+ var senderType: Any.Type
46
+ var description: String {
47
+ "Sender for event `\(eventName)` must be of type `\(senderType)`."
16
48
  }
17
49
  }
18
50
 
19
- internal enum EventName: Equatable {
51
+ public enum EventName: Equatable {
20
52
  case custom(_ name: String)
21
53
 
22
54
  // MARK: Module lifecycle
@@ -0,0 +1,42 @@
1
+ import Dispatch
2
+
3
+ /**
4
+ A protocol for any type-erased function.
5
+ */
6
+ public protocol AnyFunction: AnyDefinition {
7
+ /**
8
+ Name of the function. JavaScript refers to the function by this name.
9
+ */
10
+ var name: String { get }
11
+
12
+ /**
13
+ Bool value indicating whether the function takes promise as the last argument.
14
+ */
15
+ var takesPromise: Bool { get }
16
+
17
+ /**
18
+ A number of arguments the function takes. If the last argument is of type `Promise`, it is not counted.
19
+ */
20
+ var argumentsCount: Int { get }
21
+
22
+ /**
23
+ Dispatch queue on which each function's call is run.
24
+ */
25
+ var queue: DispatchQueue? { get }
26
+
27
+ /**
28
+ Calls the function on given module with arguments and a promise.
29
+ */
30
+ func call(args: [Any], promise: Promise) -> Void
31
+
32
+ /**
33
+ Synchronously calls the function with given arguments. If the function takes a promise,
34
+ the current thread will be locked until the promise rejects or resolves with the return value.
35
+ */
36
+ func callSync(args: [Any]) -> Any
37
+
38
+ /**
39
+ Specifies on which queue the function should run.
40
+ */
41
+ func runOnQueue(_ queue: DispatchQueue?) -> Self
42
+ }
@@ -1,12 +1,12 @@
1
1
  import Dispatch
2
2
 
3
- public class ConcreteMethod<Args, ReturnType>: AnyMethod {
3
+ public final class ConcreteFunction<Args, ReturnType>: AnyFunction {
4
4
  public typealias ClosureType = (Args) -> ReturnType
5
5
 
6
6
  public let name: String
7
7
 
8
8
  public var takesPromise: Bool {
9
- return argTypes.last?.canCastToType(Promise.self) ?? false
9
+ return argTypes.last is PromiseArgumentType
10
10
  }
11
11
 
12
12
  public var argumentsCount: Int {
@@ -29,7 +29,7 @@ public class ConcreteMethod<Args, ReturnType>: AnyMethod {
29
29
  self.closure = closure
30
30
  }
31
31
 
32
- public func call(args: [Any?], promise: Promise) {
32
+ public func call(args: [Any], promise: Promise) {
33
33
  let takesPromise = self.takesPromise
34
34
  let returnedValue: ReturnType?
35
35
 
@@ -54,6 +54,31 @@ public class ConcreteMethod<Args, ReturnType>: AnyMethod {
54
54
  }
55
55
  }
56
56
 
57
+ public func callSync(args: [Any]) -> Any {
58
+ if takesPromise {
59
+ var result: Any?
60
+ let semaphore = DispatchSemaphore(value: 0)
61
+
62
+ let promise = Promise {
63
+ result = $0
64
+ semaphore.signal()
65
+ } rejecter: { error in
66
+ semaphore.signal()
67
+ }
68
+ call(args: args, promise: promise)
69
+ semaphore.wait()
70
+ return result as Any
71
+ } else {
72
+ do {
73
+ let finalArgs = try castArguments(args)
74
+ let tuple = try Conversions.toTuple(finalArgs) as! Args
75
+ return closure(tuple)
76
+ } catch let error {
77
+ return error
78
+ }
79
+ }
80
+ }
81
+
57
82
  public func runOnQueue(_ queue: DispatchQueue?) -> Self {
58
83
  self.queue = queue
59
84
  return self
@@ -63,33 +88,15 @@ public class ConcreteMethod<Args, ReturnType>: AnyMethod {
63
88
  return (0..<argTypes.count).contains(index) ? argTypes[index] : nil
64
89
  }
65
90
 
66
- private func castArguments(_ args: [Any?]) throws -> [AnyMethodArgument?] {
91
+ private func castArguments(_ args: [Any]) throws -> [Any] {
67
92
  if args.count != argumentsCount {
68
93
  throw InvalidArgsNumberError(received: args.count, expected: argumentsCount)
69
94
  }
70
95
  return try args.enumerated().map { (index, arg) in
71
- guard let desiredType = argumentType(atIndex: index) else {
72
- return nil
73
- }
96
+ let expectedType = argumentType(atIndex: index)
74
97
 
75
- // If the type of argument matches the desired type, just cast and return it.
76
- // This usually covers all cases for primitive types or plain dicts and arrays.
77
- if desiredType.canCast(arg) {
78
- return desiredType.cast(arg)
79
- }
80
-
81
- // TODO: (@tsapeta) Handle structs convertible to dictionary
82
- // If we get here, the argument can be converted (not casted!) to the desired type.
83
- if let arg = arg as? Record.Dict, let dt = desiredType.castWrappedType(Record.Type.self) {
84
- return try dt.init(from: arg)
85
- }
86
-
87
- // TODO: (@tsapeta) Handle convertible arrays
88
- throw IncompatibleArgTypeError(
89
- argument: arg,
90
- atIndex: index,
91
- desiredType: desiredType
92
- )
98
+ // It's safe to unwrap since the arguments count matches.
99
+ return try expectedType!.cast(arg)
93
100
  }
94
101
  }
95
102
  }
@@ -101,12 +108,3 @@ internal struct InvalidArgsNumberError: CodedError {
101
108
  "Received \(received) arguments, but \(expected) was expected."
102
109
  }
103
110
  }
104
-
105
- internal struct IncompatibleArgTypeError<ArgumentType>: CodedError {
106
- let argument: ArgumentType
107
- let atIndex: Int
108
- let desiredType: AnyArgumentType
109
- var description: String {
110
- "Type `\(type(of: argument))` of argument at index `\(atIndex)` is not compatible with expected type `\(desiredType.typeName)`."
111
- }
112
- }
@@ -3,44 +3,78 @@ import Dispatch
3
3
  /**
4
4
  Holds a reference to the module instance and caches its definition.
5
5
  */
6
- public class ModuleHolder {
6
+ public final class ModuleHolder {
7
+ /**
8
+ Instance of the module.
9
+ */
7
10
  private(set) var module: AnyModule
8
11
 
9
- private(set) lazy var definition: ModuleDefinition = module.definition()
12
+ /**
13
+ A weak reference to the app context.
14
+ */
15
+ private(set) weak var appContext: AppContext?
10
16
 
17
+ /**
18
+ Caches the definition of the module type.
19
+ */
20
+ let definition: ModuleDefinition
21
+
22
+ /**
23
+ Returns `definition.name` if not empty, otherwise falls back to the module type name.
24
+ */
11
25
  var name: String {
12
- return definition.name ?? String(describing: type(of: module))
26
+ return definition.name.isEmpty ? String(describing: type(of: module)) : definition.name
13
27
  }
14
28
 
15
- init(module: AnyModule) {
16
- self.module = module
29
+ /**
30
+ Number of JavaScript listeners attached to the module.
31
+ */
32
+ var listenersCount: Int = 0
17
33
 
34
+ init(appContext: AppContext, module: AnyModule) {
35
+ self.appContext = appContext
36
+ self.module = module
37
+ self.definition = module.definition()
18
38
  post(event: .moduleCreate)
19
39
  }
20
40
 
21
- // MARK: Calling methods
41
+ // MARK: Calling functions
42
+
43
+ func call(function functionName: String, args: [Any], promise: Promise) {
44
+ do {
45
+ guard let function = definition.functions[functionName] else {
46
+ throw FunctionNotFoundError(functionName: functionName, moduleName: self.name)
47
+ }
48
+ let queue = function.queue ?? DispatchQueue.global(qos: .default)
22
49
 
23
- func call(method methodName: String, args: [Any?], promise: Promise) {
24
- if let method = definition.methods[methodName] {
25
- let queue = method.queue ?? DispatchQueue.global(qos: .default)
26
50
  queue.async {
27
- method.call(args: args, promise: promise)
51
+ function.call(args: args, promise: promise)
28
52
  }
29
- } else {
30
- promise.reject(MethodNotFoundError(methodName: methodName, moduleName: self.name))
53
+ } catch let error as CodedError {
54
+ promise.reject(error)
55
+ } catch {
56
+ promise.reject(UnexpectedError(error))
31
57
  }
32
58
  }
33
59
 
34
- func call(method methodName: String, args: [Any?], _ callback: @escaping (Any?, CodedError?) -> Void = { _, _ in }) {
60
+ func call(function functionName: String, args: [Any], _ callback: @escaping (Any?, CodedError?) -> Void = { _, _ in }) {
35
61
  let promise = Promise {
36
62
  callback($0, nil)
37
63
  } rejecter: {
38
64
  callback(nil, $0)
39
65
  }
40
- call(method: methodName, args: args, promise: promise)
66
+ call(function: functionName, args: args, promise: promise)
41
67
  }
42
68
 
43
- // MARK: Listening to events
69
+ @discardableResult
70
+ func callSync(function functionName: String, args: [Any]) -> Any? {
71
+ if let function = definition.functions[functionName] {
72
+ return function.callSync(args: args)
73
+ }
74
+ return nil
75
+ }
76
+
77
+ // MARK: Listening to native events
44
78
 
45
79
  func listeners(forEvent event: EventName) -> [EventListener] {
46
80
  return definition.eventListeners.filter {
@@ -50,16 +84,30 @@ public class ModuleHolder {
50
84
 
51
85
  func post(event: EventName) {
52
86
  listeners(forEvent: event).forEach {
53
- $0.call(nil)
87
+ try? $0.call(module, nil)
54
88
  }
55
89
  }
56
90
 
57
91
  func post<PayloadType>(event: EventName, payload: PayloadType?) {
58
92
  listeners(forEvent: event).forEach {
59
- $0.call(payload)
93
+ try? $0.call(module, payload)
60
94
  }
61
95
  }
62
96
 
97
+ // MARK: JavaScript events
98
+
99
+ /**
100
+ Modifies module's listeners count and calls `onStartObserving` or `onStopObserving` accordingly.
101
+ */
102
+ func modifyListenersCount(_ count: Int) {
103
+ if count > 0 && listenersCount == 0 {
104
+ let _ = definition.functions["startObserving"]?.callSync(args: [])
105
+ } else if count < 0 && listenersCount + count <= 0 {
106
+ let _ = definition.functions["stopObserving"]?.callSync(args: [])
107
+ }
108
+ listenersCount = max(0, listenersCount + count)
109
+ }
110
+
63
111
  // MARK: Deallocation
64
112
 
65
113
  deinit {
@@ -68,11 +116,18 @@ public class ModuleHolder {
68
116
 
69
117
  // MARK: Errors
70
118
 
71
- struct MethodNotFoundError: CodedError {
72
- let methodName: String
119
+ struct ModuleNotFoundError: CodedError {
120
+ let moduleName: String
121
+ var description: String {
122
+ "Cannot find module `\(moduleName)`"
123
+ }
124
+ }
125
+
126
+ struct FunctionNotFoundError: CodedError {
127
+ let functionName: String
73
128
  let moduleName: String
74
129
  var description: String {
75
- "Cannot find method `\(methodName)` in module `\(moduleName)`"
130
+ "Cannot find function `\(functionName)` in module `\(moduleName)`"
76
131
  }
77
132
  }
78
133
  }
@@ -1,5 +1,5 @@
1
1
 
2
- public class ModuleRegistry: Sequence {
2
+ public final class ModuleRegistry: Sequence {
3
3
  public typealias Element = ModuleHolder
4
4
 
5
5
  private weak var appContext: AppContext?
@@ -11,23 +11,30 @@ public class ModuleRegistry: Sequence {
11
11
  }
12
12
 
13
13
  /**
14
- Registers a single module in the registry.
14
+ Registers an instance of module holder.
15
15
  */
16
- public func register(module: AnyModule) {
17
- let holder = ModuleHolder(module: module)
16
+ internal func register(holder: ModuleHolder) {
18
17
  registry[holder.name] = holder
19
18
  }
20
19
 
21
20
  /**
22
- Registers modules exported by given modules provider.
21
+ Registers a module by its type.
23
22
  */
24
- public func register(fromProvider provider: ModulesProviderProtocol) {
23
+ public func register(moduleType: AnyModule.Type) {
25
24
  guard let appContext = appContext else {
26
- // TODO: (@tsapeta) App context is deallocated, throw an error?
27
25
  return
28
26
  }
27
+ let module = moduleType.init(appContext: appContext)
28
+ let holder = ModuleHolder(appContext: appContext, module: module)
29
+ register(holder: holder)
30
+ }
31
+
32
+ /**
33
+ Registers modules exported by given modules provider.
34
+ */
35
+ public func register(fromProvider provider: ModulesProviderProtocol) {
29
36
  provider.getModuleClasses().forEach { moduleType in
30
- register(module: moduleType.init(appContext: appContext))
37
+ register(moduleType: moduleType)
31
38
  }
32
39
  }
33
40
 
@@ -40,6 +47,10 @@ public class ModuleRegistry: Sequence {
40
47
  }
41
48
  }
42
49
 
50
+ public func unregister(moduleName: String) {
51
+ registry[moduleName] = nil
52
+ }
53
+
43
54
  public func has(moduleWithName moduleName: String) -> Bool {
44
55
  return registry[moduleName] != nil
45
56
  }