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.
- package/CHANGELOG.md +14 -1
- package/android/build.gradle +30 -2
- package/android/src/main/java/expo/modules/adapters/react/ModuleRegistryAdapter.java +27 -5
- package/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java +49 -5
- package/android/src/main/java/expo/modules/core/BasePackage.java +6 -0
- package/android/src/main/java/expo/modules/core/ModulePriorities.kt +25 -0
- package/android/src/main/java/expo/modules/core/interfaces/ActivityEventListener.java +3 -1
- package/android/src/main/java/expo/modules/core/interfaces/Package.java +4 -0
- package/android/src/main/java/expo/modules/core/interfaces/ReactActivityHandler.kt +18 -0
- package/android/src/main/java/expo/modules/core/interfaces/ReactNativeHostHandler.kt +14 -0
- package/android/src/main/java/expo/modules/core/utilities/KotlinUtilities.kt +23 -0
- package/android/src/main/java/expo/modules/kotlin/AppContext.kt +166 -0
- package/android/src/main/java/expo/modules/kotlin/DynamicExtenstions.kt +9 -0
- package/android/src/main/java/expo/modules/kotlin/ExpoModulesHelper.kt +18 -0
- package/android/src/main/java/expo/modules/kotlin/KPromiseWrapper.kt +23 -0
- package/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt +98 -0
- package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +41 -0
- package/android/src/main/java/expo/modules/kotlin/ModuleRegistry.kt +56 -0
- package/android/src/main/java/expo/modules/kotlin/ModulesProvider.kt +7 -0
- package/android/src/main/java/expo/modules/kotlin/Promise.kt +13 -0
- package/android/src/main/java/expo/modules/kotlin/ReactLifecycleDelegate.kt +39 -0
- package/android/src/main/java/expo/modules/kotlin/ReadableArrayIterator.kt +14 -0
- package/android/src/main/java/expo/modules/kotlin/ReadableTypeExtensions.kt +18 -0
- package/android/src/main/java/expo/modules/kotlin/allocators/ObjectConstructor.kt +5 -0
- package/android/src/main/java/expo/modules/kotlin/allocators/ObjectConstructorFactory.kt +31 -0
- package/android/src/main/java/expo/modules/kotlin/allocators/UnsafeAllocator.kt +49 -0
- package/android/src/main/java/expo/modules/kotlin/events/EventListener.kt +39 -0
- package/android/src/main/java/expo/modules/kotlin/events/EventName.kt +31 -0
- package/android/src/main/java/expo/modules/kotlin/events/EventsDefinition.kt +3 -0
- package/android/src/main/java/expo/modules/kotlin/events/KEventEmitterWrapper.kt +26 -0
- package/android/src/main/java/expo/modules/kotlin/events/OnActivityResultPayload.kt +8 -0
- package/android/src/main/java/expo/modules/kotlin/exception/CodedException.kt +70 -0
- package/android/src/main/java/expo/modules/kotlin/methods/AnyMethod.kt +50 -0
- package/android/src/main/java/expo/modules/kotlin/methods/Method.kt +14 -0
- package/android/src/main/java/expo/modules/kotlin/methods/PromiseMethod.kt +15 -0
- package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +24 -0
- package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +227 -0
- package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionData.kt +16 -0
- package/android/src/main/java/expo/modules/kotlin/records/Field.kt +5 -0
- package/android/src/main/java/expo/modules/kotlin/records/Record.kt +3 -0
- package/android/src/main/java/expo/modules/kotlin/records/RecordTypeConverter.kt +55 -0
- package/android/src/main/java/expo/modules/kotlin/types/AnyType.kt +14 -0
- package/android/src/main/java/expo/modules/kotlin/types/ArrayTypeConverter.kt +44 -0
- package/android/src/main/java/expo/modules/kotlin/types/BasicTypeConverters.kt +60 -0
- package/android/src/main/java/expo/modules/kotlin/types/EnumTypeConverter.kt +84 -0
- package/android/src/main/java/expo/modules/kotlin/types/ListTypeConverter.kt +25 -0
- package/android/src/main/java/expo/modules/kotlin/types/MapTypeConverter.kt +39 -0
- package/android/src/main/java/expo/modules/kotlin/types/PairTypeConverter.kt +28 -0
- package/android/src/main/java/expo/modules/kotlin/types/TypeConverter.kt +19 -0
- package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +107 -0
- package/android/src/main/java/expo/modules/kotlin/views/AnyViewProp.kt +10 -0
- package/android/src/main/java/expo/modules/kotlin/views/ConcreteViewProp.kt +17 -0
- package/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt +22 -0
- package/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt +21 -0
- package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinition.kt +36 -0
- package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinitionBuilder.kt +40 -0
- package/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +21 -0
- package/android/src/main/java/expo/modules/kotlin/views/ViewWrapperDelegateHolder.kt +5 -0
- package/build/NativeModulesProxy.native.d.ts +4 -0
- package/build/NativeModulesProxy.native.js +14 -1
- package/build/NativeModulesProxy.native.js.map +1 -1
- package/build/NativeModulesProxy.types.d.ts +3 -0
- package/build/NativeModulesProxy.types.js.map +1 -1
- package/ios/AppDelegates/EXAppDelegateWrapper.h +16 -0
- package/ios/AppDelegates/EXAppDelegateWrapper.m +42 -0
- package/ios/AppDelegates/EXAppDelegatesLoader.h +15 -0
- package/ios/AppDelegates/EXAppDelegatesLoader.m +29 -0
- package/ios/AppDelegates/EXLegacyAppDelegateWrapper.h +16 -0
- package/ios/{EXAppDelegateWrapper.m → AppDelegates/EXLegacyAppDelegateWrapper.m} +2 -2
- package/ios/AppDelegates/ExpoAppDelegate.swift +264 -0
- package/ios/AppDelegates/ExpoAppDelegateSubscriber.swift +24 -0
- package/ios/ExpoModulesCore.podspec +7 -2
- package/ios/JSI/ExpoModulesProxySpec.h +24 -0
- package/ios/JSI/ExpoModulesProxySpec.mm +135 -0
- package/ios/JSI/JSIConversions.h +42 -0
- package/ios/JSI/JSIConversions.mm +164 -0
- package/ios/JSI/JSIInstaller.h +19 -0
- package/ios/JSI/JSIInstaller.mm +22 -0
- package/ios/ModuleRegistryAdapter/EXModuleRegistryAdapter.m +1 -6
- package/ios/NativeModulesProxy/EXNativeModulesProxy.h +6 -0
- package/ios/NativeModulesProxy/{EXNativeModulesProxy.m → EXNativeModulesProxy.mm} +45 -12
- package/ios/Services/EXReactNativeEventEmitter.h +6 -0
- package/ios/Services/EXReactNativeEventEmitter.m +15 -0
- package/ios/Swift/AppContext.swift +14 -1
- package/ios/Swift/Arguments/AnyArgument.swift +14 -0
- package/ios/Swift/Arguments/AnyArgumentType.swift +13 -0
- package/ios/Swift/Arguments/ArgumentType.swift +24 -0
- package/ios/Swift/Arguments/ConvertibleArgument.swift +15 -0
- package/ios/Swift/Arguments/Convertibles.swift +93 -0
- package/ios/Swift/Arguments/Types/ArrayArgumentType.swift +42 -0
- package/ios/Swift/Arguments/Types/ConvertibleArgumentType.swift +16 -0
- package/ios/Swift/Arguments/Types/EnumArgumentType.swift +105 -0
- package/ios/Swift/Arguments/Types/OptionalArgumentType.swift +49 -0
- package/ios/Swift/Arguments/Types/PromiseArgumentType.swift +15 -0
- package/ios/Swift/Arguments/Types/RawArgumentType.swift +25 -0
- package/ios/Swift/Conversions.swift +199 -7
- package/ios/Swift/EventListener.swift +37 -5
- package/ios/Swift/Functions/AnyFunction.swift +42 -0
- package/ios/Swift/{Methods/ConcreteMethod.swift → Functions/ConcreteFunction.swift} +32 -34
- package/ios/Swift/ModuleHolder.swift +75 -20
- package/ios/Swift/ModuleRegistry.swift +19 -8
- package/ios/Swift/Modules/AnyModule.swift +8 -8
- package/ios/Swift/Modules/Module.swift +7 -0
- package/ios/Swift/Modules/ModuleDefinition.swift +52 -8
- package/ios/Swift/Modules/ModuleDefinitionBuilder.swift +1 -1
- package/ios/Swift/Modules/ModuleDefinitionComponents.swift +140 -52
- package/ios/Swift/ModulesProvider.swift +9 -0
- package/ios/Swift/Promise.swift +1 -1
- package/ios/Swift/Records/Field.swift +1 -1
- package/ios/Swift/Records/Record.swift +8 -1
- package/ios/Swift/SwiftInteropBridge.swift +45 -16
- package/ios/Swift/Views/AnyViewProp.swift +2 -2
- package/ios/Swift/Views/ConcreteViewProp.swift +37 -10
- package/ios/Swift/Views/ViewModuleWrapper.swift +9 -4
- package/ios/Swift.h +9 -0
- package/ios/Tests/ArgumentTypeSpec.swift +145 -0
- package/ios/Tests/ConvertiblesSpec.swift +231 -0
- package/ios/Tests/{MethodSpec.swift → FunctionSpec.swift} +69 -54
- package/ios/Tests/FunctionWithConvertiblesSpec.swift +66 -0
- package/ios/Tests/Mocks/ModuleMocks.swift +21 -7
- package/ios/Tests/ModuleEventListenersSpec.swift +17 -16
- package/ios/Tests/ModuleRegistrySpec.swift +4 -7
- package/package.json +3 -3
- package/src/NativeModulesProxy.native.ts +22 -2
- package/src/NativeModulesProxy.types.ts +8 -0
- package/ios/EXAppDelegateWrapper.h +0 -13
- package/ios/Swift/Methods/AnyArgumentType.swift +0 -48
- package/ios/Swift/Methods/AnyMethod.swift +0 -31
- 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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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 = {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
72
|
-
return nil
|
|
73
|
-
}
|
|
96
|
+
let expectedType = argumentType(atIndex: index)
|
|
74
97
|
|
|
75
|
-
//
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
26
|
+
return definition.name.isEmpty ? String(describing: type(of: module)) : definition.name
|
|
13
27
|
}
|
|
14
28
|
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
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
|
-
|
|
51
|
+
function.call(args: args, promise: promise)
|
|
28
52
|
}
|
|
29
|
-
}
|
|
30
|
-
promise.reject(
|
|
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(
|
|
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(
|
|
66
|
+
call(function: functionName, args: args, promise: promise)
|
|
41
67
|
}
|
|
42
68
|
|
|
43
|
-
|
|
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
|
|
72
|
-
let
|
|
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
|
|
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
|
|
14
|
+
Registers an instance of module holder.
|
|
15
15
|
*/
|
|
16
|
-
|
|
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
|
|
21
|
+
Registers a module by its type.
|
|
23
22
|
*/
|
|
24
|
-
public func register(
|
|
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(
|
|
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
|
}
|