Skip to content

Commit 268550e

Browse files
authored
Add more notification actions and multiple shield actions (#53)
* Add more notification actions and multiple shield actions * chore: update version * make it more flexible, add addCurrentToWhitelist to generic actions * chore: update version * add removeAllDeliveredNotifications, type for addCurrentToWhitelist * chore: update version
1 parent c119e34 commit 268550e

5 files changed

Lines changed: 213 additions & 114 deletions

File tree

packages/react-native-device-activity/ios/Shared.swift

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,154 @@ func notifyAppWithName(name: String) {
9797
CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, false)
9898
}
9999

100+
func executeGenericAction(
101+
action: [String: Any],
102+
placeholders: [String: String?],
103+
triggeredBy: String,
104+
applicationToken: ApplicationToken? = nil,
105+
webdomainToken: WebDomainToken? = nil,
106+
categoryToken: ActivityCategoryToken? = nil
107+
) {
108+
let type = action["type"] as? String
109+
110+
if let sleepBefore = action["sleepBefore"] as? Int {
111+
sleep(ms: sleepBefore)
112+
}
113+
114+
if type == "addCurrentToWhitelist" {
115+
var selection = getCurrentWhitelist()
116+
117+
if let applicationToken = applicationToken {
118+
selection.applicationTokens.insert(applicationToken)
119+
}
120+
121+
if let webdomainToken = webdomainToken {
122+
selection.webDomainTokens.insert(webdomainToken)
123+
}
124+
125+
if let categoryToken = categoryToken {
126+
selection.categoryTokens.insert(categoryToken)
127+
}
128+
129+
saveCurrentWhitelist(whitelist: selection)
130+
updateBlock(triggeredBy: "shieldAction")
131+
}
132+
133+
if type == "blockSelection" {
134+
if let familyActivitySelectionId = action["familyActivitySelectionId"] as? String {
135+
if let activitySelection = getFamilyActivitySelectionById(id: familyActivitySelectionId) {
136+
updateShield(
137+
shieldId: action["shieldId"] as? String,
138+
triggeredBy: triggeredBy,
139+
activitySelectionId: familyActivitySelectionId
140+
)
141+
142+
sleep(ms: 50)
143+
144+
blockSelectedApps(
145+
blockSelection: activitySelection,
146+
triggeredBy: triggeredBy
147+
)
148+
} else {
149+
logger.log("No familyActivitySelection found with ID: \(familyActivitySelectionId)")
150+
}
151+
}
152+
} else if type == "unblockSelection" {
153+
if let familyActivitySelectionId = action["familyActivitySelectionId"] as? String {
154+
if let activitySelection = getFamilyActivitySelectionById(id: familyActivitySelectionId) {
155+
156+
unblockSelection(
157+
removeSelection: activitySelection,
158+
triggeredBy: triggeredBy
159+
)
160+
161+
userDefaults?
162+
.removeObject(
163+
forKey: SHIELD_CONFIGURATION_FOR_SELECTION_PREFIX + "_" + familyActivitySelectionId)
164+
}
165+
}
166+
} else if type == "addSelectionToWhitelist" {
167+
if let familyActivitySelectionInput = action["familyActivitySelection"] as? [String: Any] {
168+
let selection = parseActivitySelectionInput(input: familyActivitySelectionInput)
169+
addSelectionToWhitelistAndUpdateBlock(
170+
whitelistSelection: selection,
171+
triggeredBy: triggeredBy
172+
)
173+
}
174+
} else if type == "removeSelectionFromWhitelist" {
175+
if let familyActivitySelectionInput = action["familyActivitySelection"] as? [String: Any] {
176+
let selection = parseActivitySelectionInput(input: familyActivitySelectionInput)
177+
removeSelectionFromWhitelistAndUpdateBlock(
178+
selection: selection,
179+
triggeredBy: triggeredBy
180+
)
181+
}
182+
} else if type == "clearWhitelistAndUpdateBlock" {
183+
logger.info("should clearWhitelistAndUpdateBlock")
184+
clearWhitelist()
185+
updateBlock(triggeredBy: triggeredBy)
186+
logger.info("done")
187+
} else if type == "resetBlocks" {
188+
resetBlocks(triggeredBy: triggeredBy)
189+
} else if type == "clearWhitelist" {
190+
clearWhitelist()
191+
} else if type == "disableBlockAllMode" {
192+
disableBlockAllMode(triggeredBy: triggeredBy)
193+
} else if type == "openApp" {
194+
// todo: replace with general string
195+
openUrl(urlString: "device-activity://")
196+
197+
sleep(ms: 1000)
198+
} else if type == "enableBlockAllMode" {
199+
updateShield(
200+
shieldId: action["shieldId"] as? String,
201+
triggeredBy: triggeredBy,
202+
activitySelectionId: nil
203+
)
204+
205+
// sometimes the shield doesn't pick up the shield config change above, trying a sleep to get around it
206+
sleep(ms: 50)
207+
208+
enableBlockAllMode(triggeredBy: triggeredBy)
209+
} else if type == "removeAllPendingNotificationRequests" {
210+
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
211+
} else if type == "removeAllDeliveredNotifications" {
212+
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
213+
} else if type == "removePendingNotificationRequests" {
214+
if let identifiers = action["identifiers"] as? [String] {
215+
UNUserNotificationCenter
216+
.current()
217+
.removePendingNotificationRequests(withIdentifiers: identifiers)
218+
}
219+
} else if type == "setBadgeCount" {
220+
let actionWithReplacedPlaceholders = replacePlaceholdersInObject(action, with: placeholders)
221+
if let count = actionWithReplacedPlaceholders["count"] as? Int {
222+
if #available(iOS 16.0, *) {
223+
UNUserNotificationCenter
224+
.current()
225+
.setBadgeCount(count)
226+
}
227+
}
228+
} else if type == "sendNotification" {
229+
if let notification = action["payload"] as? [String: Any] {
230+
sendNotification(contents: notification, placeholders: placeholders)
231+
}
232+
} else if type == "sendHttpRequest" {
233+
if let url = action["url"] as? String {
234+
let config = action["options"] as? [String: Any] ?? [:]
235+
236+
task = sendHttpRequest(with: url, config: config, placeholders: placeholders)
237+
238+
// required for it to have time to trigger before process/callback ends
239+
sleep(ms: 1000)
240+
}
241+
}
242+
243+
if let sleepAfter = action["sleepAfter"] as? Int {
244+
sleep(ms: sleepAfter)
245+
}
246+
}
247+
100248
func sendNotification(contents: [String: Any], placeholders: [String: String?]) {
101249
let content = UNMutableNotificationContent()
102250

packages/react-native-device-activity/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-device-activity",
3-
"version": "0.4.24",
3+
"version": "0.4.27",
44
"description": "Provides access to Apples DeviceActivity API",
55
"main": "build/index.js",
66
"types": "build/index.d.ts",

packages/react-native-device-activity/src/ReactNativeDeviceActivity.types.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export type DeviceActivityEvent = {
160160
export type ShieldActionType =
161161
| "disableBlockAllMode"
162162
| "dismiss"
163-
| "whitelistCurrent"
163+
| "addCurrentToWhitelist"
164164
| "unblockPossibleFamilyActivitySelection"
165165
| "unblockAllPossibleFamilyActivitySelections"
166166
| "whitelistPossibleFamilyActivitySelection"
@@ -170,14 +170,16 @@ export type ShieldActionType =
170170
| "openApp";
171171

172172
export type ShieldAction = {
173-
type: ShieldActionType;
173+
/** @deprecated use actions instead */
174+
type?: ShieldActionType;
174175
delay?: number;
175176
payload?: NotificationPayload;
176177
/**
177178
* defaults to true
178179
*/
179180
onlyFamilySelectionIdsContainingMonitoredActivityNames?: boolean;
180181
behavior: "close" | "defer";
182+
actions?: Action[];
181183
};
182184

183185
export type ShieldActions = {
@@ -267,6 +269,23 @@ export type Action =
267269
body?: Record<string, any>;
268270
headers?: Record<string, string>;
269271
};
272+
} & CommonTypeParams)
273+
| ({
274+
type: "setBadgeCount";
275+
count: number | string; // string for placeholder
276+
} & CommonTypeParams)
277+
| ({
278+
type: "removeAllPendingNotificationRequests";
279+
} & CommonTypeParams)
280+
| ({
281+
type: "removePendingNotificationRequests";
282+
identifiers: string[];
283+
} & CommonTypeParams)
284+
| ({
285+
type: "addCurrentToWhitelist";
286+
} & CommonTypeParams)
287+
| ({
288+
type: "removeAllDeliveredNotifications";
270289
} & CommonTypeParams);
271290

272291
export type DeviceActivityEventRaw = Omit<

packages/react-native-device-activity/targets/ActivityMonitorExtension/DeviceActivityMonitorExtension.swift

Lines changed: 5 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -9,109 +9,9 @@ import DeviceActivity
99
import FamilyControls
1010
import Foundation
1111
import ManagedSettings
12+
import NotificationCenter
1213
import os
1314

14-
func executeAction(action: [String: Any], placeholders: [String: String?], eventKey: String) {
15-
let type = action["type"] as? String
16-
17-
if let sleepBefore = action["sleepBefore"] as? Int {
18-
sleep(ms: sleepBefore)
19-
}
20-
21-
if type == "blockSelection" {
22-
if let familyActivitySelectionId = action["familyActivitySelectionId"] as? String {
23-
if let activitySelection = getFamilyActivitySelectionById(id: familyActivitySelectionId) {
24-
updateShield(
25-
shieldId: action["shieldId"] as? String,
26-
triggeredBy: eventKey,
27-
activitySelectionId: familyActivitySelectionId
28-
)
29-
30-
sleep(ms: 50)
31-
32-
blockSelectedApps(
33-
blockSelection: activitySelection,
34-
triggeredBy: eventKey
35-
)
36-
} else {
37-
logger.log("No familyActivitySelection found with ID: \(familyActivitySelectionId)")
38-
}
39-
}
40-
} else if type == "unblockSelection" {
41-
if let familyActivitySelectionId = action["familyActivitySelectionId"] as? String {
42-
if let activitySelection = getFamilyActivitySelectionById(id: familyActivitySelectionId) {
43-
44-
unblockSelection(
45-
removeSelection: activitySelection,
46-
triggeredBy: eventKey
47-
)
48-
49-
userDefaults?
50-
.removeObject(
51-
forKey: SHIELD_CONFIGURATION_FOR_SELECTION_PREFIX + "_" + familyActivitySelectionId)
52-
}
53-
}
54-
} else if type == "addSelectionToWhitelist" {
55-
if let familyActivitySelectionInput = action["familyActivitySelection"] as? [String: Any] {
56-
let selection = parseActivitySelectionInput(input: familyActivitySelectionInput)
57-
addSelectionToWhitelistAndUpdateBlock(
58-
whitelistSelection: selection,
59-
triggeredBy: eventKey
60-
)
61-
}
62-
} else if type == "removeSelectionFromWhitelist" {
63-
if let familyActivitySelectionInput = action["familyActivitySelection"] as? [String: Any] {
64-
let selection = parseActivitySelectionInput(input: familyActivitySelectionInput)
65-
removeSelectionFromWhitelistAndUpdateBlock(
66-
selection: selection,
67-
triggeredBy: eventKey
68-
)
69-
}
70-
} else if type == "clearWhitelistAndUpdateBlock" {
71-
clearWhitelist()
72-
updateBlock(triggeredBy: eventKey)
73-
} else if type == "resetBlocks" {
74-
resetBlocks(triggeredBy: eventKey)
75-
} else if type == "clearWhitelist" {
76-
clearWhitelist()
77-
} else if type == "disableBlockAllMode" {
78-
disableBlockAllMode(triggeredBy: eventKey)
79-
} else if type == "openApp" {
80-
// todo: replace with general string
81-
openUrl(urlString: "device-activity://")
82-
83-
sleep(ms: 1000)
84-
} else if type == "enableBlockAllMode" {
85-
updateShield(
86-
shieldId: action["shieldId"] as? String,
87-
triggeredBy: eventKey,
88-
activitySelectionId: nil
89-
)
90-
91-
// sometimes the shield doesn't pick up the shield config change above, trying a sleep to get around it
92-
sleep(ms: 50)
93-
94-
enableBlockAllMode(triggeredBy: eventKey)
95-
} else if type == "sendNotification" {
96-
if let notification = action["payload"] as? [String: Any] {
97-
sendNotification(contents: notification, placeholders: placeholders)
98-
}
99-
} else if type == "sendHttpRequest" {
100-
if let url = action["url"] as? String {
101-
let config = action["options"] as? [String: Any] ?? [:]
102-
103-
task = sendHttpRequest(with: url, config: config, placeholders: placeholders)
104-
105-
// required for it to have time to trigger before process/callback ends
106-
sleep(ms: 1000)
107-
}
108-
}
109-
110-
if let sleepAfter = action["sleepAfter"] as? Int {
111-
sleep(ms: sleepAfter)
112-
}
113-
}
114-
11515
class DeviceActivityMonitorExtension: DeviceActivityMonitor {
11616
override func intervalDidStart(for activity: DeviceActivityName) {
11717
super.intervalDidStart(for: activity)
@@ -154,7 +54,7 @@ class DeviceActivityMonitorExtension: DeviceActivityMonitor {
15454
callbackName: String,
15555
eventName: String?
15656
) {
157-
let key =
57+
let triggeredBy =
15858
eventName != nil
15959
? "actions_for_\(activityName)_\(callbackName)_\(eventName!)"
16060
: "actions_for_\(activityName)_\(callbackName)"
@@ -167,7 +67,7 @@ class DeviceActivityMonitorExtension: DeviceActivityMonitor {
16767

16868
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)
16969

170-
if let actions = userDefaults?.array(forKey: key) {
70+
if let actions = userDefaults?.array(forKey: triggeredBy) {
17171
actions.forEach { actionRaw in
17272
if let action = actionRaw as? [String: Any] {
17373
let skipIfAlreadyTriggeredAfter = action["skipIfAlreadyTriggeredAfter"] as? Double
@@ -200,10 +100,10 @@ class DeviceActivityMonitorExtension: DeviceActivityMonitor {
200100
callbackName: callbackName,
201101
eventName: eventName
202102
) {
203-
executeAction(
103+
executeGenericAction(
204104
action: action,
205105
placeholders: placeholders,
206-
eventKey: key
106+
triggeredBy: triggeredBy
207107
)
208108
}
209109
}

0 commit comments

Comments
 (0)