expo-app-blocker 0.1.68 → 0.1.69
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-app-blocker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.69",
|
|
4
4
|
"description": "Expo module for cross-platform app blocking. Android: UsageStatsManager + Overlay. iOS: Screen Time API (FamilyControls + ManagedSettings + DeviceActivity).",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -6,6 +6,11 @@ import UserNotifications
|
|
|
6
6
|
class ShieldActionExtension: ShieldActionDelegate {
|
|
7
7
|
private let appGroupIdentifier = "APP_GROUP_PLACEHOLDER"
|
|
8
8
|
private let pendingUnlockKey = "appBlocker.pendingUnlock.v1"
|
|
9
|
+
private let pendingInterceptsKey = "appBlocker.pendingIntercepts.v1"
|
|
10
|
+
private let lastInterceptTsKey = "appBlocker.lastInterceptTs.v1"
|
|
11
|
+
private let shieldInvokeCountKey = "appBlocker.shieldInvokeCount.v1"
|
|
12
|
+
private let interceptDebounceMs: Double = 2_000
|
|
13
|
+
private let maxPendingIntercepts = 200
|
|
9
14
|
private let pendingUnlockNotificationIdentifier = "expo.appblocker.pendingUnlock.local"
|
|
10
15
|
// Notification copy + behavior — configurable via plugin options so apps
|
|
11
16
|
// can localize without forking. Defaults preserve the original English
|
|
@@ -27,6 +32,11 @@ class ShieldActionExtension: ShieldActionDelegate {
|
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
private func handleAction(_ action: ShieldAction, completionHandler: @escaping (ShieldActionResponse) -> Void) {
|
|
35
|
+
// Any interaction with the shield is a confirmed block event. The
|
|
36
|
+
// ShieldConfiguration data source is cached by the system and not
|
|
37
|
+
// re-invoked per open, so this — the action handler, which fires every
|
|
38
|
+
// time — is the reliable place to record the block.
|
|
39
|
+
recordIntercept()
|
|
30
40
|
switch action {
|
|
31
41
|
case .primaryButtonPressed:
|
|
32
42
|
setPendingUnlockFlag()
|
|
@@ -54,6 +64,41 @@ class ShieldActionExtension: ShieldActionDelegate {
|
|
|
54
64
|
}
|
|
55
65
|
}
|
|
56
66
|
|
|
67
|
+
/// Queue a block event (JSON-string queue in the App Group), debounced,
|
|
68
|
+
/// for the app to drain into `blocker_intercepts`.
|
|
69
|
+
private func recordIntercept() {
|
|
70
|
+
guard let defaults = UserDefaults(suiteName: appGroupIdentifier) else { return }
|
|
71
|
+
defaults.synchronize()
|
|
72
|
+
|
|
73
|
+
let invokes = defaults.integer(forKey: shieldInvokeCountKey) + 1
|
|
74
|
+
defaults.set(invokes, forKey: shieldInvokeCountKey)
|
|
75
|
+
|
|
76
|
+
let nowMs = Date().timeIntervalSince1970 * 1000.0
|
|
77
|
+
let lastMs = defaults.double(forKey: lastInterceptTsKey)
|
|
78
|
+
if lastMs > 0, (nowMs - lastMs) < interceptDebounceMs {
|
|
79
|
+
defaults.synchronize()
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
var queue: [[String: Any]] = []
|
|
84
|
+
if let json = defaults.string(forKey: pendingInterceptsKey),
|
|
85
|
+
let data = json.data(using: .utf8),
|
|
86
|
+
let parsed = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] {
|
|
87
|
+
queue = parsed
|
|
88
|
+
}
|
|
89
|
+
queue.append(["appName": NSNull(), "interceptedAt": nowMs])
|
|
90
|
+
if queue.count > maxPendingIntercepts {
|
|
91
|
+
queue = Array(queue.suffix(maxPendingIntercepts))
|
|
92
|
+
}
|
|
93
|
+
if let data = try? JSONSerialization.data(withJSONObject: queue),
|
|
94
|
+
let json = String(data: data, encoding: .utf8) {
|
|
95
|
+
defaults.set(json, forKey: pendingInterceptsKey)
|
|
96
|
+
}
|
|
97
|
+
defaults.set(nowMs, forKey: lastInterceptTsKey)
|
|
98
|
+
defaults.synchronize()
|
|
99
|
+
NSLog("[appblocker] ShieldAction recordIntercept queued depth=\(queue.count) invokes=\(invokes)")
|
|
100
|
+
}
|
|
101
|
+
|
|
57
102
|
private func setPendingUnlockFlag() {
|
|
58
103
|
guard let sharedDefaults = UserDefaults(suiteName: appGroupIdentifier) else { return }
|
|
59
104
|
sharedDefaults.set(true, forKey: pendingUnlockKey)
|