expo-invoke 1.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.
Files changed (144) hide show
  1. package/MIGRATION.md +125 -0
  2. package/README.md +942 -0
  3. package/SECURITY.md +35 -0
  4. package/android/build.gradle +42 -0
  5. package/android/src/main/java/expo/modules/invoke/AppActionsHandler.kt +74 -0
  6. package/android/src/main/java/expo/modules/invoke/ExpoInvokeModule.kt +135 -0
  7. package/android/src/main/java/expo/modules/invoke/QuickTileService.kt +63 -0
  8. package/android/src/main/java/expo/modules/invoke/ShortcutsHelper.kt +72 -0
  9. package/build/cli/commands/generate.d.ts +1 -0
  10. package/build/cli/commands/generate.js +119 -0
  11. package/build/cli/commands/generate.js.map +1 -0
  12. package/build/cli/commands/test.d.ts +1 -0
  13. package/build/cli/commands/test.js +53 -0
  14. package/build/cli/commands/test.js.map +1 -0
  15. package/build/cli/commands/validate.d.ts +1 -0
  16. package/build/cli/commands/validate.js +116 -0
  17. package/build/cli/commands/validate.js.map +1 -0
  18. package/build/cli/index.d.ts +2 -0
  19. package/build/cli/index.js +37 -0
  20. package/build/cli/index.js.map +1 -0
  21. package/build/plugin/android/withAndroidInvoke.d.ts +8 -0
  22. package/build/plugin/android/withAndroidInvoke.js +106 -0
  23. package/build/plugin/android/withAndroidInvoke.js.map +1 -0
  24. package/build/plugin/codegen/generateActionsXml.d.ts +2 -0
  25. package/build/plugin/codegen/generateActionsXml.js +69 -0
  26. package/build/plugin/codegen/generateActionsXml.js.map +1 -0
  27. package/build/plugin/codegen/generateShortcutsXml.d.ts +2 -0
  28. package/build/plugin/codegen/generateShortcutsXml.js +45 -0
  29. package/build/plugin/codegen/generateShortcutsXml.js.map +1 -0
  30. package/build/plugin/codegen/generateSwiftIntents.d.ts +2 -0
  31. package/build/plugin/codegen/generateSwiftIntents.js +129 -0
  32. package/build/plugin/codegen/generateSwiftIntents.js.map +1 -0
  33. package/build/plugin/ios/withIOSInvoke.d.ts +9 -0
  34. package/build/plugin/ios/withIOSInvoke.js +83 -0
  35. package/build/plugin/ios/withIOSInvoke.js.map +1 -0
  36. package/build/plugin/src/withInvoke.d.ts +16 -0
  37. package/build/plugin/src/withInvoke.js +13 -0
  38. package/build/plugin/src/withInvoke.js.map +1 -0
  39. package/build/src/components/AddToSiriButton.d.ts +18 -0
  40. package/build/src/components/AddToSiriButton.d.ts.map +1 -0
  41. package/build/src/components/AddToSiriButton.js +35 -0
  42. package/build/src/components/AddToSiriButton.js.map +1 -0
  43. package/build/src/components/InvokeDebugger.d.ts +12 -0
  44. package/build/src/components/InvokeDebugger.d.ts.map +1 -0
  45. package/build/src/components/InvokeDebugger.js +122 -0
  46. package/build/src/components/InvokeDebugger.js.map +1 -0
  47. package/build/src/components/ShortcutChip.d.ts +20 -0
  48. package/build/src/components/ShortcutChip.d.ts.map +1 -0
  49. package/build/src/components/ShortcutChip.js +44 -0
  50. package/build/src/components/ShortcutChip.js.map +1 -0
  51. package/build/src/hooks/useAddToSiri.d.ts +9 -0
  52. package/build/src/hooks/useAddToSiri.d.ts.map +1 -0
  53. package/build/src/hooks/useAddToSiri.js +15 -0
  54. package/build/src/hooks/useAddToSiri.js.map +1 -0
  55. package/build/src/hooks/useDonation.d.ts +26 -0
  56. package/build/src/hooks/useDonation.d.ts.map +1 -0
  57. package/build/src/hooks/useDonation.js +21 -0
  58. package/build/src/hooks/useDonation.js.map +1 -0
  59. package/build/src/hooks/useFocusFilter.d.ts +11 -0
  60. package/build/src/hooks/useFocusFilter.d.ts.map +1 -0
  61. package/build/src/hooks/useFocusFilter.js +24 -0
  62. package/build/src/hooks/useFocusFilter.js.map +1 -0
  63. package/build/src/hooks/useHandoff.d.ts +29 -0
  64. package/build/src/hooks/useHandoff.d.ts.map +1 -0
  65. package/build/src/hooks/useHandoff.js +27 -0
  66. package/build/src/hooks/useHandoff.js.map +1 -0
  67. package/build/src/hooks/useInvoke.d.ts +43 -0
  68. package/build/src/hooks/useInvoke.d.ts.map +1 -0
  69. package/build/src/hooks/useInvoke.js +70 -0
  70. package/build/src/hooks/useInvoke.js.map +1 -0
  71. package/build/src/hooks/useInvokeAnalytics.d.ts +23 -0
  72. package/build/src/hooks/useInvokeAnalytics.d.ts.map +1 -0
  73. package/build/src/hooks/useInvokeAnalytics.js +27 -0
  74. package/build/src/hooks/useInvokeAnalytics.js.map +1 -0
  75. package/build/src/hooks/useInvokeHistory.d.ts +11 -0
  76. package/build/src/hooks/useInvokeHistory.d.ts.map +1 -0
  77. package/build/src/hooks/useInvokeHistory.js +26 -0
  78. package/build/src/hooks/useInvokeHistory.js.map +1 -0
  79. package/build/src/hooks/useInvokeInit.d.ts +16 -0
  80. package/build/src/hooks/useInvokeInit.d.ts.map +1 -0
  81. package/build/src/hooks/useInvokeInit.js +32 -0
  82. package/build/src/hooks/useInvokeInit.js.map +1 -0
  83. package/build/src/hooks/useLiveActivityIntent.d.ts +21 -0
  84. package/build/src/hooks/useLiveActivityIntent.d.ts.map +1 -0
  85. package/build/src/hooks/useLiveActivityIntent.js +9 -0
  86. package/build/src/hooks/useLiveActivityIntent.js.map +1 -0
  87. package/build/src/hooks/useQuickTile.d.ts +23 -0
  88. package/build/src/hooks/useQuickTile.d.ts.map +1 -0
  89. package/build/src/hooks/useQuickTile.js +15 -0
  90. package/build/src/hooks/useQuickTile.js.map +1 -0
  91. package/build/src/hooks/useShortcuts.d.ts +25 -0
  92. package/build/src/hooks/useShortcuts.d.ts.map +1 -0
  93. package/build/src/hooks/useShortcuts.js +23 -0
  94. package/build/src/hooks/useShortcuts.js.map +1 -0
  95. package/build/src/hooks/useSpotlight.d.ts +23 -0
  96. package/build/src/hooks/useSpotlight.d.ts.map +1 -0
  97. package/build/src/hooks/useSpotlight.js +13 -0
  98. package/build/src/hooks/useSpotlight.js.map +1 -0
  99. package/build/src/hooks/useWidgetData.d.ts +20 -0
  100. package/build/src/hooks/useWidgetData.d.ts.map +1 -0
  101. package/build/src/hooks/useWidgetData.js +12 -0
  102. package/build/src/hooks/useWidgetData.js.map +1 -0
  103. package/build/src/index.d.ts +32 -0
  104. package/build/src/index.d.ts.map +1 -0
  105. package/build/src/index.js +27 -0
  106. package/build/src/index.js.map +1 -0
  107. package/build/src/module.d.ts +25 -0
  108. package/build/src/module.d.ts.map +1 -0
  109. package/build/src/module.js +102 -0
  110. package/build/src/module.js.map +1 -0
  111. package/build/src/types/index.d.ts +156 -0
  112. package/build/src/types/index.d.ts.map +1 -0
  113. package/build/src/types/index.js +4 -0
  114. package/build/src/types/index.js.map +1 -0
  115. package/build/src/utils/actionStore.d.ts +6 -0
  116. package/build/src/utils/actionStore.d.ts.map +1 -0
  117. package/build/src/utils/actionStore.js +32 -0
  118. package/build/src/utils/actionStore.js.map +1 -0
  119. package/build/src/utils/analytics.d.ts +12 -0
  120. package/build/src/utils/analytics.d.ts.map +1 -0
  121. package/build/src/utils/analytics.js +29 -0
  122. package/build/src/utils/analytics.js.map +1 -0
  123. package/build/src/utils/testing.d.ts +39 -0
  124. package/build/src/utils/testing.d.ts.map +1 -0
  125. package/build/src/utils/testing.js +58 -0
  126. package/build/src/utils/testing.js.map +1 -0
  127. package/build/src/utils/validation.d.ts +11 -0
  128. package/build/src/utils/validation.d.ts.map +1 -0
  129. package/build/src/utils/validation.js +128 -0
  130. package/build/src/utils/validation.js.map +1 -0
  131. package/expo-module.config.json +9 -0
  132. package/ios/Focus/FocusFilterManager.swift +19 -0
  133. package/ios/Intents/BaseInvokeIntent.swift +42 -0
  134. package/ios/Intents/HandoffManager.swift +49 -0
  135. package/ios/Intents/InvokeActionStore.swift +21 -0
  136. package/ios/Intents/InvokeParameterTypes.swift +142 -0
  137. package/ios/LiveActivity/LiveActivityIntentBridge.swift +44 -0
  138. package/ios/Module/ExpoInvokeModule.swift +170 -0
  139. package/ios/Notifications/NotificationActionHandler.swift +80 -0
  140. package/ios/Shortcuts/DonationManager.swift +44 -0
  141. package/ios/Shortcuts/ShortcutsManager.swift +32 -0
  142. package/ios/Shortcuts/SpotlightManager.swift +48 -0
  143. package/ios/Widget/WidgetManager.swift +19 -0
  144. package/package.json +111 -0
@@ -0,0 +1,80 @@
1
+ import Foundation
2
+ import UserNotifications
3
+
4
+ /// Registers UNNotificationCategory entries from the notificationActions config.
5
+ /// Called once at app launch (auto-called by the generated AppDelegate extension).
6
+ public struct NotificationActionHandler {
7
+
8
+ public static func registerCategories(from configJson: String) {
9
+ guard let data = configJson.data(using: .utf8),
10
+ let config = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
11
+ let categories = config["notificationActions"] as? [[String: Any]] else { return }
12
+
13
+ var unCategories = Set<UNNotificationCategory>()
14
+
15
+ for category in categories {
16
+ guard let categoryId = category["categoryId"] as? String,
17
+ let actions = category["actions"] as? [[String: Any]] else { continue }
18
+
19
+ let unActions: [UNNotificationAction] = actions.compactMap { action in
20
+ guard let id = action["id"] as? String,
21
+ let title = action["title"] as? String else { return nil }
22
+
23
+ var options: UNNotificationActionOptions = []
24
+ if action["foreground"] as? Bool == true { options.insert(.foreground) }
25
+ if action["destructive"] as? Bool == true { options.insert(.destructive) }
26
+
27
+ return UNNotificationAction(
28
+ identifier: id,
29
+ title: title,
30
+ options: options
31
+ )
32
+ }
33
+
34
+ unCategories.insert(UNNotificationCategory(
35
+ identifier: categoryId,
36
+ actions: unActions,
37
+ intentIdentifiers: [],
38
+ options: []
39
+ ))
40
+ }
41
+
42
+ UNUserNotificationCenter.current().setNotificationCategories(unCategories)
43
+ }
44
+
45
+ /// Call from your UNUserNotificationCenterDelegate didReceive response handler.
46
+ public static func handleResponse(
47
+ categoryIdentifier: String,
48
+ actionIdentifier: String,
49
+ configJson: String
50
+ ) {
51
+ guard let data = configJson.data(using: .utf8),
52
+ let config = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
53
+ let categories = config["notificationActions"] as? [[String: Any]] else { return }
54
+
55
+ for category in categories {
56
+ guard (category["categoryId"] as? String) == categoryIdentifier,
57
+ let actions = category["actions"] as? [[String: Any]] else { continue }
58
+
59
+ if actionIdentifier == UNNotificationDefaultActionIdentifier {
60
+ // Body tap — use tapIntent
61
+ if let tapIntentId = category["tapIntent"] as? String {
62
+ ExpoInvokeModule.writePendingIntent(
63
+ intentId: tapIntentId,
64
+ source: "notification_tap"
65
+ )
66
+ }
67
+ return
68
+ }
69
+
70
+ if let action = actions.first(where: { $0["id"] as? String == actionIdentifier }),
71
+ let intentId = action["intentId"] as? String {
72
+ ExpoInvokeModule.writePendingIntent(
73
+ intentId: intentId,
74
+ source: "notification"
75
+ )
76
+ }
77
+ return
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,44 @@
1
+ import Foundation
2
+ import Intents
3
+
4
+ public struct DonationManager {
5
+
6
+ public static func donate(from json: String) {
7
+ guard let data = json.data(using: .utf8),
8
+ let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
9
+ let intentId = payload["intentId"] as? String else { return }
10
+
11
+ let title = payload["title"] as? String ?? intentId
12
+ let intent = buildIntent(title: title, identifier: intentId)
13
+ let interaction = INInteraction(intent: intent, response: nil)
14
+ interaction.identifier = "\(intentId)-\(UUID().uuidString)"
15
+ interaction.groupIdentifier = intentId
16
+ interaction.donate { error in
17
+ if let error = error {
18
+ print("[expo-invoke] Donation failed for \(intentId): \(error)")
19
+ }
20
+ }
21
+ }
22
+
23
+ public static func delete(intentId: String) {
24
+ INInteraction.delete(with: intentId) { _ in }
25
+ }
26
+
27
+ public static func deleteAll() {
28
+ INInteraction.deleteAll { _ in }
29
+ }
30
+
31
+ private static func buildIntent(title: String, identifier: String) -> INIntent {
32
+ let intent = INSendMessageIntent(
33
+ recipients: nil,
34
+ outgoingMessageType: .outgoingMessageText,
35
+ content: title,
36
+ speakableGroupName: INSpeakableString(spokenPhrase: title),
37
+ conversationIdentifier: identifier,
38
+ serviceName: Bundle.main.bundleIdentifier,
39
+ sender: nil,
40
+ attachments: nil
41
+ )
42
+ return intent
43
+ }
44
+ }
@@ -0,0 +1,32 @@
1
+ import Foundation
2
+ import Intents
3
+
4
+ public struct ShortcutsManager {
5
+
6
+ public static func update(from json: String) {
7
+ guard let data = json.data(using: .utf8),
8
+ let items = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { return }
9
+
10
+ let shortcuts: [INShortcut] = items.compactMap { item in
11
+ guard let id = item["id"] as? String,
12
+ let title = item["title"] as? String else { return nil }
13
+
14
+ let activity = NSUserActivity(activityType: "expo.invoke.shortcut.\(id)")
15
+ activity.title = title
16
+ activity.userInfo = item
17
+ activity.isEligibleForSearch = true
18
+ activity.isEligibleForPrediction = true
19
+ activity.persistentIdentifier = id
20
+ if let subtitle = item["subtitle"] as? String {
21
+ activity.userInfo?["subtitle"] = subtitle
22
+ }
23
+ return INShortcut(userActivity: activity)
24
+ }
25
+
26
+ INVoiceShortcutCenter.shared.setShortcutSuggestions(shortcuts)
27
+ }
28
+
29
+ public static func clear() {
30
+ INVoiceShortcutCenter.shared.setShortcutSuggestions([])
31
+ }
32
+ }
@@ -0,0 +1,48 @@
1
+ import Foundation
2
+ import CoreSpotlight
3
+ import MobileCoreServices
4
+
5
+ public struct SpotlightManager {
6
+
7
+ public static func index(from json: String) {
8
+ guard let data = json.data(using: .utf8),
9
+ let items = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { return }
10
+
11
+ let searchItems: [CSSearchableItem] = items.compactMap { item in
12
+ guard let id = item["id"] as? String,
13
+ let title = item["title"] as? String else { return nil }
14
+
15
+ let attrs = CSSearchableItemAttributeSet(contentType: .item)
16
+ attrs.title = title
17
+ attrs.contentDescription = item["description"] as? String
18
+ if let keywords = item["keywords"] as? [String] { attrs.keywords = keywords }
19
+ if let thumbUrl = item["thumbnailUrl"] as? String,
20
+ let url = URL(string: thumbUrl) {
21
+ attrs.thumbnailURL = url
22
+ }
23
+
24
+ // Store intent info in userInfo for retrieval on tap
25
+ let userInfo: [String: Any] = [
26
+ "intentId": item["intentId"] as? String ?? "",
27
+ "intentParameters": item["intentParameters"] ?? [:],
28
+ ]
29
+ attrs.userInfo = userInfo as NSDictionary
30
+
31
+ return CSSearchableItem(
32
+ uniqueIdentifier: id,
33
+ domainIdentifier: Bundle.main.bundleIdentifier,
34
+ attributeSet: attrs
35
+ )
36
+ }
37
+
38
+ CSSearchableIndex.default().indexSearchableItems(searchItems) { error in
39
+ if let error = error {
40
+ print("[expo-invoke] Spotlight indexing error: \(error)")
41
+ }
42
+ }
43
+ }
44
+
45
+ public static func deindex(ids: [String]) {
46
+ CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids) { _ in }
47
+ }
48
+ }
@@ -0,0 +1,19 @@
1
+ import Foundation
2
+
3
+ /// Triggers a reload of all WidgetKit widget timelines so they display fresh data
4
+ /// after setWidgetData() is called.
5
+ public struct WidgetManager {
6
+ @available(iOS 14.0, *)
7
+ public static func reloadAll() {
8
+ // WidgetCenter is in WidgetKit — use performSelector to avoid hard linking
9
+ // the framework in packages that don't need it.
10
+ if let widgetCenter = NSClassFromString("WidgetKit.WidgetCenter") as? NSObject.Type,
11
+ let center = widgetCenter.value(forKey: "shared") as? NSObject {
12
+ center.performSelector(inBackground: #selector(NSObject.reloadAllTimelines), with: nil)
13
+ }
14
+ }
15
+ }
16
+
17
+ extension NSObject {
18
+ @objc func reloadAllTimelines() {}
19
+ }
package/package.json ADDED
@@ -0,0 +1,111 @@
1
+ {
2
+ "name": "expo-invoke",
3
+ "version": "1.0.0",
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
+ "main": "build/src/index.js",
6
+ "module": "build/src/index.js",
7
+ "types": "build/src/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "require": "./build/src/index.js",
11
+ "import": "./build/src/index.js",
12
+ "types": "./build/src/index.d.ts"
13
+ }
14
+ },
15
+ "expo": {
16
+ "plugin": "./build/plugin/src/withInvoke"
17
+ },
18
+ "files": [
19
+ "build",
20
+ "ios",
21
+ "android/src",
22
+ "android/build.gradle",
23
+ "expo-module.config.json",
24
+ "README.md",
25
+ "MIGRATION.md",
26
+ "SECURITY.md"
27
+ ],
28
+ "scripts": {
29
+ "build": "tsc -p tsconfig.json && tsc -p tsconfig.node.json",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.node.json --noEmit",
31
+ "clean": "rm -rf build",
32
+ "prepublishOnly": "npm run clean && npm run build",
33
+ "test": "jest"
34
+ },
35
+ "bin": {
36
+ "expo-invoke": "./build/cli/index.js"
37
+ },
38
+ "keywords": [
39
+ "expo",
40
+ "react-native",
41
+ "siri",
42
+ "app-intents",
43
+ "google-assistant",
44
+ "app-actions",
45
+ "shortcuts",
46
+ "voice",
47
+ "widget",
48
+ "widgetkit",
49
+ "dynamic-island",
50
+ "live-activities",
51
+ "nfc",
52
+ "deep-link",
53
+ "ios",
54
+ "android"
55
+ ],
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "https://github.com/smarthivelabs/expo-invoke"
59
+ },
60
+ "bugs": {
61
+ "url": "https://github.com/smarthivelabs/expo-invoke/issues"
62
+ },
63
+ "homepage": "https://github.com/smarthivelabs/expo-invoke#readme",
64
+ "author": "SmartHive Labs",
65
+ "license": "MIT",
66
+ "engines": {
67
+ "node": ">=18.0.0",
68
+ "npm": ">=9.0.0"
69
+ },
70
+ "publishConfig": {
71
+ "access": "public",
72
+ "registry": "https://registry.npmjs.org/"
73
+ },
74
+ "peerDependencies": {
75
+ "expo": ">=50.0.0",
76
+ "expo-modules-core": ">=1.12.0",
77
+ "react": ">=18.0.0",
78
+ "react-native": ">=0.73.0"
79
+ },
80
+ "peerDependenciesMeta": {
81
+ "expo-live-activity": {
82
+ "optional": true
83
+ }
84
+ },
85
+ "dependencies": {
86
+ "@expo/config-plugins": "^9.0.0"
87
+ },
88
+ "devDependencies": {
89
+ "@types/jest": "^29.5.14",
90
+ "@types/node": "^20.19.41",
91
+ "@types/react": "^19.2.16",
92
+ "@types/react-native": "^0.73.0",
93
+ "expo-modules-core": "^56.0.14",
94
+ "jest": "^29.0.0",
95
+ "react": "^18.3.1",
96
+ "react-native": "^0.73.11",
97
+ "ts-jest": "^29.4.11",
98
+ "typescript": "^5.9.3"
99
+ },
100
+ "jest": {
101
+ "preset": "ts-jest",
102
+ "testEnvironment": "node",
103
+ "moduleNameMapper": {
104
+ "^react-native$": "<rootDir>/node_modules/react-native/jest/setup.js",
105
+ "^expo-modules-core$": "<rootDir>/__mocks__/expo-modules-core.ts"
106
+ },
107
+ "testMatch": [
108
+ "**/__tests__/**/*.test.ts"
109
+ ]
110
+ }
111
+ }