expo-invoke 2.6.14 → 2.7.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/android/src/main/java/expo/modules/invoke/ExpoInvokeModule.kt +35 -0
- package/android/src/main/java/expo/modules/invoke/InvokeSliceProvider.kt +9 -0
- package/build/plugin/codegen/generateSwiftIntents.js +22 -0
- package/build/plugin/codegen/generateSwiftIntents.js.map +1 -1
- package/build/plugin/codegen/generateSwiftLiveActivities.d.ts +17 -0
- package/build/plugin/codegen/generateSwiftLiveActivities.js +165 -0
- package/build/plugin/codegen/generateSwiftLiveActivities.js.map +1 -0
- package/build/plugin/codegen/generateSwiftWidget.d.ts +5 -0
- package/build/plugin/codegen/generateSwiftWidget.js.map +1 -1
- package/build/plugin/ios/widgets/withAppGroupEntitlements.d.ts +6 -0
- package/build/plugin/ios/widgets/withAppGroupEntitlements.js +27 -0
- package/build/plugin/ios/widgets/withAppGroupEntitlements.js.map +1 -0
- package/build/plugin/ios/widgets/withAppInfoPlist.d.ts +7 -0
- package/build/plugin/ios/widgets/withAppInfoPlist.js +12 -0
- package/build/plugin/ios/widgets/withAppInfoPlist.js.map +1 -0
- package/build/plugin/ios/widgets/withEasConfig.d.ts +8 -0
- package/build/plugin/ios/widgets/withEasConfig.js +52 -0
- package/build/plugin/ios/widgets/withEasConfig.js.map +1 -0
- package/build/plugin/ios/widgets/withIosWarning.d.ts +7 -0
- package/build/plugin/ios/widgets/withIosWarning.js +10 -0
- package/build/plugin/ios/widgets/withIosWarning.js.map +1 -0
- package/build/plugin/ios/widgets/withIosWidgets.d.ts +11 -0
- package/build/plugin/ios/widgets/withIosWidgets.js +83 -0
- package/build/plugin/ios/widgets/withIosWidgets.js.map +1 -0
- package/build/plugin/ios/widgets/withPodsLinking.d.ts +6 -0
- package/build/plugin/ios/widgets/withPodsLinking.js +102 -0
- package/build/plugin/ios/widgets/withPodsLinking.js.map +1 -0
- package/build/plugin/ios/widgets/withPushNotifications.d.ts +6 -0
- package/build/plugin/ios/widgets/withPushNotifications.js +12 -0
- package/build/plugin/ios/widgets/withPushNotifications.js.map +1 -0
- package/build/plugin/ios/widgets/withWidgetSourceFiles.d.ts +10 -0
- package/build/plugin/ios/widgets/withWidgetSourceFiles.js +379 -0
- package/build/plugin/ios/widgets/withWidgetSourceFiles.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addBuildPhases.d.ts +12 -0
- package/build/plugin/ios/widgets/xcode/addBuildPhases.js +41 -0
- package/build/plugin/ios/widgets/xcode/addBuildPhases.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addPbxGroup.d.ts +5 -0
- package/build/plugin/ios/widgets/xcode/addPbxGroup.js +20 -0
- package/build/plugin/ios/widgets/xcode/addPbxGroup.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addProductFile.d.ts +7 -0
- package/build/plugin/ios/widgets/xcode/addProductFile.js +41 -0
- package/build/plugin/ios/widgets/xcode/addProductFile.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addTargetDependency.d.ts +4 -0
- package/build/plugin/ios/widgets/xcode/addTargetDependency.js +17 -0
- package/build/plugin/ios/widgets/xcode/addTargetDependency.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addToPbxNativeTargetSection.d.ts +14 -0
- package/build/plugin/ios/widgets/xcode/addToPbxNativeTargetSection.js +29 -0
- package/build/plugin/ios/widgets/xcode/addToPbxNativeTargetSection.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addToPbxProjectSection.d.ts +4 -0
- package/build/plugin/ios/widgets/xcode/addToPbxProjectSection.js +20 -0
- package/build/plugin/ios/widgets/xcode/addToPbxProjectSection.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addXCConfigurationList.d.ts +11 -0
- package/build/plugin/ios/widgets/xcode/addXCConfigurationList.js +37 -0
- package/build/plugin/ios/widgets/xcode/addXCConfigurationList.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/withTargetXcodeProject.d.ts +10 -0
- package/build/plugin/ios/widgets/xcode/withTargetXcodeProject.js +86 -0
- package/build/plugin/ios/widgets/xcode/withTargetXcodeProject.js.map +1 -0
- package/build/plugin/ios/withIOSInvoke.d.ts +6 -15
- package/build/plugin/ios/withIOSInvoke.js +54 -25
- package/build/plugin/ios/withIOSInvoke.js.map +1 -1
- package/build/plugin/ios/withWidgetExtension.d.ts +5 -0
- package/build/plugin/ios/withWidgetExtension.js +36 -6
- package/build/plugin/ios/withWidgetExtension.js.map +1 -1
- package/build/plugin/src/withInvoke.d.ts +34 -23
- package/build/plugin/src/withInvoke.js +16 -27
- package/build/plugin/src/withInvoke.js.map +1 -1
- package/build/plugin/types/WidgetConfig.type.d.ts +54 -0
- package/build/plugin/types/WidgetConfig.type.js +3 -0
- package/build/plugin/types/WidgetConfig.type.js.map +1 -0
- package/build/plugin/types/WidgetFamily.type.d.ts +10 -0
- package/build/plugin/types/WidgetFamily.type.js +13 -0
- package/build/plugin/types/WidgetFamily.type.js.map +1 -0
- package/build/src/hooks/useLiveActivity.d.ts +20 -10
- package/build/src/hooks/useLiveActivity.d.ts.map +1 -1
- package/build/src/hooks/useLiveActivity.js +20 -10
- package/build/src/hooks/useLiveActivity.js.map +1 -1
- package/build/src/index.d.ts +2 -0
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +5 -0
- package/build/src/index.js.map +1 -1
- package/build/src/module.d.ts +4 -0
- package/build/src/module.d.ts.map +1 -1
- package/build/src/module.js +8 -0
- package/build/src/module.js.map +1 -1
- package/build/src/web/index.d.ts +10 -1
- package/build/src/web/index.d.ts.map +1 -1
- package/build/src/web/index.js +4 -0
- package/build/src/web/index.js.map +1 -1
- package/build/src/widgets/ExpoWidgets.d.ts +12 -0
- package/build/src/widgets/ExpoWidgets.d.ts.map +1 -0
- package/build/src/widgets/ExpoWidgets.ios.d.ts +12 -0
- package/build/src/widgets/ExpoWidgets.ios.d.ts.map +1 -0
- package/build/src/widgets/ExpoWidgets.ios.js +3 -0
- package/build/src/widgets/ExpoWidgets.ios.js.map +1 -0
- package/build/src/widgets/ExpoWidgets.js +42 -0
- package/build/src/widgets/ExpoWidgets.js.map +1 -0
- package/build/src/widgets/Widgets.d.ts +119 -0
- package/build/src/widgets/Widgets.d.ts.map +1 -0
- package/build/src/widgets/Widgets.js +158 -0
- package/build/src/widgets/Widgets.js.map +1 -0
- package/build/src/widgets/Widgets.types.d.ts +263 -0
- package/build/src/widgets/Widgets.types.d.ts.map +1 -0
- package/build/src/widgets/Widgets.types.js +2 -0
- package/build/src/widgets/Widgets.types.js.map +1 -0
- package/build/src/widgets/index.d.ts +3 -0
- package/build/src/widgets/index.d.ts.map +1 -0
- package/build/src/widgets/index.js +2 -0
- package/build/src/widgets/index.js.map +1 -0
- package/bundle/__tests__/decorator.test.ts +89 -0
- package/bundle/decorator.ts +61 -0
- package/bundle/expo-modules-core-stub.ts +1 -0
- package/bundle/expo-stub.ts +15 -0
- package/bundle/index.ts +69 -0
- package/bundle/jsx-runtime-stub.ts +71 -0
- package/bundle/react-native-stub.ts +3 -0
- package/bundle/react-stub.ts +13 -0
- package/expo-module.config.json +1 -1
- package/ios/LiveActivity/LiveActivityManager.swift +15 -14
- package/ios/Module/ExpoInvokeModule.swift +124 -0
- package/ios/Widget/WidgetManager.swift +25 -8
- package/ios/Widgets/LiveActivity.swift +81 -0
- package/ios/Widgets/LiveActivityFactory.swift +49 -0
- package/ios/Widgets/Widget/AppIntent.swift +130 -0
- package/ios/Widgets/Widget/Buttons.swift +62 -0
- package/ios/Widgets/Widget/DynamicView.swift +161 -0
- package/ios/Widgets/Widget/EntryView.swift +36 -0
- package/ios/Widgets/Widget/Fragment.swift +13 -0
- package/ios/Widgets/Widget/LiveActivityBanner.swift +19 -0
- package/ios/Widgets/Widget/RedBoxView.swift +83 -0
- package/ios/Widgets/Widget/TimelineEntry.swift +8 -0
- package/ios/Widgets/Widget/TimelineProvider.swift +45 -0
- package/ios/Widgets/Widget/Type.swift +6 -0
- package/ios/Widgets/Widget/Utils.swift +116 -0
- package/ios/Widgets/Widget/WidgetLiveActivity.swift +104 -0
- package/ios/Widgets/Widget/WidgetsJSRuntime.swift +148 -0
- package/ios/Widgets/WidgetObject.swift +31 -0
- package/ios/Widgets/WidgetsContext.swift +7 -0
- package/ios/Widgets/WidgetsEvents.swift +16 -0
- package/ios/Widgets/WidgetsExceptions.swift +25 -0
- package/ios/Widgets/WidgetsModule.swift +149 -0
- package/ios/Widgets/WidgetsRecords.swift +22 -0
- package/ios/Widgets/WidgetsStorage.swift +58 -0
- package/metro.widget.config.js +76 -0
- package/package.json +11 -1
- package/scripts/autolinking.rb +34 -0
- package/scripts/build-bundle.mjs +69 -0
- package/scripts/with-node.sh +44 -0
- package/scripts/xcode-build-bundle.sh +23 -0
- package/spm.config.json +40 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import JavaScriptCore
|
|
3
|
+
|
|
4
|
+
struct WidgetJavaScriptError: Error {
|
|
5
|
+
let message: String
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
typealias WidgetJavaScriptResult<Value> = Result<Value, WidgetJavaScriptError>
|
|
9
|
+
|
|
10
|
+
private final class WidgetsJSRuntime {
|
|
11
|
+
static let shared = WidgetsJSRuntime()
|
|
12
|
+
|
|
13
|
+
private let lock = NSRecursiveLock()
|
|
14
|
+
private var context: JSContext?
|
|
15
|
+
private var bundleScript: String?
|
|
16
|
+
private var layoutCache: [String: JSValue] = [:]
|
|
17
|
+
|
|
18
|
+
private init() {}
|
|
19
|
+
|
|
20
|
+
func render(layout: String, props: [String: Any]?, environment: [String: Any]) -> WidgetJavaScriptResult<[String: Any]> {
|
|
21
|
+
call(layout: layout, functionName: "__expoWidgetRender") { context in
|
|
22
|
+
[props.map { $0 } ?? JSValue(undefinedIn: context) as Any, environment]
|
|
23
|
+
}.flatMap { result in
|
|
24
|
+
guard let renderedNode = result?.toObject() as? [String: Any] else {
|
|
25
|
+
return .failure(WidgetJavaScriptError(message: "Expo widget render did not produce any results."))
|
|
26
|
+
}
|
|
27
|
+
return .success(renderedNode)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func handlePress(layout: String, props: [String: Any], environment: [String: Any]) -> WidgetJavaScriptResult<[String: Any]?> {
|
|
32
|
+
call(layout: layout, functionName: "__expoWidgetHandlePress") { _ in [props, environment] }
|
|
33
|
+
.map { $0?.toObject() as? [String: Any] }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private func call(
|
|
37
|
+
layout: String,
|
|
38
|
+
functionName: String,
|
|
39
|
+
arguments: (JSContext) -> [Any]
|
|
40
|
+
) -> WidgetJavaScriptResult<JSValue?> {
|
|
41
|
+
lock.lock()
|
|
42
|
+
defer { lock.unlock() }
|
|
43
|
+
|
|
44
|
+
guard let context = getContext() else {
|
|
45
|
+
return .failure(WidgetJavaScriptError(message: "Could not create context for layout evaluation."))
|
|
46
|
+
}
|
|
47
|
+
guard let layoutValue = getLayoutValue(layout, in: context) else {
|
|
48
|
+
return .failure(WidgetJavaScriptError(message: contextExceptionMessage(context) ?? "Could not evaluate layout."))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
context.exception = nil
|
|
52
|
+
context.setObject(layoutValue, forKeyedSubscript: "__expoWidgetLayout" as NSString)
|
|
53
|
+
|
|
54
|
+
let function = context.objectForKeyedSubscript(functionName)
|
|
55
|
+
guard let function, function.isObject else {
|
|
56
|
+
return .failure(WidgetJavaScriptError(message: "Expo widget runtime function \(functionName) is unavailable."))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let result = function.call(withArguments: arguments(context))
|
|
60
|
+
if let exceptionMessage = contextExceptionMessage(context) {
|
|
61
|
+
return .failure(WidgetJavaScriptError(message: exceptionMessage))
|
|
62
|
+
}
|
|
63
|
+
return .success(result)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private func getContext() -> JSContext? {
|
|
67
|
+
if let context {
|
|
68
|
+
return context
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
guard let context = JSContext() else {
|
|
72
|
+
return nil
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
guard let script = getBundleScript() else {
|
|
76
|
+
print("[ExpoWidgets] Missing ExpoWidgets.bundle")
|
|
77
|
+
return nil
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
context.evaluateScript(script)
|
|
81
|
+
if let exceptionMessage = contextExceptionMessage(context) {
|
|
82
|
+
print("[ExpoWidgets] Bundle evaluation failed: \(exceptionMessage)")
|
|
83
|
+
return nil
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
self.context = context
|
|
87
|
+
return context
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private func getBundleScript() -> String? {
|
|
91
|
+
if let bundleScript {
|
|
92
|
+
return bundleScript
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
guard let bundleURL = Bundle.main.url(forResource: "ExpoWidgets", withExtension: "bundle"),
|
|
96
|
+
let bundle = Bundle(url: bundleURL),
|
|
97
|
+
let url = bundle.url(forResource: "ExpoWidgets", withExtension: "bundle"),
|
|
98
|
+
let script = try? String(contentsOf: url, encoding: .utf8) else {
|
|
99
|
+
return nil
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
bundleScript = script
|
|
103
|
+
return script
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private func getLayoutValue(_ layout: String, in context: JSContext) -> JSValue? {
|
|
107
|
+
if let layoutValue = layoutCache[layout] {
|
|
108
|
+
return layoutValue
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
context.exception = nil
|
|
112
|
+
guard let layoutValue = context.evaluateScript("(\(layout))"),
|
|
113
|
+
!layoutValue.isUndefined else {
|
|
114
|
+
return nil
|
|
115
|
+
}
|
|
116
|
+
guard context.exception == nil else {
|
|
117
|
+
return nil
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
layoutCache[layout] = layoutValue
|
|
121
|
+
return layoutValue
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private func contextExceptionMessage(_ context: JSContext) -> String? {
|
|
125
|
+
guard let exception = context.exception else {
|
|
126
|
+
return nil
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
context.exception = nil
|
|
130
|
+
return exception.toString() ?? "Unknown JavaScript exception."
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func evaluateWidgetLayout(
|
|
135
|
+
layout: String,
|
|
136
|
+
props: [String: Any]?,
|
|
137
|
+
environment: [String: Any]
|
|
138
|
+
) -> WidgetJavaScriptResult<[String: Any]> {
|
|
139
|
+
WidgetsJSRuntime.shared.render(layout: layout, props: props, environment: environment)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
func evaluateWidgetButtonPress(
|
|
143
|
+
layout: String,
|
|
144
|
+
props: [String: Any],
|
|
145
|
+
environment: [String: Any]
|
|
146
|
+
) -> WidgetJavaScriptResult<[String: Any]?> {
|
|
147
|
+
WidgetsJSRuntime.shared.handlePress(layout: layout, props: props, environment: environment)
|
|
148
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import WidgetKit
|
|
3
|
+
|
|
4
|
+
final class WidgetObject: SharedObject {
|
|
5
|
+
let name: String
|
|
6
|
+
init(name: String, layout: String) {
|
|
7
|
+
self.name = name
|
|
8
|
+
WidgetsStorage.set(layout, forKey: "__expo_widgets_\(name)_layout")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
func reload() {
|
|
12
|
+
WidgetCenter.shared.reloadTimelines(ofKind: name)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func updateTimeline(entries: [WidgetsJSTimelineEntry]) throws {
|
|
16
|
+
if WidgetsStorage.getString(forKey: "__expo_widgets_\(name)_layout") == nil {
|
|
17
|
+
throw UpdatedTimelineWithoutLayout(name)
|
|
18
|
+
}
|
|
19
|
+
WidgetsStorage.set(entries.map { $0.toDictionary() }, forKey: "__expo_widgets_\(name)_timeline")
|
|
20
|
+
|
|
21
|
+
self.reload()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func getTimeline() throws -> [WidgetsJSTimelineEntry] {
|
|
25
|
+
guard let entries = WidgetsStorage.getArray(forKey: "__expo_widgets_\(name)_timeline") as? [[String: Any]],
|
|
26
|
+
let appContext else {
|
|
27
|
+
return []
|
|
28
|
+
}
|
|
29
|
+
return try entries.map { try WidgetsJSTimelineEntry(from: $0, appContext: appContext) }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
public enum WidgetsEventType: String {
|
|
2
|
+
case userEvent = "ExpoWidgetsUserInteraction"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
public class WidgetsEvents {
|
|
6
|
+
public static let shared = WidgetsEvents()
|
|
7
|
+
|
|
8
|
+
private init() {}
|
|
9
|
+
|
|
10
|
+
public func sendNotification(type: WidgetsEventType, data: [String: Any]) {
|
|
11
|
+
var eventData = data
|
|
12
|
+
eventData["type"] = type.rawValue
|
|
13
|
+
let userInfo = ["eventData": eventData] as [String: Any]
|
|
14
|
+
NotificationCenter.default.post(name: onUserInteractionNotification, object: nil, userInfo: userInfo)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
internal final class LiveActivitiesNotSupportedException: Exception, @unchecked Sendable {
|
|
4
|
+
override var reason: String {
|
|
5
|
+
"Live Activities are not supported on this device"
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
internal final class StartLiveActivityException: GenericException<String>, @unchecked Sendable {
|
|
10
|
+
override var reason: String {
|
|
11
|
+
"Failed to start live activity: \(param)"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
internal final class LiveActivityNotFoundException: GenericException<String>, @unchecked Sendable {
|
|
16
|
+
override var reason: String {
|
|
17
|
+
"Can't find live activity with id: \(param)"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
internal final class UpdatedTimelineWithoutLayout: GenericException<String>, @unchecked Sendable {
|
|
22
|
+
override var reason: String {
|
|
23
|
+
"Cannot update widget timeline without a layout, first register a layout for: \(param)"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import ActivityKit
|
|
3
|
+
import WidgetKit
|
|
4
|
+
|
|
5
|
+
let pushNotificationsEnabledKey: String = "ExpoWidgets_EnablePushNotifications"
|
|
6
|
+
|
|
7
|
+
let onUserInteraction = "onExpoWidgetsUserInteraction"
|
|
8
|
+
let onPushToStartTokenReceived = "onExpoWidgetsPushToStartTokenReceived"
|
|
9
|
+
let onTokenReceived = "onExpoWidgetsTokenReceived"
|
|
10
|
+
let onUserInteractionNotification = Notification.Name(onUserInteraction)
|
|
11
|
+
|
|
12
|
+
public final class WidgetsModule: Module {
|
|
13
|
+
var pushToStartTokenObserverTask: Task<Void, Never>?
|
|
14
|
+
|
|
15
|
+
public func definition() -> ModuleDefinition {
|
|
16
|
+
Name("ExpoWidgets")
|
|
17
|
+
|
|
18
|
+
Events(onPushToStartTokenReceived, onTokenReceived, onUserInteraction)
|
|
19
|
+
|
|
20
|
+
OnStartObserving(onUserInteraction) {
|
|
21
|
+
NotificationCenter.default.addObserver(
|
|
22
|
+
self,
|
|
23
|
+
selector: #selector(handleUserInteractionNotification),
|
|
24
|
+
name: onUserInteractionNotification,
|
|
25
|
+
object: nil
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
OnStopObserving(onUserInteraction) {
|
|
30
|
+
NotificationCenter.default.removeObserver(
|
|
31
|
+
self,
|
|
32
|
+
name: onUserInteractionNotification,
|
|
33
|
+
object: nil
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
OnStartObserving(onPushToStartTokenReceived) {
|
|
38
|
+
if pushNotificationsEnabled {
|
|
39
|
+
observePushToStartToken()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
OnStopObserving(onPushToStartTokenReceived) {
|
|
44
|
+
pushToStartTokenObserverTask?.cancel()
|
|
45
|
+
pushToStartTokenObserverTask = nil
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Constant("widgetsDirectory") { () -> String? in
|
|
49
|
+
guard let appGroupIdentifier = WidgetsStorage.appGroupIdentifier,
|
|
50
|
+
let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else {
|
|
51
|
+
return nil
|
|
52
|
+
}
|
|
53
|
+
let directoryUrl = containerUrl.appendingPathComponent("ExpoWidgets", isDirectory: true)
|
|
54
|
+
do {
|
|
55
|
+
try FileManager.default.createDirectory(at: directoryUrl, withIntermediateDirectories: true)
|
|
56
|
+
return directoryUrl.absoluteString
|
|
57
|
+
} catch {
|
|
58
|
+
return nil
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Function("reloadAllWidgets") {
|
|
63
|
+
WidgetCenter.shared.reloadAllTimelines()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Class("Widget", WidgetObject.self) {
|
|
67
|
+
Constructor { (name: String, layout: String) in
|
|
68
|
+
WidgetObject(name: name, layout: layout)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
Function("reload") { (widget: WidgetObject) in
|
|
72
|
+
widget.reload()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
Function("updateTimeline") { (widget: WidgetObject, entries: [WidgetsJSTimelineEntry]) in
|
|
76
|
+
try widget.updateTimeline(entries: entries)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Function("getTimeline") { (widget: WidgetObject) in
|
|
80
|
+
try widget.getTimeline()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
Class("LiveActivityFactory", LiveActivityFactory.self) {
|
|
85
|
+
Constructor { (name: String, layout: String) in
|
|
86
|
+
LiveActivityFactory(name: name, layout: layout)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
Function("start") { (liveActivity: LiveActivityFactory, props: String?, url: URL?) in
|
|
90
|
+
try liveActivity.start(props: props, url: url)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Function("getInstances") { (liveActivity: LiveActivityFactory) in
|
|
94
|
+
try liveActivity.getInstances()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Class("LiveActivity", LiveActivity.self) {
|
|
99
|
+
AsyncFunction("update") { (instance: LiveActivity, props: String?) in
|
|
100
|
+
try await instance.update(props: props)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
AsyncFunction("end") { (instance: LiveActivity, dismissalPolicy: LiveActivityDismissalPolicy?, afterDate: Date?, props: String?, contentDate: Date?) in
|
|
104
|
+
try await instance.end(dismissalPolicy: dismissalPolicy, afterDate: afterDate, props: props, contentDate: contentDate)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
AsyncFunction("getPushToken") { (instance: LiveActivity) in
|
|
108
|
+
try instance.getPushToken()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@objc func handleUserInteractionNotification(_ notification: Notification) {
|
|
114
|
+
guard let userInfo = notification.userInfo as? [String: Any],
|
|
115
|
+
let eventData = userInfo["eventData"] as? [String: Any]
|
|
116
|
+
else { return }
|
|
117
|
+
self.sendEvent(onUserInteraction, eventData)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private func sendPushToStartToken(activityPushToStartToken: String) {
|
|
121
|
+
sendEvent(
|
|
122
|
+
onPushToStartTokenReceived,
|
|
123
|
+
[
|
|
124
|
+
"activityPushToStartToken": activityPushToStartToken
|
|
125
|
+
]
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private func observePushToStartToken() {
|
|
130
|
+
guard #available(iOS 17.2, *), ActivityAuthorizationInfo().areActivitiesEnabled else { return }
|
|
131
|
+
pushToStartTokenObserverTask = Task {
|
|
132
|
+
let initialToken = (Activity<LiveActivityAttributes>.pushToStartToken?.reduce("") { $0 + String(format: "%02x", $1) })
|
|
133
|
+
if let initialToken {
|
|
134
|
+
sendPushToStartToken(activityPushToStartToken: initialToken)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for await data in Activity<LiveActivityAttributes>.pushToStartTokenUpdates {
|
|
138
|
+
let token = data.reduce("") { $0 + String(format: "%02x", $1) }
|
|
139
|
+
if token != initialToken {
|
|
140
|
+
sendPushToStartToken(activityPushToStartToken: token)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private var pushNotificationsEnabled: Bool {
|
|
147
|
+
Bundle.main.object(forInfoDictionaryKey: pushNotificationsEnabledKey) as? Bool ?? false
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import ActivityKit
|
|
3
|
+
|
|
4
|
+
struct WidgetsJSTimelineEntry: Record {
|
|
5
|
+
@Field var timestamp: Int
|
|
6
|
+
@Field var props: [String: Any] = [:]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
internal enum LiveActivityDismissalPolicy: String, Enumerable {
|
|
10
|
+
case `default`
|
|
11
|
+
case immediate
|
|
12
|
+
case after
|
|
13
|
+
|
|
14
|
+
@available(iOS 16.1, *)
|
|
15
|
+
internal func toDismissalPolicy(date: Date?) -> ActivityUIDismissalPolicy {
|
|
16
|
+
return switch self {
|
|
17
|
+
case .default: .default
|
|
18
|
+
case .immediate: .immediate
|
|
19
|
+
case .after: .after(date ?? Date.now)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
public enum WidgetsStorage {
|
|
2
|
+
public static var appGroupIdentifier: String? = Bundle.main.object(forInfoDictionaryKey: "ExpoWidgetsAppGroupIdentifier") as? String
|
|
3
|
+
static let defaults = UserDefaults(suiteName: appGroupIdentifier)
|
|
4
|
+
|
|
5
|
+
static func set(_ value: [String: Any], forKey key: String) {
|
|
6
|
+
guard let defaults else { return }
|
|
7
|
+
|
|
8
|
+
defaults.set(value, forKey: key)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static func set(_ value: [[String: Any]], forKey key: String) {
|
|
12
|
+
guard let defaults else { return }
|
|
13
|
+
|
|
14
|
+
defaults.set(value, forKey: key)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static func set(_ value: String, forKey key: String) {
|
|
18
|
+
guard let defaults else { return }
|
|
19
|
+
|
|
20
|
+
defaults.set(value, forKey: key)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static func set(_ value: Data, forKey key: String) {
|
|
24
|
+
guard let defaults else { return }
|
|
25
|
+
|
|
26
|
+
defaults.set(value, forKey: key)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static func getDictionary(forKey key: String) -> [String: Any]? {
|
|
30
|
+
guard let defaults else { return nil }
|
|
31
|
+
|
|
32
|
+
return defaults.dictionary(forKey: key)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static func getArray(forKey key: String) -> [Any]? {
|
|
36
|
+
guard let defaults else { return nil }
|
|
37
|
+
|
|
38
|
+
return defaults.array(forKey: key)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public static func getData(forKey key: String) -> Data? {
|
|
42
|
+
guard let defaults else { return nil }
|
|
43
|
+
|
|
44
|
+
return defaults.data(forKey: key)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public static func getString(forKey key: String) -> String? {
|
|
48
|
+
guard let defaults else { return nil }
|
|
49
|
+
|
|
50
|
+
return defaults.string(forKey: key)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static func removeObject(forKey key: String) {
|
|
54
|
+
guard let defaults else { return }
|
|
55
|
+
|
|
56
|
+
defaults.removeObject(forKey: key)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const { getDefaultConfig } = require('expo/metro-config');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { resolveWorkspaceRoot } = require('resolve-workspace-root');
|
|
4
|
+
|
|
5
|
+
const projectRoot = process.cwd();
|
|
6
|
+
const config = getDefaultConfig(projectRoot);
|
|
7
|
+
|
|
8
|
+
const expoStubPath = path.resolve(__dirname, './bundle/expo-stub.ts');
|
|
9
|
+
const expoModulesCoreStubPath = path.resolve(__dirname, './bundle/expo-modules-core-stub.ts');
|
|
10
|
+
const reactStubPath = path.resolve(__dirname, './bundle/react-stub.ts');
|
|
11
|
+
const reactNativeStubPath = path.resolve(__dirname, './bundle/react-native-stub.ts');
|
|
12
|
+
const jsxRuntimeStubPath = path.resolve(__dirname, './bundle/jsx-runtime-stub.ts');
|
|
13
|
+
|
|
14
|
+
// The `projectRoot` won't be included by default, since we alter it to be `__dirname`
|
|
15
|
+
// to bundle from `expo-widgets` as the main module
|
|
16
|
+
// NOTE: We check the `watchFolders` to start with `projectRoot`, since `expo/metro-config`
|
|
17
|
+
// might add folders if we're in a monorepo
|
|
18
|
+
const watchFolders = config.watchFolders;
|
|
19
|
+
if (!watchFolders.some((entry) => !entry.startsWith(projectRoot))) {
|
|
20
|
+
watchFolders.push(projectRoot);
|
|
21
|
+
}
|
|
22
|
+
// If expo-widgets is outside the user's project (e.g., symlinked, in a different monorepo,
|
|
23
|
+
// or as an installed dependency), add its workspace root so Metro can resolve its dependencies.
|
|
24
|
+
const rel = path.relative(projectRoot, __dirname);
|
|
25
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
26
|
+
const widgetWorkspaceRoot = resolveWorkspaceRoot(__dirname);
|
|
27
|
+
if (widgetWorkspaceRoot && !watchFolders.includes(widgetWorkspaceRoot)) {
|
|
28
|
+
watchFolders.push(widgetWorkspaceRoot);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const buildConfig = {
|
|
33
|
+
...config,
|
|
34
|
+
projectRoot: __dirname, // Override root to be expo-widgets
|
|
35
|
+
watchFolders,
|
|
36
|
+
resolver: {
|
|
37
|
+
...config.resolver,
|
|
38
|
+
resolveRequest(context, moduleName, platform) {
|
|
39
|
+
const fileSpecifierRe = /^[\\/]|^\.\.?(?:$|[\\/])/i;
|
|
40
|
+
if (fileSpecifierRe.test(moduleName)) {
|
|
41
|
+
return context.resolveRequest(context, moduleName, platform);
|
|
42
|
+
}
|
|
43
|
+
switch (moduleName) {
|
|
44
|
+
case 'expo':
|
|
45
|
+
return { type: 'sourceFile', filePath: expoStubPath };
|
|
46
|
+
case 'expo-modules-core':
|
|
47
|
+
return { type: 'sourceFile', filePath: expoModulesCoreStubPath };
|
|
48
|
+
case 'react':
|
|
49
|
+
return { type: 'sourceFile', filePath: reactStubPath };
|
|
50
|
+
case 'react/jsx-runtime':
|
|
51
|
+
case 'react/jsx-dev-runtime':
|
|
52
|
+
return { type: 'sourceFile', filePath: jsxRuntimeStubPath };
|
|
53
|
+
case 'react-native':
|
|
54
|
+
return { type: 'sourceFile', filePath: reactNativeStubPath };
|
|
55
|
+
case 'react-native-worklets':
|
|
56
|
+
case 'react-native-reanimated':
|
|
57
|
+
return { type: 'empty' };
|
|
58
|
+
default:
|
|
59
|
+
return context.resolveRequest(context, moduleName, platform);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
transformer: {
|
|
64
|
+
...config.transformer,
|
|
65
|
+
enableBabelRCLookup: false,
|
|
66
|
+
getTransformOptions: async () => ({
|
|
67
|
+
transform: { experimentalImportSupport: false, inlineRequires: false },
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
serializer: {
|
|
71
|
+
...config.serializer,
|
|
72
|
+
getPolyfills: () => [],
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
module.exports = buildConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-invoke",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "The complete native-surface-to-JS bridge for Expo. One intent config → Siri, Google Assistant, home screen widgets, Dynamic Island, app icon menus, notification actions, NFC, QR, deep links and more — all through a single useInvoke() hook.",
|
|
5
5
|
"main": "build/src/index.js",
|
|
6
6
|
"module": "build/src/index.js",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"ios",
|
|
25
25
|
"android/src",
|
|
26
26
|
"android/build.gradle",
|
|
27
|
+
"bundle",
|
|
28
|
+
"scripts",
|
|
29
|
+
"metro.widget.config.js",
|
|
30
|
+
"spm.config.json",
|
|
27
31
|
"expo-module.config.json",
|
|
28
32
|
"README.md",
|
|
29
33
|
"MIGRATION.md",
|
|
@@ -77,6 +81,7 @@
|
|
|
77
81
|
},
|
|
78
82
|
"peerDependencies": {
|
|
79
83
|
"@expo/config-plugins": ">=7.0.0",
|
|
84
|
+
"@expo/ui": ">=56.0.0",
|
|
80
85
|
"expo": ">=50.0.0",
|
|
81
86
|
"expo-modules-core": ">=1.12.0",
|
|
82
87
|
"react": ">=18.0.0",
|
|
@@ -86,6 +91,9 @@
|
|
|
86
91
|
"@expo/config-plugins": {
|
|
87
92
|
"optional": false
|
|
88
93
|
},
|
|
94
|
+
"@expo/ui": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
89
97
|
"expo-modules-core": {
|
|
90
98
|
"optional": true
|
|
91
99
|
},
|
|
@@ -94,6 +102,8 @@
|
|
|
94
102
|
}
|
|
95
103
|
},
|
|
96
104
|
"devDependencies": {
|
|
105
|
+
"@expo/plist": "^0.7.0",
|
|
106
|
+
"@expo/spawn-async": "^1.7.2",
|
|
97
107
|
"@types/jest": "^29.5.14",
|
|
98
108
|
"@types/node": "^20.19.41",
|
|
99
109
|
"@types/react": "^19.2.16",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
require 'colored2' # dependency of CocoaPods
|
|
4
|
+
|
|
5
|
+
require File.join(File.dirname(`node --print "require.resolve('expo-modules-autolinking/package.json', { paths: ['#{__dir__}'] })"`), "scripts/ios/autolinking_manager")
|
|
6
|
+
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
|
|
7
|
+
|
|
8
|
+
def use_expo_modules_widgets!(options = {})
|
|
9
|
+
output = Expo::AutolinkingManager.new(self, @current_target_definition, options).resolve
|
|
10
|
+
|
|
11
|
+
all_packages = output["modules"].map { |mod| mod["packageName"] }
|
|
12
|
+
used_packages = ["expo", "expo-invoke", "@expo/ui"]
|
|
13
|
+
packages_to_exclude = all_packages - used_packages
|
|
14
|
+
|
|
15
|
+
options[:exclude] = packages_to_exclude
|
|
16
|
+
use_expo_modules!(options)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def use_expo_native_module!(config_command = $default_command)
|
|
20
|
+
config = list_native_modules!(config_command)
|
|
21
|
+
config[:ios_packages].select! { |package| package[:name] == "expo" }
|
|
22
|
+
|
|
23
|
+
return link_native_modules!(config)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def expo_widgets_post_install(installer)
|
|
27
|
+
installer.pods_project.targets.each do |target|
|
|
28
|
+
if target.name == 'ExpoModulesCore' || target.name == 'ExpoUI' || target.name.start_with?('React-')
|
|
29
|
+
target.build_configurations.each do |config|
|
|
30
|
+
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'No'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Build the ExpoWidgets.bundle.
|
|
4
|
+
// Use `yarn build:bundle` to run this script.
|
|
5
|
+
|
|
6
|
+
import spawn from '@expo/spawn-async';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { argv } from 'node:process';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
// NOTE: Modified from expo-constants/expo-updates entrypoint. We cd into the right folder anyway
|
|
14
|
+
const [, , possibleProjectRoot = process.cwd(), platform = 'ios', bundleOutput] = argv;
|
|
15
|
+
|
|
16
|
+
// TODO: Verify we can remove projectRoot validation, now that we no longer
|
|
17
|
+
// support React Native <= 62
|
|
18
|
+
let projectRoot;
|
|
19
|
+
if (fs.existsSync(path.join(possibleProjectRoot, 'package.json'))) {
|
|
20
|
+
projectRoot = possibleProjectRoot;
|
|
21
|
+
} else if (fs.existsSync(path.join(possibleProjectRoot, '..', 'package.json'))) {
|
|
22
|
+
projectRoot = path.resolve(possibleProjectRoot, '..');
|
|
23
|
+
} else {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Unable to locate project (no package.json found) at path: ${possibleProjectRoot}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
30
|
+
const __dirname = path.dirname(__filename);
|
|
31
|
+
const require = createRequire(__dirname);
|
|
32
|
+
|
|
33
|
+
// NODE_BINARY is set for Xcode builds via the `with-node.sh` script.
|
|
34
|
+
const nodePath = process.env.NODE_BINARY || 'node';
|
|
35
|
+
const outputDir = path.join(__dirname, '../bundle/build');
|
|
36
|
+
const defaultBundleOutput = path.join(outputDir, 'ExpoWidgets.bundle');
|
|
37
|
+
const appBundlePath = path.resolve(projectRoot, bundleOutput ?? defaultBundleOutput);
|
|
38
|
+
|
|
39
|
+
await fs.promises.rm(appBundlePath, { recursive: true, force: true });
|
|
40
|
+
await fs.promises.mkdir(path.dirname(appBundlePath), { recursive: true });
|
|
41
|
+
|
|
42
|
+
const result = await spawn(
|
|
43
|
+
nodePath,
|
|
44
|
+
[
|
|
45
|
+
require.resolve('expo/bin/cli'),
|
|
46
|
+
'export:embed',
|
|
47
|
+
'--platform',
|
|
48
|
+
platform,
|
|
49
|
+
'--bundle-output',
|
|
50
|
+
appBundlePath,
|
|
51
|
+
'--entry-file',
|
|
52
|
+
path.join(__dirname, '../bundle/index.ts'),
|
|
53
|
+
'--dev',
|
|
54
|
+
'false',
|
|
55
|
+
'--skip-server',
|
|
56
|
+
],
|
|
57
|
+
{
|
|
58
|
+
stdio: 'inherit',
|
|
59
|
+
cwd: projectRoot,
|
|
60
|
+
env: {
|
|
61
|
+
...process.env,
|
|
62
|
+
EXPO_OVERRIDE_METRO_CONFIG: path.join(__dirname, '../metro.widget.config.js'),
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (result.error) {
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|