expo-app-intents 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `donateIntentAsync()` and `deleteDonationsAsync()` to donate App Intents to the system. ([#50760](https://github.com/expo/expo/pull/50760) by [@chrfalch](https://github.com/chrfalch))
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies. ([#50881](https://github.com/expo/expo/pull/50881), [#49933](https://github.com/expo/expo/pull/49933))
12
+ - @expo/ui@58.0.11
13
+
14
+ ## 0.4.8
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies. ([#50801](https://github.com/expo/expo/pull/50801), [#49986](https://github.com/expo/expo/pull/49986), [#50674](https://github.com/expo/expo/pull/50674), [#50786](https://github.com/expo/expo/pull/50786), [#50851](https://github.com/expo/expo/pull/50851))
19
+ - @expo/ui@58.0.10
20
+
3
21
  ## 0.4.7
4
22
 
5
23
  ### Patch Changes
package/README.md CHANGED
@@ -45,6 +45,33 @@ export function AppIntentHandler() {
45
45
  }
46
46
  ```
47
47
 
48
+ ## Donating intents
49
+
50
+ Donate an intent when the user performs its action inside your app, so the system can suggest it later on the Lock Screen, in Siri Suggestions, and in Spotlight. Make the intent donatable in Swift and register it in the `OnCreate` of your `AppIntentsSetup` module. JavaScript donates by the registered name. Nothing requires it to match the name the intent dispatches, but reusing that name gives each intent one name in JavaScript. The params that JavaScript passes are converted to the intent's `DonationParams` record, so a missing required field or a field of the wrong type rejects the donation. An intent without params only needs to conform to `DonatableAppIntent`. Declare the record with `@Field` properties, because the `@Record` macro is not available in the app target:
51
+
52
+ ```swift
53
+ extension SaveNoteIntent: DonatableAppIntent {
54
+ struct DonationParams: Record {
55
+ @Field(.required) var text: String = ""
56
+ }
57
+
58
+ init(donationParams: DonationParams) {
59
+ self.init()
60
+ self.text = donationParams.text
61
+ }
62
+ }
63
+
64
+ AppIntentDonationRegistry.shared.register("saveNote", as: SaveNoteIntent.self)
65
+ ```
66
+
67
+ Then donate it from JavaScript, and delete donations when they no longer apply:
68
+
69
+ ```ts
70
+ await AppIntents.donateIntentAsync('saveNote', { text: 'Buy milk' });
71
+
72
+ await AppIntents.deleteDonationsAsync({ intent: 'saveNote' });
73
+ ```
74
+
48
75
  ## Limitations
49
76
 
50
77
  - Shortcut phrases are compiled at build time and cannot be created from JavaScript at runtime. Only parameter values are dynamic.
@@ -77,6 +77,31 @@ export type AppEntityIdentifierModifier = ModifierConfig & {
77
77
  /** Stable entity id from the matching App Intents entity catalog. */
78
78
  id: string;
79
79
  };
80
+ /**
81
+ * A value that can be passed as an App Intent parameter: anything JSON can represent.
82
+ */
83
+ export type AppIntentJSONValue = string | number | boolean | null | AppIntentJSONValue[] | {
84
+ [key: string]: AppIntentJSONValue;
85
+ };
86
+ /**
87
+ * Selects the donations that `deleteDonationsAsync()` deletes. Pass one of these shapes:
88
+ *
89
+ * - `{ ids }` deletes the donations with the given IDs, as returned by `donateIntentAsync()`.
90
+ * - `{ intent }` deletes every donation of the intent type registered under that name. When
91
+ * several names are registered for one intent type, this also deletes the donations made under
92
+ * the other names.
93
+ * - `{ entity, id }` deletes every donation that refers to the given entity. The `entity` value
94
+ * must be registered with `AppEntityIdentifierRegistry.shared.register(_:as:)` or
95
+ * `AppEntityIdentifierRegistry.shared.registerIndexed(_:as:)`.
96
+ */
97
+ export type AppIntentDonationFilter = {
98
+ ids: string[];
99
+ } | {
100
+ intent: string;
101
+ } | {
102
+ entity: string;
103
+ id: string;
104
+ };
80
105
  export type ExpoAppIntentsModuleEvents = {
81
106
  onIntent: (invocation: AppIntentInvocation) => void;
82
107
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["ExpoAppIntents.types.ts"],"sourcesContent":[null],"names":[],"mappings":"AAAA,gGAAgG;AAChG,wBAAwB;AAwFxB,WAEE"}
1
+ {"version":3,"sources":["ExpoAppIntents.types.ts"],"sourcesContent":[null],"names":[],"mappings":"AAAA,gGAAgG;AAChG,wBAAwB;AAmHxB,WAEE"}
@@ -1,5 +1,5 @@
1
1
  import { NativeModule } from 'expo-modules-core';
2
- import type { AppIntentEntity, AppIntentInvocation, ExpoAppIntentsModuleEvents } from './ExpoAppIntents.types';
2
+ import type { AppIntentDonationFilter, AppIntentEntity, AppIntentInvocation, AppIntentJSONValue, ExpoAppIntentsModuleEvents } from './ExpoAppIntents.types';
3
3
  declare class ExpoAppIntentsNativeModule extends NativeModule<ExpoAppIntentsModuleEvents> {
4
4
  getPendingInvocationsAsync(): Promise<AppIntentInvocation[]>;
5
5
  removePendingInvocationAsync(id: string): Promise<void>;
@@ -8,6 +8,8 @@ declare class ExpoAppIntentsNativeModule extends NativeModule<ExpoAppIntentsModu
8
8
  reindexEntitiesAsync(kind: string | null): Promise<void>;
9
9
  getEntityCatalogAsync(kind: string): Promise<AppIntentEntity[]>;
10
10
  refreshShortcutsAsync(): Promise<void>;
11
+ donateIntentAsync(name: string, params?: Record<string, AppIntentJSONValue>): Promise<string>;
12
+ deleteDonationsAsync(filter: AppIntentDonationFilter): Promise<string[]>;
11
13
  }
12
14
  declare const _default: ExpoAppIntentsNativeModule | null;
13
15
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"sources":["ExpoAppIntentsModule.ts"],"sourcesContent":[null],"names":["NativeModule","requireOptionalNativeModule"],"mappings":"AAAA,SAASA,YAAY,EAAEC,2BAA2B,QAAQ,oBAAoB;AAkB9E,eAAeA,4BAAwD,kBAAkB"}
1
+ {"version":3,"sources":["ExpoAppIntentsModule.ts"],"sourcesContent":[null],"names":["NativeModule","requireOptionalNativeModule"],"mappings":"AAAA,SAASA,YAAY,EAAEC,2BAA2B,QAAQ,oBAAoB;AAsB9E,eAAeA,4BAAwD,kBAAkB"}
package/build/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type EventSubscription } from 'expo-modules-core';
2
- import type { AppEntityIdentifierModifier, AppIntentEntity, AppIntentInvocation, AppIntentsHandler } from './ExpoAppIntents.types';
2
+ import type { AppEntityIdentifierModifier, AppIntentDonationFilter, AppIntentEntity, AppIntentInvocation, AppIntentJSONValue, AppIntentsHandler } from './ExpoAppIntents.types';
3
3
  export type * from './ExpoAppIntents.types';
4
4
  export { default as AppEntityView } from './AppEntityView';
5
5
  export type { AppEntityViewProps } from './AppEntityView.types';
@@ -112,6 +112,46 @@ export declare function getEntityCatalogAsync(kind: string): Promise<AppIntentEn
112
112
  * shortcuts.
113
113
  */
114
114
  export declare function refreshShortcutsAsync(): Promise<void>;
115
+ /**
116
+ * Tells the system that the user just performed an App Intent's action in your app, for example
117
+ * ordering food from a screen rather than through Siri. The system learns from donations and can
118
+ * suggest the action later on the Lock Screen, in Siri Suggestions, and in Spotlight.
119
+ *
120
+ * The `name` must be registered from app-target Swift with
121
+ * `AppIntentDonationRegistry.shared.register(_:as:)`, on an intent that conforms to
122
+ * `DonatableAppIntent`. Native code converts `params` to the `DonationParams` record of that intent,
123
+ * and the intent builds itself from the record in its `init(donationParams:)`.
124
+ *
125
+ * The returned promise is fulfilled with an ID for the donation, which
126
+ * [`deleteDonationsAsync()`](#appintentsdeletedonationsasyncfilter) accepts, or with `null` when
127
+ * App Intents are unavailable. It is rejected when no intent is registered as `name`, when `params`
128
+ * misses a required field of the record or has a field of the wrong type, when the intent cannot be
129
+ * built from the record, or when the system fails to record the donation.
130
+ *
131
+ * > **Note:** The ID is the system's donation identifier in its encoded form. Store it only for as
132
+ * > long as you need it. An iOS update may change how the system encodes identifiers, and an ID
133
+ * > stored before such an update may then be rejected. To delete donations without stored IDs,
134
+ * > delete by `intent` or by `entity`.
135
+ *
136
+ * @platform ios
137
+ */
138
+ export declare function donateIntentAsync(name: string, params?: Record<string, AppIntentJSONValue>): Promise<string | null>;
139
+ /**
140
+ * Deletes donations made with [`donateIntentAsync()`](#appintentsdonateintentasyncname-params), so
141
+ * the system stops suggesting them. Delete donations when what they refer to is gone, for example
142
+ * after the user deletes an order, or when the user signs out.
143
+ *
144
+ * The returned promise is fulfilled with the IDs of the deleted donations, or with an empty array
145
+ * when App Intents are unavailable. It is rejected when `filter` names an intent or entity that is
146
+ * not registered, contains an ID that cannot be read, or contains an entity `id` that cannot be
147
+ * converted to the ID type of that entity. In those cases nothing is deleted. With `ids`, every ID
148
+ * is attempted. When the system fails to delete one or more of them, the promise is rejected with
149
+ * an error that lists the deleted IDs and the IDs that were not deleted. With `intent` or
150
+ * `entity`, the promise is rejected with the system error when the system fails to delete.
151
+ *
152
+ * @platform ios
153
+ */
154
+ export declare function deleteDonationsAsync(filter: AppIntentDonationFilter): Promise<string[]>;
115
155
  /**
116
156
  * Returns an ExpoUI SwiftUI modifier config that ties a view to an AppEntity identifier.
117
157
  *
package/build/index.js CHANGED
@@ -217,6 +217,54 @@ function callAppIntentsHandler(handler, pendingIntents, newIntent) {
217
217
  }
218
218
  return ExpoAppIntents.refreshShortcutsAsync();
219
219
  }
220
+ /**
221
+ * Tells the system that the user just performed an App Intent's action in your app, for example
222
+ * ordering food from a screen rather than through Siri. The system learns from donations and can
223
+ * suggest the action later on the Lock Screen, in Siri Suggestions, and in Spotlight.
224
+ *
225
+ * The `name` must be registered from app-target Swift with
226
+ * `AppIntentDonationRegistry.shared.register(_:as:)`, on an intent that conforms to
227
+ * `DonatableAppIntent`. Native code converts `params` to the `DonationParams` record of that intent,
228
+ * and the intent builds itself from the record in its `init(donationParams:)`.
229
+ *
230
+ * The returned promise is fulfilled with an ID for the donation, which
231
+ * [`deleteDonationsAsync()`](#appintentsdeletedonationsasyncfilter) accepts, or with `null` when
232
+ * App Intents are unavailable. It is rejected when no intent is registered as `name`, when `params`
233
+ * misses a required field of the record or has a field of the wrong type, when the intent cannot be
234
+ * built from the record, or when the system fails to record the donation.
235
+ *
236
+ * > **Note:** The ID is the system's donation identifier in its encoded form. Store it only for as
237
+ * > long as you need it. An iOS update may change how the system encodes identifiers, and an ID
238
+ * > stored before such an update may then be rejected. To delete donations without stored IDs,
239
+ * > delete by `intent` or by `entity`.
240
+ *
241
+ * @platform ios
242
+ */ export async function donateIntentAsync(name, params) {
243
+ if (!ExpoAppIntents) {
244
+ return null;
245
+ }
246
+ return ExpoAppIntents.donateIntentAsync(name, params);
247
+ }
248
+ /**
249
+ * Deletes donations made with [`donateIntentAsync()`](#appintentsdonateintentasyncname-params), so
250
+ * the system stops suggesting them. Delete donations when what they refer to is gone, for example
251
+ * after the user deletes an order, or when the user signs out.
252
+ *
253
+ * The returned promise is fulfilled with the IDs of the deleted donations, or with an empty array
254
+ * when App Intents are unavailable. It is rejected when `filter` names an intent or entity that is
255
+ * not registered, contains an ID that cannot be read, or contains an entity `id` that cannot be
256
+ * converted to the ID type of that entity. In those cases nothing is deleted. With `ids`, every ID
257
+ * is attempted. When the system fails to delete one or more of them, the promise is rejected with
258
+ * an error that lists the deleted IDs and the IDs that were not deleted. With `intent` or
259
+ * `entity`, the promise is rejected with the system error when the system fails to delete.
260
+ *
261
+ * @platform ios
262
+ */ export async function deleteDonationsAsync(filter) {
263
+ if (!ExpoAppIntents) {
264
+ return [];
265
+ }
266
+ return ExpoAppIntents.deleteDonationsAsync(filter);
267
+ }
220
268
  /**
221
269
  * Returns an ExpoUI SwiftUI modifier config that ties a view to an AppEntity identifier.
222
270
  *
@@ -1 +1 @@
1
- {"version":3,"sources":["index.ts"],"sourcesContent":[null],"names":["UnavailabilityError","useEffect","useRef","ExpoAppIntents","default","AppEntityView","MAX_SEEN_INVOCATION_IDS","isAvailable","addAppIntentListener","listener","remove","addListener","callAppIntentsHandler","handler","pendingIntents","newIntent","Promise","resolve","then","catch","error","console","warn","useAppIntents","handlerRef","current","isMounted","seenLiveInvocationIds","Set","deliveryQueue","enqueue","deliver","notify","deliverNewIntent","getPendingInvocationsAsync","length","deliverInitialPendingIntents","initialPendingIntents","filter","invocation","has","id","forEach","add","subscription","size","delete","values","next","value","removePendingInvocationAsync","clearPendingInvocationsAsync","setEntityCatalogAsync","kind","entities","reindexEntitiesAsync","getEntityCatalogAsync","refreshShortcutsAsync","appEntityIdentifier","entity","$type"],"mappings":"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"}
1
+ {"version":3,"sources":["index.ts"],"sourcesContent":[null],"names":["UnavailabilityError","useEffect","useRef","ExpoAppIntents","default","AppEntityView","MAX_SEEN_INVOCATION_IDS","isAvailable","addAppIntentListener","listener","remove","addListener","callAppIntentsHandler","handler","pendingIntents","newIntent","Promise","resolve","then","catch","error","console","warn","useAppIntents","handlerRef","current","isMounted","seenLiveInvocationIds","Set","deliveryQueue","enqueue","deliver","notify","deliverNewIntent","getPendingInvocationsAsync","length","deliverInitialPendingIntents","initialPendingIntents","filter","invocation","has","id","forEach","add","subscription","size","delete","values","next","value","removePendingInvocationAsync","clearPendingInvocationsAsync","setEntityCatalogAsync","kind","entities","reindexEntitiesAsync","getEntityCatalogAsync","refreshShortcutsAsync","donateIntentAsync","name","params","deleteDonationsAsync","appEntityIdentifier","entity","$type"],"mappings":"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"}
@@ -0,0 +1,304 @@
1
+ import AppIntents
2
+ import ExpoModulesCore
3
+ import Foundation
4
+
5
+ /// An `AppIntent` that JavaScript can donate by name with `donateIntentAsync()`.
6
+ ///
7
+ /// A donation tells the system that the user just did this in the app, so Siri, Spotlight and the
8
+ /// Shortcuts app can suggest it later. The system needs a real intent value to learn from, and only
9
+ /// the app target can build one, so each donatable intent builds itself from the params that
10
+ /// JavaScript passed.
11
+ ///
12
+ /// The params arrive as the intent's `DonationParams` record, so each field already has the type the
13
+ /// intent declares, and a missing required field is rejected before the intent is built. An intent
14
+ /// that takes no params from JavaScript can leave out both `DonationParams` and `init(donationParams:)`.
15
+ public protocol DonatableAppIntent: AppIntent {
16
+ associatedtype DonationParams: Record = NoDonationParams
17
+
18
+ init(donationParams: DonationParams) async throws
19
+ }
20
+
21
+ /// The `DonationParams` of an intent that takes no params from JavaScript. Any params passed are
22
+ /// ignored.
23
+ public struct NoDonationParams: Record {
24
+ public init() {}
25
+ }
26
+
27
+ extension DonatableAppIntent where DonationParams == NoDonationParams {
28
+ /// An intent without params needs no donation code of its own: conforming is enough.
29
+ public init(donationParams: NoDonationParams) {
30
+ self.init()
31
+ }
32
+ }
33
+
34
+ internal enum AppIntentDonationFilter {
35
+ case ids([String])
36
+ case intent(String)
37
+ case entity(String, id: String)
38
+ }
39
+
40
+ /// The system predicate a filter resolves to. `IntentDonationMatchingPredicate` is opaque, so this is
41
+ /// what the donor is handed instead, and what tests can inspect.
42
+ internal enum AppIntentDonationMatch {
43
+ case donation(IntentDonationIdentifier)
44
+ case intentType(any AppIntent.Type)
45
+ case entity(EntityIdentifier)
46
+ }
47
+
48
+ /// The calls made to `IntentDonationManager`, behind a protocol so tests never donate to the device.
49
+ internal protocol AppIntentDonor: Sendable {
50
+ func donate(_ intent: any AppIntent) async throws -> IntentDonationIdentifier
51
+ func deleteDonations(matching match: AppIntentDonationMatch) async throws -> [IntentDonationIdentifier]
52
+ }
53
+
54
+ internal struct SystemAppIntentDonor: AppIntentDonor {
55
+ func donate(_ intent: any AppIntent) async throws -> IntentDonationIdentifier {
56
+ return try await IntentDonationManager.shared.donate(intent: intent)
57
+ }
58
+
59
+ func deleteDonations(matching match: AppIntentDonationMatch) async throws -> [IntentDonationIdentifier] {
60
+ let manager = IntentDonationManager.shared
61
+ switch match {
62
+ case .donation(let identifier):
63
+ return try await manager.deleteDonations(matching: .donationIdentifier(identifier))
64
+ case .intentType(let intentType):
65
+ return try await manager.deleteDonations(matching: .intentType(intentType))
66
+ case .entity(let identifier):
67
+ return try await manager.deleteDonations(matching: .entityIdentifier(identifier))
68
+ }
69
+ }
70
+ }
71
+
72
+ /// Maps the names JavaScript donates by to the app-target intent types that can be donated.
73
+ ///
74
+ /// Register each intent in the `OnCreate` of the `AppIntentsSetup` inline module. The name only has to
75
+ /// match what JavaScript passes to `donateIntentAsync()` and `deleteDonationsAsync({ intent })`.
76
+ /// Nothing links it to the name the intent passes to `AppIntentDispatcher.shared.dispatch(name:params:)`,
77
+ /// but reusing that name, where there is one, gives JavaScript one name per intent.
78
+ public final class AppIntentDonationRegistry: Sendable {
79
+ public static let shared = AppIntentDonationRegistry()
80
+
81
+ /// What `register(_:as:)` keeps for one name. The intent type is erased so that intents with
82
+ /// different `DonationParams` can share the registry, and `makeIntent` keeps the concrete type
83
+ /// that converting the params needs.
84
+ private struct Registration: Sendable {
85
+ let intentType: any AppIntent.Type
86
+ let makeIntent: @Sendable ([String: Any], AppContext) async throws -> any AppIntent
87
+ }
88
+
89
+ /// Registration happens on the main actor from `OnCreate`, while lookups come from the module's
90
+ /// async functions at the same time.
91
+ private let registrations = Mutex<[String: Registration]>([:])
92
+ private let donor: any AppIntentDonor
93
+ private let entities: AppEntityIdentifierRegistry
94
+
95
+ internal init(
96
+ donor: any AppIntentDonor = SystemAppIntentDonor(),
97
+ entities: AppEntityIdentifierRegistry = .shared
98
+ ) {
99
+ self.donor = donor
100
+ self.entities = entities
101
+ }
102
+
103
+ /// Registering a name again replaces its earlier intent. Deleting by `{ intent }` deletes by intent
104
+ /// type, so when two names are registered for one type, deleting by either name deletes the
105
+ /// donations made under both.
106
+ public func register<Intent: DonatableAppIntent>(_ name: String, as intentType: Intent.Type) {
107
+ let registration = Registration(intentType: intentType) { params, appContext in
108
+ let donationParams: Intent.DonationParams
109
+ do {
110
+ donationParams = try Intent.DonationParams.from(dictionary: params, appContext: appContext)
111
+ } catch {
112
+ throw InvalidDonationParamsException((intent: name, paramsType: "\(Intent.DonationParams.self)")).causedBy(
113
+ error
114
+ )
115
+ }
116
+ do {
117
+ return try await Intent(donationParams: donationParams)
118
+ } catch {
119
+ throw DonationIntentInitException((intent: name, error: error))
120
+ }
121
+ }
122
+ registrations.withLock {
123
+ $0[name] = registration
124
+ }
125
+ }
126
+
127
+ /// Builds the intent registered as `name` from `params`, donates it, and returns the donation id
128
+ /// JavaScript can later delete it by.
129
+ internal func donate(_ name: String, params: [String: Any], appContext: AppContext) async throws -> String {
130
+ let intent = try await registration(named: name).makeIntent(params, appContext)
131
+ return try encode(await donor.donate(intent))
132
+ }
133
+
134
+ /// Deletes the donations matching `filter`, and returns the ids of the ones that were deleted.
135
+ internal func deleteDonations(matching filter: AppIntentDonationFilter) async throws -> [String] {
136
+ switch filter {
137
+ case .ids(let ids):
138
+ // Every id is read before anything is deleted, so an unreadable id deletes nothing.
139
+ return try await deleteEach(ids.map { (id: $0, identifier: try decode($0)) })
140
+ case .intent(let name):
141
+ return try await donor.deleteDonations(matching: .intentType(registration(named: name).intentType)).map(encode)
142
+ case .entity(let entity, let id):
143
+ guard let identifier = entities.identifier(for: entity, id: id) else {
144
+ throw UnregisteredDonationEntityException((entity, id))
145
+ }
146
+ return try await donor.deleteDonations(matching: .entity(identifier)).map(encode)
147
+ }
148
+ }
149
+
150
+ /// `.donationIdentifiers(_:)` needs iOS 26.4 and its SDK, so each id is deleted on its own. A failed
151
+ /// id does not stop the ids after it, and the exception names which ids were deleted and which not.
152
+ private func deleteEach(_ donations: [(id: String, identifier: IntentDonationIdentifier)]) async throws -> [String] {
153
+ var deleted: [IntentDonationIdentifier] = []
154
+ var failed: [String] = []
155
+ var firstError: (any Error)?
156
+ for donation in donations {
157
+ do {
158
+ deleted += try await donor.deleteDonations(matching: .donation(donation.identifier))
159
+ } catch {
160
+ failed.append(donation.id)
161
+ firstError = firstError ?? error
162
+ }
163
+ }
164
+ let deletedIds = try deleted.map(encode)
165
+ if let firstError {
166
+ throw PartialDonationDeletionException((deleted: deletedIds, failed: failed)).causedBy(firstError)
167
+ }
168
+ return deletedIds
169
+ }
170
+
171
+ private func registration(named name: String) throws -> Registration {
172
+ guard let registration = registrations.withLock({ $0[name] }) else {
173
+ throw UnregisteredDonationIntentException(name)
174
+ }
175
+ return registration
176
+ }
177
+
178
+ private func encode(_ identifier: IntentDonationIdentifier) throws -> String {
179
+ // `JSONEncoder` only writes valid UTF-8, so there is nothing for a failable conversion to catch.
180
+ // swiftlint:disable:next optional_data_string_conversion
181
+ return String(decoding: try JSONEncoder().encode(identifier), as: UTF8.self)
182
+ }
183
+
184
+ private func decode(_ id: String) throws -> IntentDonationIdentifier {
185
+ do {
186
+ return try JSONDecoder().decode(IntentDonationIdentifier.self, from: Data(id.utf8))
187
+ } catch {
188
+ throw InvalidDonationIdentifierException(id)
189
+ }
190
+ }
191
+ }
192
+
193
+ /// The filter `deleteDonationsAsync()` receives. JavaScript types it as a union, so it arrives as one
194
+ /// object whose keys say which variant it is.
195
+ @Record
196
+ internal struct AppIntentDonationFilterRecord {
197
+ var ids: [String]?
198
+ var intent: String?
199
+ var entity: String?
200
+ var id: String?
201
+
202
+ func toFilter() throws -> AppIntentDonationFilter {
203
+ switch (ids, intent, entity, id) {
204
+ case (let ids?, nil, nil, nil):
205
+ return .ids(ids)
206
+ case (nil, let intent?, nil, nil):
207
+ return .intent(intent)
208
+ case (nil, nil, let entity?, let id?):
209
+ return .entity(entity, id: id)
210
+ default:
211
+ throw InvalidDonationFilterException()
212
+ }
213
+ }
214
+ }
215
+
216
+ internal final class UnregisteredDonationIntentException: GenericException<String>, @unchecked Sendable {
217
+ override var reason: String {
218
+ return """
219
+ expo-app-intents has no intent registered for donation as '\(param)'. JavaScript can only name \
220
+ intents that app-target Swift has registered, because only the app target can build them. Make \
221
+ the intent conform to DonatableAppIntent, then add this to the OnCreate of your AppIntentsSetup \
222
+ module: AppIntentDonationRegistry.shared.register("\(param)", as: YourIntent.self)
223
+ """
224
+ }
225
+ }
226
+
227
+ internal final class InvalidDonationIdentifierException: GenericException<String>, @unchecked Sendable {
228
+ override var reason: String {
229
+ return """
230
+ expo-app-intents could not read the donation id '\(param)', so no donations were deleted. \
231
+ Donation ids are opaque, and one that was changed or built by hand no longer names a donation. \
232
+ Pass the id exactly as donateIntentAsync() or deleteDonationsAsync() returned it.
233
+ """
234
+ }
235
+ }
236
+
237
+ internal final class UnregisteredDonationEntityException: GenericException<(String, String)>, @unchecked Sendable {
238
+ override var reason: String {
239
+ let (entity, id) = param
240
+ return """
241
+ expo-app-intents could not delete donations for the '\(entity)' entity '\(id)'. This happens \
242
+ when no AppEntity is registered as '\(entity)'. It also happens when '\(id)' is not a valid \
243
+ identifier for that entity. Register the entity in the OnCreate of your AppIntentsSetup module with \
244
+ AppEntityIdentifierRegistry.shared.register("\(entity)", as: YourEntity.self), and pass an id \
245
+ that converts to the type of its id property.
246
+ """
247
+ }
248
+ }
249
+
250
+ /// The record's own exception, which names the field, is the cause.
251
+ internal final class InvalidDonationParamsException: GenericException<(intent: String, paramsType: String)>,
252
+ @unchecked Sendable
253
+ {
254
+ override var reason: String {
255
+ return """
256
+ expo-app-intents could not donate the '\(param.intent)' intent, because the params passed to \
257
+ donateIntentAsync() do not fit its DonationParams record \(param.paramsType). Nothing was \
258
+ donated. Pass every required field of that record, with the type the record declares.
259
+ """
260
+ }
261
+ }
262
+
263
+ /// Keeps the app's error in the reason rather than using `causedBy(_:)`: a cause that is not an
264
+ /// `Exception` is described by its `localizedDescription`, which drops the text of a plain Swift error
265
+ /// such as a `CustomStringConvertible` struct, and this error comes from app code.
266
+ internal final class DonationIntentInitException: GenericException<(intent: String, error: any Error)>,
267
+ @unchecked Sendable
268
+ {
269
+ override var reason: String {
270
+ return """
271
+ expo-app-intents could not donate the '\(param.intent)' intent, because its \
272
+ init(donationParams:) threw: \(param.error). Nothing was donated. Check that the params passed \
273
+ to donateIntentAsync() match what init(donationParams:) of the intent registered as \
274
+ '\(param.intent)' expects.
275
+ """
276
+ }
277
+ }
278
+
279
+ /// The first error the system reported is the cause.
280
+ internal final class PartialDonationDeletionException: GenericException<(deleted: [String], failed: [String])>,
281
+ @unchecked Sendable
282
+ {
283
+ override var reason: String {
284
+ return """
285
+ expo-app-intents could not delete every donation. Deleted: \(list(param.deleted)). Not \
286
+ deleted: \(list(param.failed)). Call deleteDonationsAsync() again with the ids that were not \
287
+ deleted.
288
+ """
289
+ }
290
+
291
+ private func list(_ ids: [String]) -> String {
292
+ return ids.isEmpty ? "none" : ids.map { "'\($0)'" }.joined(separator: ", ")
293
+ }
294
+ }
295
+
296
+ internal final class InvalidDonationFilterException: Exception, @unchecked Sendable {
297
+ override var reason: String {
298
+ return """
299
+ deleteDonationsAsync() needs exactly one of { ids }, { intent }, or { entity, id }, so it cannot \
300
+ tell which donations to delete. Pass one of those shapes, and call it once per filter to delete \
301
+ by more than one.
302
+ """
303
+ }
304
+ }
@@ -138,6 +138,19 @@ public final class ExpoAppIntentsModule: Module, @unchecked Sendable {
138
138
  AsyncFunction("refreshShortcutsAsync") { () async throws in
139
139
  try await self.refreshShortcuts()
140
140
  }
141
+
142
+ AsyncFunction("donateIntentAsync") { (name: String, params: [String: Any]?) async throws -> String in
143
+ guard let appContext = self.appContext else {
144
+ throw Exceptions.AppContextLost()
145
+ }
146
+ // The registry converts `params` to the `DonationParams` record of the intent registered as
147
+ // `name`, since only that registration knows the record type.
148
+ return try await AppIntentDonationRegistry.shared.donate(name, params: params ?? [:], appContext: appContext)
149
+ }
150
+
151
+ AsyncFunction("deleteDonationsAsync") { (filter: AppIntentDonationFilterRecord) async throws -> [String] in
152
+ return try await AppIntentDonationRegistry.shared.deleteDonations(matching: filter.toFilter())
153
+ }
141
154
  }
142
155
 
143
156
  /// Installs the `appEntityIdentifier` factory. Both factories take the `AppContext` from the call
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-app-intents",
3
- "version": "0.4.7",
3
+ "version": "0.5.0",
4
4
  "description": "Expose Apple App Intents (Siri, Shortcuts, Spotlight, Apple Intelligence) from Expo apps",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -28,16 +28,16 @@
28
28
  "license": "MIT",
29
29
  "homepage": "https://docs.expo.dev/versions/latest/sdk/app-intents",
30
30
  "devDependencies": {
31
- "@testing-library/react-native": "^13.3.0",
31
+ "@testing-library/react-native": "^14.0.1",
32
32
  "@types/jest": "^29.2.1",
33
33
  "@types/node": "^22.14.0",
34
34
  "@types/prompts": "^2.4.9",
35
35
  "@types/react": "~19.3.0",
36
- "expo": "58.0.0",
37
- "expo-module-scripts": "56.0.4"
36
+ "expo": "58.0.2",
37
+ "expo-module-scripts": "56.0.5"
38
38
  },
39
39
  "dependencies": {
40
- "@expo/ui": "^58.0.9",
40
+ "@expo/ui": "^58.0.11",
41
41
  "commander": "^12.1.0",
42
42
  "prompts": "^2.4.2"
43
43
  },
@@ -87,6 +87,33 @@ export type AppEntityIdentifierModifier = ModifierConfig & {
87
87
  id: string;
88
88
  };
89
89
 
90
+ /**
91
+ * A value that can be passed as an App Intent parameter: anything JSON can represent.
92
+ */
93
+ export type AppIntentJSONValue =
94
+ | string
95
+ | number
96
+ | boolean
97
+ | null
98
+ | AppIntentJSONValue[]
99
+ | { [key: string]: AppIntentJSONValue };
100
+
101
+ /**
102
+ * Selects the donations that `deleteDonationsAsync()` deletes. Pass one of these shapes:
103
+ *
104
+ * - `{ ids }` deletes the donations with the given IDs, as returned by `donateIntentAsync()`.
105
+ * - `{ intent }` deletes every donation of the intent type registered under that name. When
106
+ * several names are registered for one intent type, this also deletes the donations made under
107
+ * the other names.
108
+ * - `{ entity, id }` deletes every donation that refers to the given entity. The `entity` value
109
+ * must be registered with `AppEntityIdentifierRegistry.shared.register(_:as:)` or
110
+ * `AppEntityIdentifierRegistry.shared.registerIndexed(_:as:)`.
111
+ */
112
+ export type AppIntentDonationFilter =
113
+ | { ids: string[] }
114
+ | { intent: string }
115
+ | { entity: string; id: string };
116
+
90
117
  export type ExpoAppIntentsModuleEvents = {
91
118
  onIntent: (invocation: AppIntentInvocation) => void;
92
119
  };
@@ -1,8 +1,10 @@
1
1
  import { NativeModule, requireOptionalNativeModule } from 'expo-modules-core';
2
2
 
3
3
  import type {
4
+ AppIntentDonationFilter,
4
5
  AppIntentEntity,
5
6
  AppIntentInvocation,
7
+ AppIntentJSONValue,
6
8
  ExpoAppIntentsModuleEvents,
7
9
  } from './ExpoAppIntents.types';
8
10
 
@@ -14,6 +16,8 @@ declare class ExpoAppIntentsNativeModule extends NativeModule<ExpoAppIntentsModu
14
16
  reindexEntitiesAsync(kind: string | null): Promise<void>;
15
17
  getEntityCatalogAsync(kind: string): Promise<AppIntentEntity[]>;
16
18
  refreshShortcutsAsync(): Promise<void>;
19
+ donateIntentAsync(name: string, params?: Record<string, AppIntentJSONValue>): Promise<string>;
20
+ deleteDonationsAsync(filter: AppIntentDonationFilter): Promise<string[]>;
17
21
  }
18
22
 
19
23
  export default requireOptionalNativeModule<ExpoAppIntentsNativeModule>('ExpoAppIntents');
package/src/index.ts CHANGED
@@ -3,8 +3,10 @@ import { useEffect, useRef } from 'react';
3
3
 
4
4
  import type {
5
5
  AppEntityIdentifierModifier,
6
+ AppIntentDonationFilter,
6
7
  AppIntentEntity,
7
8
  AppIntentInvocation,
9
+ AppIntentJSONValue,
8
10
  AppIntentsHandler,
9
11
  } from './ExpoAppIntents.types';
10
12
  import ExpoAppIntents from './ExpoAppIntentsModule';
@@ -266,6 +268,61 @@ export async function refreshShortcutsAsync(): Promise<void> {
266
268
  return ExpoAppIntents.refreshShortcutsAsync();
267
269
  }
268
270
 
271
+ /**
272
+ * Tells the system that the user just performed an App Intent's action in your app, for example
273
+ * ordering food from a screen rather than through Siri. The system learns from donations and can
274
+ * suggest the action later on the Lock Screen, in Siri Suggestions, and in Spotlight.
275
+ *
276
+ * The `name` must be registered from app-target Swift with
277
+ * `AppIntentDonationRegistry.shared.register(_:as:)`, on an intent that conforms to
278
+ * `DonatableAppIntent`. Native code converts `params` to the `DonationParams` record of that intent,
279
+ * and the intent builds itself from the record in its `init(donationParams:)`.
280
+ *
281
+ * The returned promise is fulfilled with an ID for the donation, which
282
+ * [`deleteDonationsAsync()`](#appintentsdeletedonationsasyncfilter) accepts, or with `null` when
283
+ * App Intents are unavailable. It is rejected when no intent is registered as `name`, when `params`
284
+ * misses a required field of the record or has a field of the wrong type, when the intent cannot be
285
+ * built from the record, or when the system fails to record the donation.
286
+ *
287
+ * > **Note:** The ID is the system's donation identifier in its encoded form. Store it only for as
288
+ * > long as you need it. An iOS update may change how the system encodes identifiers, and an ID
289
+ * > stored before such an update may then be rejected. To delete donations without stored IDs,
290
+ * > delete by `intent` or by `entity`.
291
+ *
292
+ * @platform ios
293
+ */
294
+ export async function donateIntentAsync(
295
+ name: string,
296
+ params?: Record<string, AppIntentJSONValue>
297
+ ): Promise<string | null> {
298
+ if (!ExpoAppIntents) {
299
+ return null;
300
+ }
301
+ return ExpoAppIntents.donateIntentAsync(name, params);
302
+ }
303
+
304
+ /**
305
+ * Deletes donations made with [`donateIntentAsync()`](#appintentsdonateintentasyncname-params), so
306
+ * the system stops suggesting them. Delete donations when what they refer to is gone, for example
307
+ * after the user deletes an order, or when the user signs out.
308
+ *
309
+ * The returned promise is fulfilled with the IDs of the deleted donations, or with an empty array
310
+ * when App Intents are unavailable. It is rejected when `filter` names an intent or entity that is
311
+ * not registered, contains an ID that cannot be read, or contains an entity `id` that cannot be
312
+ * converted to the ID type of that entity. In those cases nothing is deleted. With `ids`, every ID
313
+ * is attempted. When the system fails to delete one or more of them, the promise is rejected with
314
+ * an error that lists the deleted IDs and the IDs that were not deleted. With `intent` or
315
+ * `entity`, the promise is rejected with the system error when the system fails to delete.
316
+ *
317
+ * @platform ios
318
+ */
319
+ export async function deleteDonationsAsync(filter: AppIntentDonationFilter): Promise<string[]> {
320
+ if (!ExpoAppIntents) {
321
+ return [];
322
+ }
323
+ return ExpoAppIntents.deleteDonationsAsync(filter);
324
+ }
325
+
269
326
  /**
270
327
  * Returns an ExpoUI SwiftUI modifier config that ties a view to an AppEntity identifier.
271
328
  *