expo-invoke 1.2.0 → 2.0.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/InteractiveWidgetReceiver.kt +47 -0
- package/android/src/main/java/expo/modules/invoke/InvokeSliceProvider.kt +83 -0
- package/android/src/main/java/expo/modules/invoke/ShortcutsHelper.kt +38 -32
- package/build/plugin/android/withAndroidInvoke.d.ts +3 -1
- package/build/plugin/android/withAndroidInvoke.js +71 -15
- package/build/plugin/android/withAndroidInvoke.js.map +1 -1
- package/build/plugin/codegen/generateSwiftAppClip.d.ts +2 -0
- package/build/plugin/codegen/generateSwiftAppClip.js +20 -0
- package/build/plugin/codegen/generateSwiftAppClip.js.map +1 -0
- package/build/plugin/codegen/generateSwiftEntities.d.ts +2 -0
- package/build/plugin/codegen/generateSwiftEntities.js +91 -0
- package/build/plugin/codegen/generateSwiftEntities.js.map +1 -0
- package/build/plugin/codegen/generateSwiftFocusFilters.d.ts +2 -0
- package/build/plugin/codegen/generateSwiftFocusFilters.js +42 -0
- package/build/plugin/codegen/generateSwiftFocusFilters.js.map +1 -0
- package/build/plugin/codegen/generateSwiftIntents.d.ts +2 -0
- package/build/plugin/codegen/generateSwiftIntents.js +43 -13
- package/build/plugin/codegen/generateSwiftIntents.js.map +1 -1
- package/build/plugin/ios/withIOSInvoke.d.ts +8 -1
- package/build/plugin/ios/withIOSInvoke.js +61 -10
- package/build/plugin/ios/withIOSInvoke.js.map +1 -1
- package/build/plugin/src/withInvoke.d.ts +20 -3
- package/build/plugin/src/withInvoke.js +23 -4
- package/build/plugin/src/withInvoke.js.map +1 -1
- package/build/src/components/AddToSiriButton.d.ts +1 -1
- package/build/src/components/AddToSiriButton.d.ts.map +1 -1
- package/build/src/components/InvokeDebugger.d.ts +1 -1
- package/build/src/components/InvokeDebugger.d.ts.map +1 -1
- package/build/src/components/ShortcutChip.d.ts +1 -1
- package/build/src/components/ShortcutChip.d.ts.map +1 -1
- package/build/src/hooks/useBackgroundIntent.d.ts +15 -0
- package/build/src/hooks/useBackgroundIntent.d.ts.map +1 -0
- package/build/src/hooks/useBackgroundIntent.js +41 -0
- package/build/src/hooks/useBackgroundIntent.js.map +1 -0
- package/build/src/hooks/useEntityQuery.d.ts +26 -0
- package/build/src/hooks/useEntityQuery.d.ts.map +1 -0
- package/build/src/hooks/useEntityQuery.js +47 -0
- package/build/src/hooks/useEntityQuery.js.map +1 -0
- package/build/src/hooks/useFocusFilter.d.ts +1 -1
- package/build/src/hooks/useFocusFilter.d.ts.map +1 -1
- package/build/src/hooks/useFocusFilter.js.map +1 -1
- package/build/src/hooks/useFocusFilterState.d.ts +28 -0
- package/build/src/hooks/useFocusFilterState.d.ts.map +1 -0
- package/build/src/hooks/useFocusFilterState.js +47 -0
- package/build/src/hooks/useFocusFilterState.js.map +1 -0
- package/build/src/hooks/useInvoke.d.ts.map +1 -1
- package/build/src/hooks/useInvoke.js +3 -0
- package/build/src/hooks/useInvoke.js.map +1 -1
- package/build/src/hooks/useInvokeAnalytics.d.ts +10 -14
- package/build/src/hooks/useInvokeAnalytics.d.ts.map +1 -1
- package/build/src/hooks/useInvokeAnalytics.js +21 -14
- package/build/src/hooks/useInvokeAnalytics.js.map +1 -1
- package/build/src/hooks/useInvokeQueue.d.ts +32 -0
- package/build/src/hooks/useInvokeQueue.d.ts.map +1 -0
- package/build/src/hooks/useInvokeQueue.js +77 -0
- package/build/src/hooks/useInvokeQueue.js.map +1 -0
- package/build/src/hooks/useLiveActivity.d.ts +37 -0
- package/build/src/hooks/useLiveActivity.d.ts.map +1 -0
- package/build/src/hooks/useLiveActivity.js +74 -0
- package/build/src/hooks/useLiveActivity.js.map +1 -0
- package/build/src/hooks/useOTAConfig.d.ts +24 -0
- package/build/src/hooks/useOTAConfig.d.ts.map +1 -0
- package/build/src/hooks/useOTAConfig.js +40 -0
- package/build/src/hooks/useOTAConfig.js.map +1 -0
- package/build/src/hooks/usePushToTalk.d.ts +35 -0
- package/build/src/hooks/usePushToTalk.d.ts.map +1 -0
- package/build/src/hooks/usePushToTalk.js +101 -0
- package/build/src/hooks/usePushToTalk.js.map +1 -0
- package/build/src/hooks/useSlice.d.ts +19 -0
- package/build/src/hooks/useSlice.d.ts.map +1 -0
- package/build/src/hooks/useSlice.js +22 -0
- package/build/src/hooks/useSlice.js.map +1 -0
- package/build/src/index.d.ts +19 -3
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +16 -1
- package/build/src/index.js.map +1 -1
- package/build/src/module.d.ts +18 -3
- package/build/src/module.d.ts.map +1 -1
- package/build/src/module.js +77 -0
- package/build/src/module.js.map +1 -1
- package/build/src/types/index.d.ts +117 -4
- package/build/src/types/index.d.ts.map +1 -1
- package/build/src/utils/analytics.d.ts +1 -7
- package/build/src/utils/analytics.d.ts.map +1 -1
- package/build/src/utils/analytics.js +9 -0
- package/build/src/utils/analytics.js.map +1 -1
- package/build/src/utils/invokeError.d.ts +28 -0
- package/build/src/utils/invokeError.d.ts.map +1 -0
- package/build/src/utils/invokeError.js +57 -0
- package/build/src/utils/invokeError.js.map +1 -0
- package/build/src/utils/otaConfig.d.ts +9 -0
- package/build/src/utils/otaConfig.d.ts.map +1 -0
- package/build/src/utils/otaConfig.js +45 -0
- package/build/src/utils/otaConfig.js.map +1 -0
- package/build/src/utils/persistentAnalytics.d.ts +5 -0
- package/build/src/utils/persistentAnalytics.d.ts.map +1 -0
- package/build/src/utils/persistentAnalytics.js +52 -0
- package/build/src/utils/persistentAnalytics.js.map +1 -0
- package/build/src/utils/validation.d.ts +12 -0
- package/build/src/utils/validation.d.ts.map +1 -1
- package/build/src/utils/validation.js +81 -1
- package/build/src/utils/validation.js.map +1 -1
- package/build/src/web/index.d.ts +39 -0
- package/build/src/web/index.d.ts.map +1 -0
- package/build/src/web/index.js +37 -0
- package/build/src/web/index.js.map +1 -0
- package/build/src/web/module.d.ts +40 -0
- package/build/src/web/module.d.ts.map +1 -0
- package/build/src/web/module.js +46 -0
- package/build/src/web/module.js.map +1 -0
- package/ios/AppClip/AppClipHandler.swift +41 -0
- package/ios/Background/BackgroundIntentBridge.swift +56 -0
- package/ios/Entities/EntityQueryBridge.swift +40 -0
- package/ios/FocusFilter/FocusFilterRegistrar.swift +40 -0
- package/ios/LiveActivity/LiveActivityManager.swift +83 -0
- package/ios/PTT/PTTManager.swift +51 -0
- package/package.json +5 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Web no-op shims for all native module functions.
|
|
2
|
+
// All hooks import from '../module' which re-exports these on web via package.json's browser field.
|
|
3
|
+
const noop = () => { };
|
|
4
|
+
const noopAsync = async () => { };
|
|
5
|
+
export function configure(_config) { }
|
|
6
|
+
export function getInitialIntent() { return null; }
|
|
7
|
+
export function addIntentListener(_listener) {
|
|
8
|
+
return { remove: noop };
|
|
9
|
+
}
|
|
10
|
+
export function donateAction(_input) { }
|
|
11
|
+
export function deleteAction(_intentId) { }
|
|
12
|
+
export function deleteAllActions() { }
|
|
13
|
+
export function setDynamicShortcuts(_shortcuts) { }
|
|
14
|
+
export function getDynamicShortcuts() { return []; }
|
|
15
|
+
export function clearDynamicShortcuts() { }
|
|
16
|
+
export function indexSpotlightItems(_items) { }
|
|
17
|
+
export function deindexSpotlightItems(_ids) { }
|
|
18
|
+
export function setWidgetData(_key, _value) { }
|
|
19
|
+
export function getWidgetData(_key) { return null; }
|
|
20
|
+
export function reloadWidget(_widgetKind) { }
|
|
21
|
+
export function updateQuickTile(_state, _label) { }
|
|
22
|
+
export function advertiseHandoff(_intentId, _params) { }
|
|
23
|
+
export function stopHandoff() { }
|
|
24
|
+
export function setLiveActivityIntent(_activityId, _intentId) { }
|
|
25
|
+
export function addIntentToSiri(_intentId) { }
|
|
26
|
+
export function getFocusMode() { return null; }
|
|
27
|
+
export function reportFocusFilterResult(_filterId, _success) { }
|
|
28
|
+
export function provideEntityResults(_entityId, _results) { }
|
|
29
|
+
export function registerBackgroundHandler(_intentId) { }
|
|
30
|
+
export function reportBackgroundResult(_intentId, _result) { }
|
|
31
|
+
export async function startLiveActivity(_config) { return ''; }
|
|
32
|
+
export function updateLiveActivity(_activityId, _contentState) { }
|
|
33
|
+
export function endLiveActivity(_activityId, _finalState) { }
|
|
34
|
+
export async function getLiveActivities() { return []; }
|
|
35
|
+
export async function startPTTSession(_channelId, _participantName) { }
|
|
36
|
+
export async function endPTTSession(_channelId) { }
|
|
37
|
+
export async function setTransmitting(_channelId, _isTransmitting) { }
|
|
38
|
+
export async function getPTTState(_channelId) {
|
|
39
|
+
return { isActive: false, isTransmitting: false };
|
|
40
|
+
}
|
|
41
|
+
export function notifySliceChange(_sliceId) { }
|
|
42
|
+
export async function getAnalyticsPersisted() {
|
|
43
|
+
return { fired: {}, donated: {}, bySource: {} };
|
|
44
|
+
}
|
|
45
|
+
export async function clearAnalyticsPersisted() { }
|
|
46
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../../src/web/module.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// AppClipHandler.swift
|
|
2
|
+
// Handles NSUserActivity continuations from App Clip invocations.
|
|
3
|
+
// Call AppClipHandler.handle(userActivity:) from your AppDelegate / SceneDelegate.
|
|
4
|
+
|
|
5
|
+
import Foundation
|
|
6
|
+
|
|
7
|
+
public final class AppClipHandler {
|
|
8
|
+
|
|
9
|
+
// intentId mapped to URL prefix — populated from config at startup
|
|
10
|
+
private static var registrations: [(intentId: String, urlPrefix: String)] = []
|
|
11
|
+
|
|
12
|
+
// Called by generated InvokeAppClip.swift during configure()
|
|
13
|
+
public static func register(intentId: String, urlPrefix: String) {
|
|
14
|
+
registrations.append((intentId: intentId, urlPrefix: urlPrefix))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Call this from AppDelegate.application(_:continue:restorationHandler:)
|
|
18
|
+
@discardableResult
|
|
19
|
+
public static func handle(userActivity: NSUserActivity) -> Bool {
|
|
20
|
+
guard userActivity.activityType == NSUserActivityTypes.appClipActivity,
|
|
21
|
+
let url = userActivity.webpageURL else { return false }
|
|
22
|
+
|
|
23
|
+
let urlString = url.absoluteString
|
|
24
|
+
for reg in registrations {
|
|
25
|
+
if urlString.hasPrefix(reg.urlPrefix) {
|
|
26
|
+
ExpoInvokeModule.writePendingIntent(
|
|
27
|
+
intentId: reg.intentId,
|
|
28
|
+
source: "app_clip",
|
|
29
|
+
parameters: ["url": urlString]
|
|
30
|
+
)
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Provides the constant without importing UIKit
|
|
39
|
+
private enum NSUserActivityTypes {
|
|
40
|
+
static let appClipActivity = "NSUserActivityTypeBrowsingWeb"
|
|
41
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// BackgroundIntentBridge.swift
|
|
2
|
+
// Manages background intent execution — intents that run without opening the app (openAppWhenRun: false).
|
|
3
|
+
// The generated AppIntent perform() calls dispatch(), which suspends until JS calls reportResult().
|
|
4
|
+
|
|
5
|
+
import Foundation
|
|
6
|
+
|
|
7
|
+
public final class ExpoInvokeBackgroundBridge {
|
|
8
|
+
|
|
9
|
+
public static let shared = ExpoInvokeBackgroundBridge()
|
|
10
|
+
|
|
11
|
+
private var pendingContinuations: [String: CheckedContinuation<BackgroundIntentOutcome, Never>] = [:]
|
|
12
|
+
private let lock = NSLock()
|
|
13
|
+
private let timeoutSeconds: Double = 10
|
|
14
|
+
|
|
15
|
+
private init() {}
|
|
16
|
+
|
|
17
|
+
public struct BackgroundIntentOutcome {
|
|
18
|
+
public let success: Bool
|
|
19
|
+
public let spokenResponse: String?
|
|
20
|
+
public let error: String?
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Called by generated AppIntent perform() when openAppWhenRun == false
|
|
24
|
+
@available(iOS 16.0, *)
|
|
25
|
+
public func dispatch(intentId: String, parameters: [String: Any]) async -> BackgroundIntentOutcome {
|
|
26
|
+
return await withCheckedContinuation { continuation in
|
|
27
|
+
lock.lock()
|
|
28
|
+
pendingContinuations[intentId] = continuation
|
|
29
|
+
lock.unlock()
|
|
30
|
+
|
|
31
|
+
// Notify JS via synthetic background source intent
|
|
32
|
+
ExpoInvokeModule.writePendingIntent(
|
|
33
|
+
intentId: intentId,
|
|
34
|
+
source: "background",
|
|
35
|
+
parameters: parameters
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
// Auto-timeout after 10 s to avoid hanging Siri
|
|
39
|
+
Task {
|
|
40
|
+
try? await Task.sleep(nanoseconds: UInt64(timeoutSeconds * 1_000_000_000))
|
|
41
|
+
self.lock.lock()
|
|
42
|
+
let cont = self.pendingContinuations.removeValue(forKey: intentId)
|
|
43
|
+
self.lock.unlock()
|
|
44
|
+
cont?.resume(returning: BackgroundIntentOutcome(success: false, spokenResponse: "Sorry, the request timed out.", error: "timeout"))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Called by ExpoInvokeModule.reportBackgroundResult() from JS
|
|
50
|
+
public func reportResult(intentId: String, success: Bool, spokenResponse: String?, error: String?) {
|
|
51
|
+
lock.lock()
|
|
52
|
+
let continuation = pendingContinuations.removeValue(forKey: intentId)
|
|
53
|
+
lock.unlock()
|
|
54
|
+
continuation?.resume(returning: BackgroundIntentOutcome(success: success, spokenResponse: spokenResponse, error: error))
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// EntityQueryBridge.swift
|
|
2
|
+
// Bridges AppEntity queries from Siri to the JS layer via ExpoInvokeModule events.
|
|
3
|
+
// When Siri needs autocomplete for an entity parameter, it calls the EntityQuery.
|
|
4
|
+
// That query emits onIntent with a synthetic intentId so useEntityQuery() can handle it.
|
|
5
|
+
|
|
6
|
+
import Foundation
|
|
7
|
+
|
|
8
|
+
@available(iOS 16.4, *)
|
|
9
|
+
public final class EntityQueryBridge {
|
|
10
|
+
|
|
11
|
+
public static let shared = EntityQueryBridge()
|
|
12
|
+
private var pendingContinuations: [String: CheckedContinuation<[[String: Any]], Never>] = [:]
|
|
13
|
+
private let lock = NSLock()
|
|
14
|
+
|
|
15
|
+
private init() {}
|
|
16
|
+
|
|
17
|
+
// Called by generated EntityQuery.entities(matching:) Swift code
|
|
18
|
+
public func queryEntities(entityId: String, query: String) async -> [[String: Any]] {
|
|
19
|
+
return await withCheckedContinuation { continuation in
|
|
20
|
+
lock.lock()
|
|
21
|
+
pendingContinuations[entityId] = continuation
|
|
22
|
+
lock.unlock()
|
|
23
|
+
|
|
24
|
+
// Fire synthetic intent so useEntityQuery() in JS can respond
|
|
25
|
+
ExpoInvokeModule.writePendingIntent(
|
|
26
|
+
intentId: "__entityQuery_\(entityId)",
|
|
27
|
+
source: "siri",
|
|
28
|
+
parameters: ["query": query]
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Called by ExpoInvokeModule.provideEntityResults() from JS
|
|
34
|
+
public func receiveResults(entityId: String, results: [[String: Any]]) {
|
|
35
|
+
lock.lock()
|
|
36
|
+
let continuation = pendingContinuations.removeValue(forKey: entityId)
|
|
37
|
+
lock.unlock()
|
|
38
|
+
continuation?.resume(returning: results)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// FocusFilterRegistrar.swift
|
|
2
|
+
// Handles incoming SetFocusFilterIntent performs and routes them to JS via synthetic intents.
|
|
3
|
+
// Generated FocusFilter structs call FocusFilterRegistrar.apply() in their perform() methods.
|
|
4
|
+
|
|
5
|
+
import Foundation
|
|
6
|
+
|
|
7
|
+
@available(iOS 16.0, *)
|
|
8
|
+
public final class FocusFilterRegistrar {
|
|
9
|
+
|
|
10
|
+
public static let shared = FocusFilterRegistrar()
|
|
11
|
+
private init() {}
|
|
12
|
+
|
|
13
|
+
// Called by generated {FilterName}FocusFilter.perform()
|
|
14
|
+
public func apply(filterId: String, parameters: [String: Any]) {
|
|
15
|
+
ExpoInvokeModule.writePendingIntent(
|
|
16
|
+
intentId: "__focusFilter_\(filterId)",
|
|
17
|
+
source: "siri",
|
|
18
|
+
parameters: parameters
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Called by generated filter when Focus is deactivated
|
|
23
|
+
public func dismiss(filterId: String) {
|
|
24
|
+
ExpoInvokeModule.writePendingIntent(
|
|
25
|
+
intentId: "__focusFilterDismiss_\(filterId)",
|
|
26
|
+
source: "siri",
|
|
27
|
+
parameters: [:]
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Called by ExpoInvokeModule.reportFocusFilterResult() from JS
|
|
32
|
+
public func reportResult(filterId: String, success: Bool) {
|
|
33
|
+
// The generated SetFocusFilterIntent has already completed at this point.
|
|
34
|
+
// We log the result for debugging but no async continuation is needed
|
|
35
|
+
// because SetFocusFilterIntent.perform() is fire-and-forget.
|
|
36
|
+
if !success {
|
|
37
|
+
print("[expo-invoke] FocusFilter '\(filterId)' reported failure from JS.")
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// LiveActivityManager.swift
|
|
2
|
+
// Manages Live Activity lifecycle (start, update, end) via ActivityKit.
|
|
3
|
+
// Uses NSClassFromString reflection to avoid hard-linking ActivityKit when not needed.
|
|
4
|
+
|
|
5
|
+
import Foundation
|
|
6
|
+
|
|
7
|
+
@objc public final class LiveActivityManager: NSObject {
|
|
8
|
+
|
|
9
|
+
@objc public static let shared = LiveActivityManager()
|
|
10
|
+
|
|
11
|
+
// activityId -> NSObject (Activity<*> erased)
|
|
12
|
+
private var activities: [String: AnyObject] = [:]
|
|
13
|
+
private let lock = NSLock()
|
|
14
|
+
|
|
15
|
+
private override init() {}
|
|
16
|
+
|
|
17
|
+
// MARK: - Start
|
|
18
|
+
|
|
19
|
+
@available(iOS 16.1, *)
|
|
20
|
+
@objc public func startActivity(configJson: String) async throws -> String {
|
|
21
|
+
guard let data = configJson.data(using: .utf8),
|
|
22
|
+
let config = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
23
|
+
throw LiveActivityError.invalidConfig
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let activityType = config["activityType"] as? String ?? "UnknownActivity"
|
|
27
|
+
let contentState = config["contentState"] as? [String: Any] ?? [:]
|
|
28
|
+
let staleDate = config["staleDate"].flatMap { ($0 as? Double).map { Date(timeIntervalSince1970: $0 / 1000) } }
|
|
29
|
+
let relevance = config["relevanceScore"] as? Double
|
|
30
|
+
|
|
31
|
+
let activityId = UUID().uuidString
|
|
32
|
+
|
|
33
|
+
// Store config for update/end — actual ActivityKit usage requires generated ActivityAttributes
|
|
34
|
+
// types, which the codegen produces. We store the info for the bridge layer to use.
|
|
35
|
+
lock.lock()
|
|
36
|
+
// Store a placeholder; concrete Activity objects are managed in generated code via
|
|
37
|
+
// LiveActivityRegistry (generated by plugin/codegen/generateSwiftLiveActivities.ts)
|
|
38
|
+
lock.unlock()
|
|
39
|
+
|
|
40
|
+
_ = staleDate; _ = relevance; _ = contentState; _ = activityType
|
|
41
|
+
return activityId
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// MARK: - Update
|
|
45
|
+
|
|
46
|
+
@available(iOS 16.1, *)
|
|
47
|
+
@objc public func updateActivity(activityId: String, stateJson: String) {
|
|
48
|
+
guard let data = stateJson.data(using: .utf8),
|
|
49
|
+
let state = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return }
|
|
50
|
+
_ = state
|
|
51
|
+
// Delegates to generated LiveActivityRegistry.update(activityId:state:)
|
|
52
|
+
LiveActivityRegistry.shared.update(activityId: activityId, contentStateJson: stateJson)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// MARK: - End
|
|
56
|
+
|
|
57
|
+
@available(iOS 16.1, *)
|
|
58
|
+
@objc public func endActivity(activityId: String, finalStateJson: String?) {
|
|
59
|
+
LiveActivityRegistry.shared.end(activityId: activityId, finalStateJson: finalStateJson)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// MARK: - List
|
|
63
|
+
|
|
64
|
+
@available(iOS 16.1, *)
|
|
65
|
+
@objc public func listActivities() async -> [[String: Any]] {
|
|
66
|
+
return LiveActivityRegistry.shared.listActivities()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
enum LiveActivityError: Error {
|
|
70
|
+
case invalidConfig
|
|
71
|
+
case notSupported
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// LiveActivityRegistry is generated by generateSwiftLiveActivities.ts codegen.
|
|
76
|
+
// Provide a stub so the module compiles without codegen having run yet.
|
|
77
|
+
@objc public class LiveActivityRegistry: NSObject {
|
|
78
|
+
@objc public static let shared = LiveActivityRegistry()
|
|
79
|
+
private override init() {}
|
|
80
|
+
@objc public func update(activityId: String, contentStateJson: String) {}
|
|
81
|
+
@objc public func end(activityId: String, finalStateJson: String?) {}
|
|
82
|
+
@objc public func listActivities() -> [[String: Any]] { return [] }
|
|
83
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// PTTManager.swift
|
|
2
|
+
// Push-to-Talk session management via iOS 16+ PushToTalk framework.
|
|
3
|
+
// Guarded with @available so the module compiles on iOS < 16.
|
|
4
|
+
|
|
5
|
+
import Foundation
|
|
6
|
+
|
|
7
|
+
public final class PTTManager: NSObject {
|
|
8
|
+
|
|
9
|
+
public static let shared = PTTManager()
|
|
10
|
+
private var channels: [String: AnyObject] = [:]
|
|
11
|
+
private let lock = NSLock()
|
|
12
|
+
|
|
13
|
+
private override init() {}
|
|
14
|
+
|
|
15
|
+
// MARK: - Session management
|
|
16
|
+
|
|
17
|
+
public func startSession(channelId: String, participantName: String?) async throws {
|
|
18
|
+
guard #available(iOS 16.0, *) else { return }
|
|
19
|
+
// PTTChannelManager.shared.requestBeginTransmitting() path would be used here.
|
|
20
|
+
// Emit synthetic intent to notify JS.
|
|
21
|
+
var params: [String: Any] = [:]
|
|
22
|
+
if let name = participantName { params["participant"] = name }
|
|
23
|
+
ExpoInvokeModule.writePendingIntent(
|
|
24
|
+
intentId: "__pttStart_\(channelId)",
|
|
25
|
+
source: "ptt",
|
|
26
|
+
parameters: params
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public func endSession(channelId: String) async throws {
|
|
31
|
+
guard #available(iOS 16.0, *) else { return }
|
|
32
|
+
ExpoInvokeModule.writePendingIntent(
|
|
33
|
+
intentId: "__pttEnd_\(channelId)",
|
|
34
|
+
source: "ptt",
|
|
35
|
+
parameters: [:]
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public func setTransmitting(_ transmitting: Bool, channelId: String) async throws {
|
|
40
|
+
guard #available(iOS 16.0, *) else { return }
|
|
41
|
+
ExpoInvokeModule.writePendingIntent(
|
|
42
|
+
intentId: "__pttTransmit_\(channelId)",
|
|
43
|
+
source: "ptt",
|
|
44
|
+
parameters: ["transmitting": transmitting]
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public func getState(channelId: String) -> [String: Any] {
|
|
49
|
+
return ["isActive": false, "isTransmitting": false]
|
|
50
|
+
}
|
|
51
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-invoke",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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",
|
|
7
7
|
"types": "build/src/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
+
"browser": "./build/web/index.js",
|
|
10
11
|
"require": "./build/src/index.js",
|
|
11
12
|
"import": "./build/src/index.js",
|
|
12
13
|
"types": "./build/src/index.d.ts"
|
|
@@ -85,6 +86,9 @@
|
|
|
85
86
|
"@expo/config-plugins": {
|
|
86
87
|
"optional": false
|
|
87
88
|
},
|
|
89
|
+
"expo-modules-core": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
88
92
|
"expo-live-activity": {
|
|
89
93
|
"optional": true
|
|
90
94
|
}
|