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
package/SECURITY.md ADDED
@@ -0,0 +1,35 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ |---|---|
7
+ | 1.x (current) | ✓ |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ **Do not open a public GitHub issue for security vulnerabilities.**
12
+
13
+ Email: security@smarthivelabs.dev
14
+
15
+ Please include:
16
+ - Description of the vulnerability
17
+ - Steps to reproduce
18
+ - Affected versions
19
+ - Potential impact
20
+
21
+ We will acknowledge your report within 48 hours and aim to release a fix within 14 days for critical issues.
22
+
23
+ ## Security Considerations for Users
24
+
25
+ ### Intent ID exposure
26
+ Intent IDs defined in `app.json` are visible in the compiled binary. Do not use intent IDs as security boundaries. Treat them as public routing tokens.
27
+
28
+ ### Deep link → intent mapping
29
+ Deep links that map to intents (`deepLinkPatterns` in config) process user-supplied URLs. Validate any parameters extracted from deep links before acting on them in your app.
30
+
31
+ ### Widget / shared storage
32
+ `useWidgetData()` stores values in a shared App Group container (iOS) or SharedPreferences (Android). Do not store sensitive data (tokens, PII) using this API.
33
+
34
+ ### Notification action intents
35
+ Notification action buttons trigger intents based on category IDs in the push payload. Ensure your push notification server validates recipient identity before sending notifications with action categories.
@@ -0,0 +1,42 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'kotlin-android'
3
+
4
+ android {
5
+ namespace "expo.modules.invoke"
6
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
7
+
8
+ compileOptions {
9
+ sourceCompatibility JavaVersion.VERSION_11
10
+ targetCompatibility JavaVersion.VERSION_11
11
+ }
12
+
13
+ kotlinOptions {
14
+ jvmTarget = JavaVersion.VERSION_11.majorVersion
15
+ }
16
+
17
+ defaultConfig {
18
+ minSdkVersion safeExtGet("minSdkVersion", 24)
19
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
20
+ }
21
+
22
+ sourceSets {
23
+ main {
24
+ java.srcDirs += 'src/main/java'
25
+ }
26
+ }
27
+ }
28
+
29
+ repositories {
30
+ mavenCentral()
31
+ google()
32
+ }
33
+
34
+ dependencies {
35
+ implementation project(':expo-modules-core')
36
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}"
37
+ implementation "androidx.core:core-ktx:1.12.0"
38
+ }
39
+
40
+ def safeExtGet(prop, fallback) {
41
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
42
+ }
@@ -0,0 +1,74 @@
1
+ package expo.modules.invoke
2
+
3
+ import android.content.Intent
4
+ import android.content.SharedPreferences
5
+ import android.net.Uri
6
+
7
+ /**
8
+ * Handles incoming Android Intents from Google App Actions and deep links.
9
+ *
10
+ * Google App Actions fire as ACTION_VIEW intents with a deep link URL.
11
+ * The intentId is encoded as either:
12
+ * - A path segment: myapp://invoke/create_errand?param=value
13
+ * - A query param: myapp://open?intentId=create_errand&param=value
14
+ */
15
+ object AppActionsHandler {
16
+
17
+ private val INVOKE_ACTIONS = setOf(
18
+ Intent.ACTION_VIEW,
19
+ Intent.ACTION_MAIN,
20
+ "com.google.android.gms.actions.SEARCH_ACTION",
21
+ )
22
+
23
+ fun handleIntent(intent: Intent, prefs: SharedPreferences): String? {
24
+ if (intent.action !in INVOKE_ACTIONS) return null
25
+ val uri = intent.data ?: return null
26
+ return parseUri(uri, prefs)
27
+ }
28
+
29
+ private fun parseUri(uri: Uri, prefs: SharedPreferences): String? {
30
+ // Extract intentId from path or query param
31
+ val intentId = uri.getQueryParameter("intentId")
32
+ ?: extractIntentIdFromPath(uri)
33
+ ?: return null
34
+
35
+ val parameters = mutableMapOf<String, Any>()
36
+
37
+ for (key in uri.queryParameterNames) {
38
+ if (key == "intentId") continue
39
+ val value = uri.getQueryParameter(key) ?: continue
40
+ parameters[key] = coerceParam(key, value)
41
+ }
42
+
43
+ val source = when {
44
+ uri.host?.contains("google") == true -> "google"
45
+ else -> "deep_link"
46
+ }
47
+
48
+ ExpoInvokeModule.writePendingIntent(prefs, intentId, source, parameters)
49
+
50
+ return prefs.getString("pendingIntent", null)
51
+ }
52
+
53
+ private fun extractIntentIdFromPath(uri: Uri): String? {
54
+ // e.g. myapp://invoke/create_errand → "create_errand"
55
+ val segments = uri.pathSegments
56
+ return if (segments.size >= 2 && segments[0] == "invoke") {
57
+ segments[1]
58
+ } else if (segments.size == 1) {
59
+ segments[0].takeIf { it.isNotBlank() }
60
+ } else {
61
+ null
62
+ }
63
+ }
64
+
65
+ private fun coerceParam(key: String, value: String): Any {
66
+ // Attempt numeric coercion for common numeric keys
67
+ value.toDoubleOrNull()?.let { return it }
68
+ if (value.equals("true", ignoreCase = true)) return true
69
+ if (value.equals("false", ignoreCase = true)) return false
70
+
71
+ // Location: "lat,lng" or JSON object — keep as string; JS side parses
72
+ return value
73
+ }
74
+ }
@@ -0,0 +1,135 @@
1
+ package expo.modules.invoke
2
+
3
+ import android.content.Context
4
+ import android.content.Intent
5
+ import android.content.SharedPreferences
6
+ import expo.modules.kotlin.modules.Module
7
+ import expo.modules.kotlin.modules.ModuleDefinition
8
+ import org.json.JSONObject
9
+
10
+ private const val PREFS_NAME = "expo.invoke"
11
+ private const val KEY_PENDING_INTENT = "pendingIntent"
12
+ private const val KEY_CONFIG = "config"
13
+ private const val KEY_SHORTCUTS = "shortcuts"
14
+ private const val DEFAULT_TTL_MS = 30_000L
15
+
16
+ class ExpoInvokeModule : Module() {
17
+
18
+ private val prefs: SharedPreferences
19
+ get() = appContext.reactContext!!.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
20
+
21
+ override fun definition() = ModuleDefinition {
22
+ Name("ExpoInvoke")
23
+
24
+ Events("onIntent")
25
+
26
+ // ── configure ────────────────────────────────────────────────────────────
27
+ Function("configure") { configJson: String ->
28
+ prefs.edit().putString(KEY_CONFIG, configJson).apply()
29
+ }
30
+
31
+ // ── getInitialIntent ─────────────────────────────────────────────────────
32
+ Function("getInitialIntent") {
33
+ consumePendingIntent(prefs)
34
+ }
35
+
36
+ // ── Donations (no-op on Android — Google handles via App Actions) ────────
37
+ Function("donateAction") { _: String -> Unit }
38
+ Function("deleteAction") { _: String -> Unit }
39
+ Function("deleteAllActions") { Unit }
40
+
41
+ // ── Dynamic shortcuts ─────────────────────────────────────────────────────
42
+ Function("setDynamicShortcuts") { json: String ->
43
+ prefs.edit().putString(KEY_SHORTCUTS, json).apply()
44
+ ShortcutsHelper.setDynamicShortcuts(appContext.reactContext!!, json)
45
+ }
46
+
47
+ Function("getDynamicShortcuts") {
48
+ prefs.getString(KEY_SHORTCUTS, "[]") ?: "[]"
49
+ }
50
+
51
+ Function("clearDynamicShortcuts") {
52
+ prefs.edit().remove(KEY_SHORTCUTS).apply()
53
+ ShortcutsHelper.clearDynamicShortcuts(appContext.reactContext!!)
54
+ }
55
+
56
+ // ── Spotlight — no-op on Android ─────────────────────────────────────────
57
+ Function("indexSpotlightItems") { _: String -> Unit }
58
+ Function("deindexSpotlightItems") { _: List<String> -> Unit }
59
+
60
+ // ── Widget data (SharedPreferences — readable by AppWidgetProvider) ──────
61
+ Function("setWidgetData") { key: String, value: String ->
62
+ prefs.edit().putString("widget.$key", value).apply()
63
+ }
64
+
65
+ Function("getWidgetData") { key: String ->
66
+ prefs.getString("widget.$key", null)
67
+ }
68
+
69
+ // ── Quick tile ────────────────────────────────────────────────────────────
70
+ Function("updateQuickTile") { state: String, label: String ->
71
+ QuickTileService.pendingState = state
72
+ QuickTileService.pendingLabel = label
73
+ QuickTileService.requestUpdate(appContext.reactContext!!)
74
+ }
75
+
76
+ // ── Handoff — no-op on Android ────────────────────────────────────────────
77
+ Function("advertiseHandoff") { _: String, _: String -> Unit }
78
+ Function("stopHandoff") { Unit }
79
+
80
+ // ── Live Activity — no-op on Android ─────────────────────────────────────
81
+ Function("setLiveActivityIntent") { _: String, _: String -> Unit }
82
+
83
+ // ── Add to Siri — no-op on Android ───────────────────────────────────────
84
+ Function("addIntentToSiri") { _: String -> Unit }
85
+
86
+ // ── Focus mode — no-op on Android ────────────────────────────────────────
87
+ Function("getFocusMode") { null as String? }
88
+
89
+ // ── Warm start ────────────────────────────────────────────────────────────
90
+ OnActivityEntersForeground {
91
+ val json = consumePendingIntent(prefs) ?: return@OnActivityEntersForeground
92
+ sendEvent("onIntent", mapOf("json" to json))
93
+ }
94
+
95
+ // ── New intent (App Actions / deep links fire here) ───────────────────────
96
+ OnNewIntent { intent ->
97
+ val json = AppActionsHandler.handleIntent(intent, prefs) ?: return@OnNewIntent
98
+ sendEvent("onIntent", mapOf("json" to json))
99
+ }
100
+ }
101
+
102
+ companion object {
103
+ fun writePendingIntent(
104
+ prefs: SharedPreferences,
105
+ intentId: String,
106
+ source: String = "google",
107
+ parameters: Map<String, Any> = emptyMap(),
108
+ ) {
109
+ val payload = JSONObject().apply {
110
+ put("intentId", intentId)
111
+ put("source", source)
112
+ put("timestamp", System.currentTimeMillis().toDouble())
113
+ val params = JSONObject()
114
+ parameters.forEach { (k, v) -> params.put(k, v) }
115
+ put("parameters", params)
116
+ }
117
+ prefs.edit().putString(KEY_PENDING_INTENT, payload.toString()).apply()
118
+ }
119
+
120
+ fun consumePendingIntent(prefs: SharedPreferences): String? {
121
+ val json = prefs.getString(KEY_PENDING_INTENT, null) ?: return null
122
+ prefs.edit().remove(KEY_PENDING_INTENT).apply()
123
+
124
+ return try {
125
+ val obj = JSONObject(json)
126
+ val tsMs = obj.optDouble("timestamp", 0.0)
127
+ val ttl = obj.optDouble("_ttl", DEFAULT_TTL_MS.toDouble())
128
+ val ageMs = System.currentTimeMillis() - tsMs
129
+ if (ageMs < ttl) json else null
130
+ } catch (_: Exception) {
131
+ null
132
+ }
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,63 @@
1
+ package expo.modules.invoke
2
+
3
+ import android.annotation.TargetApi
4
+ import android.content.ComponentName
5
+ import android.content.Context
6
+ import android.content.SharedPreferences
7
+ import android.os.Build
8
+ import android.service.quicksettings.Tile
9
+ import android.service.quicksettings.TileService
10
+ import androidx.core.content.getSystemService
11
+
12
+ @TargetApi(Build.VERSION_CODES.N)
13
+ class QuickTileService : TileService() {
14
+
15
+ override fun onStartListening() {
16
+ super.onStartListening()
17
+ applyPendingState()
18
+ }
19
+
20
+ override fun onClick() {
21
+ super.onClick()
22
+ // Write a pending intent so the app fires it when opened
23
+ val prefs = getSharedPreferences("expo.invoke", MODE_PRIVATE)
24
+ val intentId = prefs.getString("quickTileIntentId", null) ?: return
25
+ ExpoInvokeModule.writePendingIntent(prefs, intentId, "quick_tile")
26
+ // Launch the app
27
+ startActivityAndCollapse(
28
+ packageManager.getLaunchIntentForPackage(packageName)?.apply {
29
+ addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
30
+ }
31
+ )
32
+ }
33
+
34
+ private fun applyPendingState() {
35
+ val tile = qsTile ?: return
36
+ val state = pendingState
37
+ val label = pendingLabel
38
+
39
+ tile.state = when (state) {
40
+ "active" -> Tile.STATE_ACTIVE
41
+ "inactive" -> Tile.STATE_INACTIVE
42
+ else -> Tile.STATE_UNAVAILABLE
43
+ }
44
+ if (label.isNotBlank()) {
45
+ tile.label = label
46
+ }
47
+ tile.updateTile()
48
+ }
49
+
50
+ companion object {
51
+ @Volatile var pendingState: String = "inactive"
52
+ @Volatile var pendingLabel: String = ""
53
+
54
+ fun requestUpdate(context: Context) {
55
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
56
+ requestListeningState(
57
+ context,
58
+ ComponentName(context, QuickTileService::class.java)
59
+ )
60
+ }
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,72 @@
1
+ package expo.modules.invoke
2
+
3
+ import android.annotation.TargetApi
4
+ import android.content.Context
5
+ import android.content.pm.ShortcutInfo
6
+ import android.content.pm.ShortcutManager
7
+ import android.graphics.drawable.Icon
8
+ import android.os.Build
9
+ import androidx.core.content.getSystemService
10
+ import org.json.JSONArray
11
+ import org.json.JSONObject
12
+
13
+ object ShortcutsHelper {
14
+
15
+ @TargetApi(Build.VERSION_CODES.N_MR1)
16
+ fun setDynamicShortcuts(context: Context, json: String) {
17
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return
18
+ val manager = context.getSystemService<ShortcutManager>() ?: return
19
+
20
+ val shortcuts = parseShortcuts(context, json)
21
+ manager.dynamicShortcuts = shortcuts.take(manager.maxShortcutCountPerActivity)
22
+ }
23
+
24
+ @TargetApi(Build.VERSION_CODES.N_MR1)
25
+ fun clearDynamicShortcuts(context: Context) {
26
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return
27
+ context.getSystemService<ShortcutManager>()?.removeAllDynamicShortcuts()
28
+ }
29
+
30
+ @TargetApi(Build.VERSION_CODES.N_MR1)
31
+ private fun parseShortcuts(context: Context, json: String): List<ShortcutInfo> {
32
+ val arr = JSONArray(json)
33
+ val result = mutableListOf<ShortcutInfo>()
34
+
35
+ for (i in 0 until arr.length()) {
36
+ val obj = arr.getJSONObject(i)
37
+ val id = obj.getString("id")
38
+ val title = obj.getString("title")
39
+ val subtitle = obj.optString("subtitle", "")
40
+ val intentId = obj.optString("intentId", id)
41
+
42
+ val launchIntent = context.packageManager
43
+ .getLaunchIntentForPackage(context.packageName)
44
+ ?.apply {
45
+ action = "expo.invoke.SHORTCUT"
46
+ putExtra("intentId", intentId)
47
+ // Encode optional parameters
48
+ if (obj.has("parameters")) {
49
+ putExtra("parameters", obj.getJSONObject("parameters").toString())
50
+ }
51
+ }
52
+ ?: continue
53
+
54
+ val builder = ShortcutInfo.Builder(context, id)
55
+ .setShortLabel(title.take(10))
56
+ .setLongLabel(if (subtitle.isNotEmpty()) subtitle.take(25) else title.take(25))
57
+ .setIntent(launchIntent)
58
+
59
+ // Use adaptive icon from the app launcher icon
60
+ try {
61
+ val iconRes = context.resources.getIdentifier("ic_launcher", "mipmap", context.packageName)
62
+ if (iconRes != 0) {
63
+ builder.setIcon(Icon.createWithResource(context, iconRes))
64
+ }
65
+ } catch (_: Exception) { /* icon is optional */ }
66
+
67
+ result.add(builder.build())
68
+ }
69
+
70
+ return result
71
+ }
72
+ }
@@ -0,0 +1 @@
1
+ export declare function runGenerate(args: string[]): void;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runGenerate = runGenerate;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ function toPascalCase(id) {
40
+ return id.split(/[_\s-]+/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
41
+ }
42
+ function tsParamType(param) {
43
+ switch (param.type) {
44
+ case 'text': return 'string';
45
+ case 'number': return 'number';
46
+ case 'boolean': return 'boolean';
47
+ case 'date': return 'number'; // timestamp ms
48
+ case 'location': return '{ latitude: number; longitude: number; name?: string }';
49
+ case 'person': return '{ displayName?: string; phoneNumber?: string; emailAddress?: string }';
50
+ case 'enum': return param.options.map((o) => `"${o.id}"`).join(' | ') || 'string';
51
+ case 'array':
52
+ switch (param.itemType) {
53
+ case 'text': return 'string[]';
54
+ case 'number': return 'number[]';
55
+ case 'location': return '{ latitude: number; longitude: number; name?: string }[]';
56
+ default: return 'unknown[]';
57
+ }
58
+ default: return 'unknown';
59
+ }
60
+ }
61
+ function generateParamsInterface(intent) {
62
+ var _a;
63
+ const params = (_a = intent.parameters) !== null && _a !== void 0 ? _a : [];
64
+ if (params.length === 0)
65
+ return ' parameters: Record<string, never>;';
66
+ const fields = params.map((p) => {
67
+ const opt = p.optional ? '?' : '';
68
+ return ` ${p.name}${opt}: ${tsParamType(p)};`;
69
+ }).join('\n');
70
+ return ` parameters: {\n${fields}\n };`;
71
+ }
72
+ function loadConfig(cwd) {
73
+ var _a, _b, _c;
74
+ const appJsonPath = path.join(cwd, 'app.json');
75
+ if (!fs.existsSync(appJsonPath)) {
76
+ console.error('❌ app.json not found in', cwd);
77
+ return null;
78
+ }
79
+ const raw = JSON.parse(fs.readFileSync(appJsonPath, 'utf8'));
80
+ const plugins = (_c = (_b = (_a = raw === null || raw === void 0 ? void 0 : raw.expo) === null || _a === void 0 ? void 0 : _a.plugins) !== null && _b !== void 0 ? _b : raw === null || raw === void 0 ? void 0 : raw.plugins) !== null && _c !== void 0 ? _c : [];
81
+ for (const plugin of plugins) {
82
+ if (Array.isArray(plugin) && plugin[0] === 'expo-invoke') {
83
+ return plugin[1];
84
+ }
85
+ }
86
+ console.error('❌ expo-invoke plugin not found in app.json');
87
+ return null;
88
+ }
89
+ function runGenerate(args) {
90
+ var _a;
91
+ const cwd = (_a = args[0]) !== null && _a !== void 0 ? _a : process.cwd();
92
+ const outputPath = path.join(cwd, 'invoke-types.d.ts');
93
+ const config = loadConfig(cwd);
94
+ if (!config) {
95
+ process.exit(1);
96
+ return;
97
+ }
98
+ const intentIds = config.intents.map((i) => `"${i.id}"`).join(' | ');
99
+ const intentTypes = config.intents.map((intent) => {
100
+ const name = `${toPascalCase(intent.id)}Intent`;
101
+ return `export interface ${name} extends ResolvedIntent {
102
+ intentId: "${intent.id}";
103
+ ${generateParamsInterface(intent)}
104
+ }`;
105
+ }).join('\n\n');
106
+ const output = `// AUTO-GENERATED by expo-invoke. Run \`npx expo-invoke generate\` to refresh.
107
+ import type { ResolvedIntent } from "expo-invoke";
108
+
109
+ export type InvokeIntentId = ${intentIds};
110
+
111
+ ${intentTypes}
112
+
113
+ export type AnyInvokeIntent =
114
+ ${config.intents.map((i) => `${toPascalCase(i.id)}Intent`).join('\n | ')};
115
+ `;
116
+ fs.writeFileSync(outputPath, output, 'utf8');
117
+ console.log('✅ Generated', outputPath);
118
+ }
119
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../cli/commands/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function runTest(args: string[]): void;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runTest = runTest;
37
+ const child_process_1 = require("child_process");
38
+ const path = __importStar(require("path"));
39
+ function runTest(args) {
40
+ var _a;
41
+ const cwd = (_a = args[0]) !== null && _a !== void 0 ? _a : process.cwd();
42
+ const jestBin = path.join(__dirname, '..', '..', 'node_modules', '.bin', 'jest');
43
+ try {
44
+ (0, child_process_1.execSync)(`node "${jestBin}" --passWithNoTests`, {
45
+ cwd,
46
+ stdio: 'inherit',
47
+ });
48
+ }
49
+ catch {
50
+ process.exit(1);
51
+ }
52
+ }
53
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../../../cli/commands/test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,0BAYC;AAfD,iDAAyC;AACzC,2CAA6B;AAE7B,SAAgB,OAAO,CAAC,IAAc;;IACpC,MAAM,GAAG,GAAG,MAAA,IAAI,CAAC,CAAC,CAAC,mCAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjF,IAAI,CAAC;QACH,IAAA,wBAAQ,EAAC,SAAS,OAAO,qBAAqB,EAAE;YAC9C,GAAG;YACH,KAAK,EAAE,SAAS;SACjB,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function runValidate(args: string[]): void;