expo-app-blocker 0.1.35 → 0.1.37

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.
@@ -161,6 +161,7 @@ public class ExpoAppBlockerModule: Module {
161
161
  self.cancelRelockActivity()
162
162
  self.store.shield.applications = nil
163
163
  self.store.shield.applicationCategories = nil
164
+ self.store.shield.webDomains = nil
164
165
  self.currentBlockConfig = nil
165
166
  self.userDefaults.removeObject(forKey: self.blockConfigStorageKey)
166
167
  self.sharedDefaults?.removeObject(forKey: self.blockConfigStorageKey)
@@ -204,6 +205,7 @@ public class ExpoAppBlockerModule: Module {
204
205
  DispatchQueue.main.async {
205
206
  self.store.shield.applications = nil
206
207
  self.store.shield.applicationCategories = nil
208
+ self.store.shield.webDomains = nil
207
209
  }
208
210
 
209
211
  // Try to schedule relock, but don't fail if schedule is too short
@@ -353,6 +355,7 @@ public class ExpoAppBlockerModule: Module {
353
355
  DispatchQueue.main.async {
354
356
  self.store.shield.applications = nil
355
357
  self.store.shield.applicationCategories = nil
358
+ self.store.shield.webDomains = nil
356
359
  }
357
360
 
358
361
  do {
@@ -393,16 +396,26 @@ public class ExpoAppBlockerModule: Module {
393
396
  }
394
397
 
395
398
  let itemTypeRaw = (selection["type"] as? String ?? "app").lowercased()
396
- let itemType: BlockedItemType = itemTypeRaw == "category" ? .category : .app
399
+ let itemType: BlockedItemType
400
+ switch itemTypeRaw {
401
+ case "category":
402
+ itemType = .category
403
+ case "webdomain":
404
+ itemType = .webDomain
405
+ default:
406
+ itemType = .app
407
+ }
397
408
 
398
409
  return BlockedItemInfo(
399
410
  type: itemType,
400
411
  tokenId: tokenString,
401
412
  appToken: itemType == .app ? self.decodeApplicationToken(from: tokenString) : nil,
402
413
  categoryToken: itemType == .category ? self.decodeCategoryToken(from: tokenString) : nil,
414
+ webDomainToken: itemType == .webDomain ? self.decodeWebDomainToken(from: tokenString) : nil,
403
415
  bundleIdentifier: selection["bundleIdentifier"] as? String,
404
416
  displayName: selection["displayName"] as? String,
405
417
  categoryName: selection["categoryName"] as? String,
418
+ domain: selection["domain"] as? String,
406
419
  iconBase64: selection["iconBase64"] as? String
407
420
  )
408
421
  }
@@ -426,21 +439,25 @@ public class ExpoAppBlockerModule: Module {
426
439
  guard config.isActive else {
427
440
  store.shield.applications = nil
428
441
  store.shield.applicationCategories = nil
442
+ store.shield.webDomains = nil
429
443
  return
430
444
  }
431
445
 
432
446
  if isTemporarilyUnlockedInternal() {
433
447
  store.shield.applications = nil
434
448
  store.shield.applicationCategories = nil
449
+ store.shield.webDomains = nil
435
450
  return
436
451
  }
437
452
 
438
453
  let validAppTokens = config.items.compactMap { $0.appToken }
439
454
  let validCategoryTokens = config.items.compactMap { $0.categoryToken }
455
+ let validWebDomainTokens = config.items.compactMap { $0.webDomainToken }
440
456
 
441
- guard !validAppTokens.isEmpty || !validCategoryTokens.isEmpty else {
457
+ guard !validAppTokens.isEmpty || !validCategoryTokens.isEmpty || !validWebDomainTokens.isEmpty else {
442
458
  store.shield.applications = nil
443
459
  store.shield.applicationCategories = nil
460
+ store.shield.webDomains = nil
444
461
  return
445
462
  }
446
463
 
@@ -455,6 +472,12 @@ public class ExpoAppBlockerModule: Module {
455
472
  } else {
456
473
  store.shield.applicationCategories = nil
457
474
  }
475
+
476
+ if !validWebDomainTokens.isEmpty {
477
+ store.shield.webDomains = Set(validWebDomainTokens)
478
+ } else {
479
+ store.shield.webDomains = nil
480
+ }
458
481
  }
459
482
 
460
483
  private func relockApps() {
@@ -558,6 +581,10 @@ public class ExpoAppBlockerModule: Module {
558
581
  if let token = tokenInfo.categoryToken, let encoded = self.encodeCategoryToken(token) {
559
582
  tokenId = encoded
560
583
  }
584
+ case .webDomain:
585
+ if let token = tokenInfo.webDomainToken, let encoded = self.encodeWebDomainToken(token) {
586
+ tokenId = encoded
587
+ }
561
588
  }
562
589
  }
563
590
 
@@ -579,6 +606,9 @@ public class ExpoAppBlockerModule: Module {
579
606
  if let categoryName = tokenInfo.categoryName {
580
607
  dict["categoryName"] = categoryName
581
608
  }
609
+ if let domain = tokenInfo.domain {
610
+ dict["domain"] = domain
611
+ }
582
612
  if let iconBase64 = tokenInfo.iconBase64 {
583
613
  dict["iconBase64"] = iconBase64
584
614
  }
@@ -669,6 +699,22 @@ public class ExpoAppBlockerModule: Module {
669
699
  return Self.decodeCategoryTokenStatic(from: encoded)
670
700
  }
671
701
 
702
+ private func encodeWebDomainToken(_ token: WebDomainToken) -> String? {
703
+ do {
704
+ let data = try JSONEncoder().encode(token)
705
+ return data.base64EncodedString()
706
+ } catch {
707
+ return nil
708
+ }
709
+ }
710
+
711
+ private func decodeWebDomainToken(from encoded: String) -> WebDomainToken? {
712
+ guard let data = Data(base64Encoded: encoded) else {
713
+ return nil
714
+ }
715
+ return try? JSONDecoder().decode(WebDomainToken.self, from: data)
716
+ }
717
+
672
718
  // Static versions for use in View prop closures
673
719
  static func decodeApplicationTokenStatic(from encoded: String) -> ApplicationToken? {
674
720
  guard let data = Data(base64Encoded: encoded) else { return nil }
@@ -789,6 +835,7 @@ struct BlockedAppsContentView: View {
789
835
  enum BlockedItemType: String {
790
836
  case app
791
837
  case category
838
+ case webDomain
792
839
  }
793
840
 
794
841
  struct BlockedItemInfo {
@@ -796,9 +843,11 @@ struct BlockedItemInfo {
796
843
  let tokenId: String
797
844
  let appToken: ApplicationToken?
798
845
  let categoryToken: ActivityCategoryToken?
846
+ let webDomainToken: WebDomainToken?
799
847
  let bundleIdentifier: String?
800
848
  let displayName: String?
801
849
  let categoryName: String?
850
+ let domain: String?
802
851
  let iconBase64: String?
803
852
  }
804
853
 
@@ -932,17 +981,30 @@ struct FamilyActivityPickerView: View {
932
981
  ]
933
982
  }
934
983
 
984
+ let webDomainItems: [[String: Any]] = selection.webDomainTokens.compactMap { webDomainToken in
985
+ guard let tokenId = encodeSelectionWebDomainToken(webDomainToken) else {
986
+ return nil
987
+ }
988
+ let domain = String(describing: webDomainToken)
989
+ return [
990
+ "type": "webDomain",
991
+ "token": tokenId,
992
+ "domain": domain
993
+ ]
994
+ }
995
+
935
996
  // Serialize the full FamilyActivitySelection for the native view
936
997
  var selectionBase64 = ""
937
998
  if let selectionData = try? JSONEncoder().encode(selection) {
938
999
  selectionBase64 = selectionData.base64EncodedString()
939
1000
  }
940
1001
 
941
- var result: [[String: Any]] = appItems + categoryItems
1002
+ var result: [[String: Any]] = appItems + categoryItems + webDomainItems
942
1003
  result.append([
943
1004
  "type": "summary",
944
1005
  "totalApps": selection.applications.count,
945
1006
  "totalCategories": selection.categoryTokens.count,
1007
+ "totalWebDomains": selection.webDomainTokens.count,
946
1008
  "selectionData": selectionBase64
947
1009
  ])
948
1010
 
@@ -970,6 +1032,15 @@ struct FamilyActivityPickerView: View {
970
1032
  }
971
1033
  }
972
1034
 
1035
+ private func encodeSelectionWebDomainToken(_ token: WebDomainToken) -> String? {
1036
+ do {
1037
+ let data = try JSONEncoder().encode(token)
1038
+ return data.base64EncodedString()
1039
+ } catch {
1040
+ return nil
1041
+ }
1042
+ }
1043
+
973
1044
  private func resolveCategoryName(_ token: ActivityCategoryToken) -> String {
974
1045
  let raw = String(describing: token)
975
1046
  return raw.isEmpty ? "Category" : raw
@@ -136,12 +136,23 @@ class FamilyActivityPickerNativeView: ExpoView {
136
136
  }
137
137
  }
138
138
 
139
+ var webDomainItems: [[String: Any]] = []
140
+ for token in selection.webDomainTokens {
141
+ if let data = try? JSONEncoder().encode(token) {
142
+ webDomainItems.append([
143
+ "type": "webDomain",
144
+ "token": data.base64EncodedString(),
145
+ "domain": String(describing: token)
146
+ ])
147
+ }
148
+ }
149
+
139
150
  var selectionBase64 = ""
140
151
  if let selectionData = try? JSONEncoder().encode(selection) {
141
152
  selectionBase64 = selectionData.base64EncodedString()
142
153
  }
143
154
 
144
- let items = appItems + categoryItems
155
+ let items = appItems + categoryItems + webDomainItems
145
156
 
146
157
  // Do not append a synthetic "summary" row to `items` — JS counts `items.length` for UI and
147
158
  // `totalApps` / `totalCategories` / `selectionData` already carry the same metadata.
@@ -149,6 +160,7 @@ class FamilyActivityPickerNativeView: ExpoView {
149
160
  "items": items,
150
161
  "totalApps": selection.applicationTokens.count,
151
162
  "totalCategories": selection.categoryTokens.count,
163
+ "totalWebDomains": selection.webDomainTokens.count,
152
164
  "selectionData": selectionBase64
153
165
  ])
154
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-app-blocker",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
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",
@@ -30,11 +30,12 @@ export interface AndroidBlockableApp {
30
30
  }
31
31
 
32
32
  export interface IOSBlockedItem {
33
- type: "app" | "category";
33
+ type: "app" | "category" | "webDomain";
34
34
  token: string;
35
35
  bundleIdentifier?: string;
36
36
  displayName?: string;
37
37
  categoryName?: string;
38
+ domain?: string;
38
39
  iconBase64?: string;
39
40
  }
40
41
 
@@ -63,12 +64,14 @@ export interface RelockResult {
63
64
  }
64
65
 
65
66
  export interface FamilyActivityPickerSelectionEvent {
66
- /** Selected apps and categories (pass to setBlockConfiguration) */
67
+ /** Selected apps, categories, and web domains (pass to setBlockConfiguration) */
67
68
  items: IOSBlockedItem[];
68
69
  /** Number of individual apps selected */
69
70
  totalApps: number;
70
71
  /** Number of categories selected */
71
72
  totalCategories: number;
73
+ /** Number of web domains selected */
74
+ totalWebDomains: number;
72
75
  /** Base64 string - save and pass back as initialSelection to restore state */
73
76
  selectionData: string;
74
77
  }
package/src/index.ts CHANGED
@@ -262,7 +262,9 @@ export function FamilyActivityPickerView({
262
262
  const ne = e.nativeEvent;
263
263
  const items = (ne.items ?? []).filter(
264
264
  (item: { type?: string }) =>
265
- item?.type === "app" || item?.type === "category",
265
+ item?.type === "app" ||
266
+ item?.type === "category" ||
267
+ item?.type === "webDomain",
266
268
  );
267
269
  onSelectionChange({ ...ne, items });
268
270
  }
@@ -34,6 +34,7 @@ class AppBlockerDeviceActivityMonitor: DeviceActivityMonitor {
34
34
  guard let configDict = userDefaults.dictionary(forKey: blockConfigStorageKey) else {
35
35
  store.shield.applications = nil
36
36
  store.shield.applicationCategories = nil
37
+ store.shield.webDomains = nil
37
38
  return
38
39
  }
39
40
 
@@ -64,13 +65,22 @@ class AppBlockerDeviceActivityMonitor: DeviceActivityMonitor {
64
65
  }
65
66
 
66
67
  let itemTypeRaw = (selection["type"] as? String ?? "app").lowercased()
67
- let itemType: MonitorBlockedItemType = itemTypeRaw == "category" ? .category : .app
68
+ let itemType: MonitorBlockedItemType
69
+ switch itemTypeRaw {
70
+ case "category":
71
+ itemType = .category
72
+ case "webdomain":
73
+ itemType = .webDomain
74
+ default:
75
+ itemType = .app
76
+ }
68
77
 
69
78
  return MonitorBlockedItemInfo(
70
79
  type: itemType,
71
80
  tokenId: tokenString,
72
81
  appToken: itemType == .app ? decodeApplicationToken(from: tokenString) : nil,
73
- categoryToken: itemType == .category ? decodeCategoryToken(from: tokenString) : nil
82
+ categoryToken: itemType == .category ? decodeCategoryToken(from: tokenString) : nil,
83
+ webDomainToken: itemType == .webDomain ? decodeWebDomainToken(from: tokenString) : nil
74
84
  )
75
85
  }
76
86
 
@@ -82,15 +92,18 @@ class AppBlockerDeviceActivityMonitor: DeviceActivityMonitor {
82
92
  guard config.isActive else {
83
93
  store.shield.applications = nil
84
94
  store.shield.applicationCategories = nil
95
+ store.shield.webDomains = nil
85
96
  return
86
97
  }
87
98
 
88
99
  let validAppTokens = config.items.compactMap { $0.appToken }
89
100
  let validCategoryTokens = config.items.compactMap { $0.categoryToken }
101
+ let validWebDomainTokens = config.items.compactMap { $0.webDomainToken }
90
102
 
91
- guard !validAppTokens.isEmpty || !validCategoryTokens.isEmpty else {
103
+ guard !validAppTokens.isEmpty || !validCategoryTokens.isEmpty || !validWebDomainTokens.isEmpty else {
92
104
  store.shield.applications = nil
93
105
  store.shield.applicationCategories = nil
106
+ store.shield.webDomains = nil
94
107
  return
95
108
  }
96
109
 
@@ -105,6 +118,12 @@ class AppBlockerDeviceActivityMonitor: DeviceActivityMonitor {
105
118
  } else {
106
119
  store.shield.applicationCategories = nil
107
120
  }
121
+
122
+ if !validWebDomainTokens.isEmpty {
123
+ store.shield.webDomains = Set(validWebDomainTokens)
124
+ } else {
125
+ store.shield.webDomains = nil
126
+ }
108
127
  }
109
128
 
110
129
  private func decodeApplicationToken(from encoded: String) -> ApplicationToken? {
@@ -130,11 +149,24 @@ class AppBlockerDeviceActivityMonitor: DeviceActivityMonitor {
130
149
  return nil
131
150
  }
132
151
  }
152
+
153
+ private func decodeWebDomainToken(from encoded: String) -> WebDomainToken? {
154
+ guard let data = Data(base64Encoded: encoded) else {
155
+ return nil
156
+ }
157
+
158
+ do {
159
+ return try JSONDecoder().decode(WebDomainToken.self, from: data)
160
+ } catch {
161
+ return nil
162
+ }
163
+ }
133
164
  }
134
165
 
135
166
  enum MonitorBlockedItemType: String {
136
167
  case app
137
168
  case category
169
+ case webDomain
138
170
  }
139
171
 
140
172
  struct MonitorBlockedItemInfo {
@@ -142,6 +174,7 @@ struct MonitorBlockedItemInfo {
142
174
  let tokenId: String
143
175
  let appToken: ApplicationToken?
144
176
  let categoryToken: ActivityCategoryToken?
177
+ let webDomainToken: WebDomainToken?
145
178
  }
146
179
 
147
180
  struct MonitorBlockConfig {