@elizaos/capacitor-appblocker 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.
@@ -0,0 +1,102 @@
1
+ import FamilyControls
2
+ import Foundation
3
+ import ManagedSettings
4
+
5
+ struct AppBlockerStoredState: Codable {
6
+ let tokenDataArray: [String]
7
+ let endsAtEpochMs: Double?
8
+ }
9
+
10
+ enum AppBlockerShared {
11
+ static let stateKey = "app_blocker_state_v1"
12
+ static let store = ManagedSettingsStore()
13
+ static let iso8601Formatter = ISO8601DateFormatter()
14
+
15
+ static func serializeToken(_ token: ApplicationToken) -> String? {
16
+ guard let data = try? JSONEncoder().encode(token) else {
17
+ return nil
18
+ }
19
+ return data.base64EncodedString()
20
+ }
21
+
22
+ static func serializeSelection(_ tokens: Set<ApplicationToken>) -> [String] {
23
+ tokens.compactMap(serializeToken).sorted()
24
+ }
25
+
26
+ static func deserializeTokens(_ tokenDataArray: [String]) -> Set<ApplicationToken> {
27
+ var tokens = Set<ApplicationToken>()
28
+ for tokenData in tokenDataArray {
29
+ guard let data = Data(base64Encoded: tokenData),
30
+ let token = try? JSONDecoder().decode(ApplicationToken.self, from: data) else {
31
+ continue
32
+ }
33
+ tokens.insert(token)
34
+ }
35
+ return tokens
36
+ }
37
+
38
+ static func loadState() -> AppBlockerStoredState? {
39
+ guard let data = UserDefaults.standard.data(forKey: stateKey) else {
40
+ return nil
41
+ }
42
+
43
+ guard let state = try? JSONDecoder().decode(AppBlockerStoredState.self, from: data) else {
44
+ UserDefaults.standard.removeObject(forKey: stateKey)
45
+ return nil
46
+ }
47
+
48
+ if let endsAtEpochMs = state.endsAtEpochMs,
49
+ endsAtEpochMs <= Date().timeIntervalSince1970 * 1000 {
50
+ UserDefaults.standard.removeObject(forKey: stateKey)
51
+ return nil
52
+ }
53
+
54
+ if state.tokenDataArray.isEmpty {
55
+ UserDefaults.standard.removeObject(forKey: stateKey)
56
+ return nil
57
+ }
58
+
59
+ return state
60
+ }
61
+
62
+ static func saveState(tokenDataArray: [String], endsAtEpochMs: Double?) {
63
+ let state = AppBlockerStoredState(
64
+ tokenDataArray: tokenDataArray,
65
+ endsAtEpochMs: endsAtEpochMs
66
+ )
67
+ guard let data = try? JSONEncoder().encode(state) else {
68
+ return
69
+ }
70
+ UserDefaults.standard.set(data, forKey: stateKey)
71
+ }
72
+
73
+ static func clearState() {
74
+ UserDefaults.standard.removeObject(forKey: stateKey)
75
+ }
76
+
77
+ static func endsAtString(for state: AppBlockerStoredState?) -> String? {
78
+ guard let endsAtEpochMs = state?.endsAtEpochMs else {
79
+ return nil
80
+ }
81
+ return iso8601Formatter.string(from: Date(timeIntervalSince1970: endsAtEpochMs / 1000))
82
+ }
83
+
84
+ static func applyShield(tokens: Set<ApplicationToken>) {
85
+ store.shield.applications = tokens
86
+ }
87
+
88
+ static func clearShield() {
89
+ store.shield.applications = nil
90
+ }
91
+
92
+ static func startBlock(tokens: Set<ApplicationToken>, endsAtEpochMs: Double? = nil) {
93
+ let tokenDataArray = serializeSelection(tokens)
94
+ saveState(tokenDataArray: tokenDataArray, endsAtEpochMs: endsAtEpochMs)
95
+ applyShield(tokens: tokens)
96
+ }
97
+
98
+ static func stopBlock() {
99
+ clearState()
100
+ clearShield()
101
+ }
102
+ }
@@ -0,0 +1,47 @@
1
+ import FamilyControls
2
+ import ManagedSettings
3
+ import SwiftUI
4
+ import UIKit
5
+
6
+ @MainActor
7
+ enum FamilyActivityPickerBridge {
8
+ static func present(
9
+ from viewController: UIViewController,
10
+ completion: @escaping ([ManagedSettings.ApplicationToken], Bool) -> Void
11
+ ) {
12
+ let pickerController = UIHostingController(
13
+ rootView: PickerWrapper(completion: completion)
14
+ )
15
+ pickerController.modalPresentationStyle = UIModalPresentationStyle.formSheet
16
+ viewController.present(pickerController, animated: true)
17
+ }
18
+ }
19
+
20
+ private struct PickerWrapper: View {
21
+ @Environment(\.dismiss) private var dismiss
22
+ @State private var selection = FamilyActivitySelection()
23
+
24
+ let completion: ([ManagedSettings.ApplicationToken], Bool) -> Void
25
+
26
+ var body: some View {
27
+ NavigationView {
28
+ FamilyActivityPicker(selection: $selection)
29
+ .navigationTitle("Select Apps")
30
+ .toolbar {
31
+ ToolbarItem(placement: .cancellationAction) {
32
+ Button("Cancel") {
33
+ completion([], true)
34
+ dismiss()
35
+ }
36
+ }
37
+ ToolbarItem(placement: .confirmationAction) {
38
+ Button("Done") {
39
+ completion(Array(selection.applicationTokens), false)
40
+ dismiss()
41
+ }
42
+ }
43
+ }
44
+ }
45
+ .navigationViewStyle(StackNavigationViewStyle())
46
+ }
47
+ }
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@elizaos/capacitor-appblocker",
3
+ "version": "1.0.0",
4
+ "description": "Blocks selected apps on Android with Usage Access and an overlay shield, and on iPhone with Family Controls.",
5
+ "keywords": [
6
+ "app-blocker",
7
+ "focus",
8
+ "family-controls",
9
+ "usage-stats"
10
+ ],
11
+ "main": "./dist/plugin.cjs.js",
12
+ "module": "./dist/esm/index.js",
13
+ "types": "./dist/esm/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/esm/index.d.ts",
17
+ "import": "./dist/esm/index.js",
18
+ "require": "./dist/plugin.cjs.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "unpkg": "dist/plugin.js",
23
+ "files": [
24
+ "android/src/main/",
25
+ "android/build.gradle",
26
+ "dist/",
27
+ "ios/Sources/",
28
+ "*.podspec"
29
+ ],
30
+ "scripts": {
31
+ "build": "npm run clean && tsc && rollup -c rollup.config.mjs",
32
+ "clean": "rimraf ./dist",
33
+ "prepublishOnly": "npm run build"
34
+ },
35
+ "author": "elizaOS",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/elizaOS/eliza.git",
40
+ "directory": "packages/native-plugins/appblocker"
41
+ },
42
+ "capacitor": {
43
+ "ios": {
44
+ "src": "ios",
45
+ "podName": "ElizaosCapacitorAppblocker"
46
+ },
47
+ "android": {
48
+ "src": "android"
49
+ }
50
+ },
51
+ "devDependencies": {
52
+ "@capacitor/cli": "^8.0.0",
53
+ "@capacitor/core": "^8.3.1",
54
+ "rimraf": "^6.0.0",
55
+ "rollup": "^4.60.2",
56
+ "typescript": "^6.0.0"
57
+ },
58
+ "peerDependencies": {
59
+ "@capacitor/core": "^8.3.1"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "elizaos": {
65
+ "platforms": [
66
+ "browser",
67
+ "android",
68
+ "ios"
69
+ ],
70
+ "runtime": "native",
71
+ "platformDetails": {
72
+ "browser": "Not available in browsers.",
73
+ "ios": "Uses Family Controls and Managed Settings to shield selected apps.",
74
+ "android": "Uses Usage Access polling plus a system overlay shield."
75
+ }
76
+ }
77
+ }