@elizaos/capacitor-websiteblocker 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,205 @@
1
+ package ai.eliza.plugins.websiteblocker
2
+
3
+ import android.content.Context
4
+
5
+ data class SavedWebsiteBlock(
6
+ val requestedWebsites: List<String>,
7
+ val blockedWebsites: List<String>,
8
+ val allowedWebsites: List<String>,
9
+ val matchMode: String,
10
+ val endsAtEpochMs: Long?,
11
+ )
12
+
13
+ object WebsiteBlockerStateStore {
14
+ private const val PREFS_NAME = "eliza_website_blocker"
15
+ private const val KEY_REQUESTED_WEBSITES = "requested_websites"
16
+ private const val KEY_BLOCKED_WEBSITES = "blocked_websites"
17
+ private const val KEY_ALLOWED_WEBSITES = "allowed_websites"
18
+ private const val KEY_MATCH_MODE = "match_mode"
19
+ private const val KEY_WEBSITES = "websites"
20
+ private const val KEY_ENDS_AT = "ends_at_epoch_ms"
21
+ private const val MATCH_MODE_EXACT = "exact"
22
+ private const val MATCH_MODE_SUBDOMAIN = "subdomain"
23
+
24
+ private val X_TWITTER_REQUESTED_HOSTS = setOf("x.com", "twitter.com")
25
+ private val X_TWITTER_BLOCKED_HOSTS = setOf(
26
+ "x.com",
27
+ "www.x.com",
28
+ "mobile.x.com",
29
+ "twitter.com",
30
+ "www.twitter.com",
31
+ "mobile.twitter.com",
32
+ "t.co",
33
+ "abs.twimg.com",
34
+ "pbs.twimg.com",
35
+ "video.twimg.com",
36
+ "ton.twimg.com",
37
+ "platform.twitter.com",
38
+ "tweetdeck.twitter.com",
39
+ )
40
+ private val X_TWITTER_ALLOWED_HOSTS = setOf("api.x.com", "api.twitter.com")
41
+ private val GOOGLE_NEWS_REQUESTED_HOSTS = setOf("news.google.com")
42
+ private val GOOGLE_NEWS_BLOCKED_HOSTS = setOf("news.google.com")
43
+ private val GOOGLE_NEWS_ALLOWED_HOSTS = setOf(
44
+ "accounts.google.com",
45
+ "oauth2.googleapis.com",
46
+ "openidconnect.googleapis.com",
47
+ "www.googleapis.com",
48
+ )
49
+
50
+ fun normalizeHostname(value: String): String? {
51
+ val trimmed = value.trim().trim('.').lowercase()
52
+ if (trimmed.isEmpty()) {
53
+ return null
54
+ }
55
+ if (!trimmed.contains('.')) {
56
+ return null
57
+ }
58
+ if (!trimmed.matches(Regex("^[a-z0-9.-]+$"))) {
59
+ return null
60
+ }
61
+ if (trimmed.startsWith(".") || trimmed.endsWith(".")) {
62
+ return null
63
+ }
64
+ return trimmed
65
+ }
66
+
67
+ private data class WebsiteBlockPolicy(
68
+ val requestedWebsites: List<String>,
69
+ val blockedWebsites: List<String>,
70
+ val allowedWebsites: List<String>,
71
+ val matchMode: String,
72
+ )
73
+
74
+ private fun shouldAddWwwVariant(hostname: String): Boolean {
75
+ val parts = hostname.split(".")
76
+ return parts.size == 2 && parts[0] != "www"
77
+ }
78
+
79
+ private fun buildPolicy(requestedWebsites: Collection<String>): WebsiteBlockPolicy {
80
+ val normalizedRequested = requestedWebsites.mapNotNull(::normalizeHostname)
81
+ .distinct()
82
+ .sorted()
83
+ val blockedWebsites = linkedSetOf<String>()
84
+ val allowedWebsites = linkedSetOf<String>()
85
+
86
+ for (website in normalizedRequested) {
87
+ blockedWebsites += website
88
+
89
+ if (shouldAddWwwVariant(website)) {
90
+ blockedWebsites += "www.$website"
91
+ }
92
+
93
+ when {
94
+ website in X_TWITTER_REQUESTED_HOSTS -> {
95
+ blockedWebsites += X_TWITTER_BLOCKED_HOSTS
96
+ allowedWebsites += X_TWITTER_ALLOWED_HOSTS
97
+ }
98
+ website in GOOGLE_NEWS_REQUESTED_HOSTS -> {
99
+ blockedWebsites += GOOGLE_NEWS_BLOCKED_HOSTS
100
+ allowedWebsites += GOOGLE_NEWS_ALLOWED_HOSTS
101
+ }
102
+ }
103
+ }
104
+
105
+ return WebsiteBlockPolicy(
106
+ requestedWebsites = normalizedRequested,
107
+ blockedWebsites = blockedWebsites.mapNotNull(::normalizeHostname).distinct().sorted(),
108
+ allowedWebsites = allowedWebsites.mapNotNull(::normalizeHostname).distinct().sorted(),
109
+ matchMode = MATCH_MODE_EXACT,
110
+ )
111
+ }
112
+
113
+ private fun readNormalizedWebsiteSet(prefs: android.content.SharedPreferences, key: String): List<String> {
114
+ return prefs.getStringSet(key, null)
115
+ ?.mapNotNull(::normalizeHostname)
116
+ ?.distinct()
117
+ ?.sorted()
118
+ .orEmpty()
119
+ }
120
+
121
+ fun load(context: Context): SavedWebsiteBlock? {
122
+ val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
123
+ val requestedWebsites = readNormalizedWebsiteSet(prefs, KEY_REQUESTED_WEBSITES)
124
+ .ifEmpty { readNormalizedWebsiteSet(prefs, KEY_WEBSITES) }
125
+ if (requestedWebsites.isEmpty()) {
126
+ return null
127
+ }
128
+
129
+ val policy = buildPolicy(requestedWebsites)
130
+ val blockedWebsites = readNormalizedWebsiteSet(prefs, KEY_BLOCKED_WEBSITES)
131
+ .ifEmpty { policy.blockedWebsites }
132
+ val allowedWebsites = readNormalizedWebsiteSet(prefs, KEY_ALLOWED_WEBSITES)
133
+ .ifEmpty { policy.allowedWebsites }
134
+ val matchMode = prefs.getString(KEY_MATCH_MODE, MATCH_MODE_EXACT)
135
+ ?.lowercase()
136
+ ?.takeIf { it == MATCH_MODE_SUBDOMAIN }
137
+ ?: MATCH_MODE_EXACT
138
+ val endsAtValue = prefs.getLong(KEY_ENDS_AT, -1L)
139
+ val endsAt = if (endsAtValue > 0) endsAtValue else null
140
+ if (endsAt != null && endsAt <= System.currentTimeMillis()) {
141
+ clear(context)
142
+ return null
143
+ }
144
+
145
+ return SavedWebsiteBlock(
146
+ requestedWebsites = requestedWebsites,
147
+ blockedWebsites = blockedWebsites,
148
+ allowedWebsites = allowedWebsites,
149
+ matchMode = matchMode,
150
+ endsAtEpochMs = endsAt,
151
+ )
152
+ }
153
+
154
+ fun save(
155
+ context: Context,
156
+ websites: Collection<String>,
157
+ endsAtEpochMs: Long?,
158
+ ): SavedWebsiteBlock? {
159
+ val policy = buildPolicy(websites)
160
+ if (policy.requestedWebsites.isEmpty()) {
161
+ clear(context)
162
+ return null
163
+ }
164
+
165
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
166
+ .edit()
167
+ .putStringSet(KEY_REQUESTED_WEBSITES, policy.requestedWebsites.toSet())
168
+ .putStringSet(KEY_BLOCKED_WEBSITES, policy.blockedWebsites.toSet())
169
+ .putStringSet(KEY_ALLOWED_WEBSITES, policy.allowedWebsites.toSet())
170
+ .putString(KEY_MATCH_MODE, policy.matchMode)
171
+ .putStringSet(KEY_WEBSITES, policy.requestedWebsites.toSet())
172
+ .putLong(KEY_ENDS_AT, endsAtEpochMs ?: -1L)
173
+ .apply()
174
+ return SavedWebsiteBlock(
175
+ requestedWebsites = policy.requestedWebsites,
176
+ blockedWebsites = policy.blockedWebsites,
177
+ allowedWebsites = policy.allowedWebsites,
178
+ matchMode = policy.matchMode,
179
+ endsAtEpochMs = endsAtEpochMs,
180
+ )
181
+ }
182
+
183
+ fun clear(context: Context) {
184
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
185
+ .edit()
186
+ .remove(KEY_REQUESTED_WEBSITES)
187
+ .remove(KEY_BLOCKED_WEBSITES)
188
+ .remove(KEY_ALLOWED_WEBSITES)
189
+ .remove(KEY_MATCH_MODE)
190
+ .remove(KEY_WEBSITES)
191
+ .remove(KEY_ENDS_AT)
192
+ .apply()
193
+ }
194
+
195
+ fun isBlockedHostname(policy: SavedWebsiteBlock, queryName: String): Boolean {
196
+ val normalizedQuery = normalizeHostname(queryName) ?: return false
197
+ if (policy.allowedWebsites.any { allowed -> normalizedQuery == allowed }) {
198
+ return false
199
+ }
200
+ return policy.blockedWebsites.any { blocked ->
201
+ normalizedQuery == blocked ||
202
+ (policy.matchMode == MATCH_MODE_SUBDOMAIN && normalizedQuery.endsWith(".$blocked"))
203
+ }
204
+ }
205
+ }
@@ -0,0 +1,326 @@
1
+ package ai.eliza.plugins.websiteblocker
2
+
3
+ import android.app.Notification
4
+ import android.app.NotificationChannel
5
+ import android.app.NotificationManager
6
+ import android.content.Context
7
+ import android.content.Intent
8
+ import android.content.pm.ServiceInfo
9
+ import android.net.ConnectivityManager
10
+ import android.net.VpnService
11
+ import android.os.Build
12
+ import android.os.Handler
13
+ import android.os.Looper
14
+ import android.os.ParcelFileDescriptor
15
+ import java.io.FileInputStream
16
+ import java.io.FileOutputStream
17
+ import java.net.DatagramPacket
18
+ import java.net.DatagramSocket
19
+ import java.net.Inet4Address
20
+ import java.net.InetAddress
21
+ import java.net.InetSocketAddress
22
+ import java.util.concurrent.atomic.AtomicBoolean
23
+
24
+ class WebsiteBlockerVpnService : VpnService() {
25
+ companion object {
26
+ const val ACTION_START = "ai.eliza.websiteblocker.START"
27
+ const val ACTION_STOP = "ai.eliza.websiteblocker.STOP"
28
+ const val EXTRA_WEBSITES = "websites"
29
+ const val EXTRA_ENDS_AT = "ends_at"
30
+ private const val NOTIFICATION_CHANNEL_ID = "website_blocker_vpn"
31
+ private const val NOTIFICATION_ID = 9184
32
+ private const val VPN_ADDRESS = "10.77.0.1"
33
+ private const val DNS_ADDRESS = "10.77.0.2"
34
+
35
+ @Volatile
36
+ private var activeInstance: WebsiteBlockerVpnService? = null
37
+
38
+ fun isRunning(): Boolean = activeInstance != null
39
+ }
40
+
41
+ private var vpnInterface: ParcelFileDescriptor? = null
42
+ private var tunnelThread: Thread? = null
43
+ private val tunnelRunning = AtomicBoolean(false)
44
+ private val mainHandler = Handler(Looper.getMainLooper())
45
+ private var scheduledStop: Runnable? = null
46
+ private var shouldClearStateOnStop = false
47
+ @Volatile
48
+ private var blockedWebsites: Set<String> = emptySet()
49
+ @Volatile
50
+ private var allowedWebsites: Set<String> = emptySet()
51
+ @Volatile
52
+ private var matchMode: String = "exact"
53
+ @Volatile
54
+ private var activePolicy: SavedWebsiteBlock? = null
55
+
56
+ override fun onCreate() {
57
+ super.onCreate()
58
+ activeInstance = this
59
+ createNotificationChannel()
60
+ }
61
+
62
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
63
+ val action = intent?.action ?: ACTION_START
64
+ if (action == ACTION_STOP) {
65
+ shouldClearStateOnStop = true
66
+ stopSelf()
67
+ return START_NOT_STICKY
68
+ }
69
+
70
+ val persisted = WebsiteBlockerStateStore.load(this)
71
+ val websites = intent?.getStringArrayListExtra(EXTRA_WEBSITES)
72
+ ?.mapNotNull(WebsiteBlockerStateStore::normalizeHostname)
73
+ ?.distinct()
74
+ ?: persisted?.requestedWebsites
75
+ ?: emptyList()
76
+ if (websites.isEmpty()) {
77
+ shouldClearStateOnStop = true
78
+ stopSelf()
79
+ return START_NOT_STICKY
80
+ }
81
+
82
+ val endsAt = when {
83
+ intent?.hasExtra(EXTRA_ENDS_AT) == true -> {
84
+ val value = intent.getLongExtra(EXTRA_ENDS_AT, -1L)
85
+ if (value > 0L) value else null
86
+ }
87
+ else -> persisted?.endsAtEpochMs
88
+ }
89
+
90
+ val savedBlock = WebsiteBlockerStateStore.save(this, websites, endsAt)
91
+ ?: run {
92
+ shouldClearStateOnStop = true
93
+ stopSelf()
94
+ return START_NOT_STICKY
95
+ }
96
+ activePolicy = savedBlock
97
+ blockedWebsites = savedBlock.blockedWebsites.toSet()
98
+ allowedWebsites = savedBlock.allowedWebsites.toSet()
99
+ matchMode = savedBlock.matchMode
100
+ shouldClearStateOnStop = false
101
+ startForegroundNotification()
102
+ establishVpn()
103
+ startTunnelLoop()
104
+ scheduleStop(endsAt)
105
+ return START_STICKY
106
+ }
107
+
108
+ override fun onDestroy() {
109
+ super.onDestroy()
110
+ cancelScheduledStop()
111
+ stopTunnelLoop()
112
+ if (shouldClearStateOnStop) {
113
+ WebsiteBlockerStateStore.clear(this)
114
+ }
115
+ activeInstance = null
116
+ }
117
+
118
+ override fun onRevoke() {
119
+ shouldClearStateOnStop = true
120
+ super.onRevoke()
121
+ stopSelf()
122
+ }
123
+
124
+ private fun establishVpn() {
125
+ if (vpnInterface != null) {
126
+ return
127
+ }
128
+
129
+ val builder = Builder()
130
+ .setSession("Eliza Website Blocker")
131
+ .setBlocking(true)
132
+ .setMtu(1500)
133
+ .addAddress(VPN_ADDRESS, 32)
134
+ .addRoute(DNS_ADDRESS, 32)
135
+ .addDnsServer(DNS_ADDRESS)
136
+
137
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
138
+ builder.setMetered(false)
139
+ }
140
+
141
+ vpnInterface = builder.establish()
142
+ }
143
+
144
+ private fun startTunnelLoop() {
145
+ if (tunnelRunning.get()) {
146
+ return
147
+ }
148
+
149
+ val descriptor = vpnInterface ?: return
150
+ val policy = activePolicy ?: WebsiteBlockerStateStore.load(this) ?: return
151
+ tunnelRunning.set(true)
152
+ tunnelThread = Thread {
153
+ val dnsAddress = InetAddress.getByName(DNS_ADDRESS) as Inet4Address
154
+ FileInputStream(descriptor.fileDescriptor).use { input ->
155
+ FileOutputStream(descriptor.fileDescriptor).use { output ->
156
+ val packetBuffer = ByteArray(32_767)
157
+ while (tunnelRunning.get()) {
158
+ val length = try {
159
+ input.read(packetBuffer)
160
+ } catch (_: Exception) {
161
+ break
162
+ }
163
+ if (length <= 0) {
164
+ continue
165
+ }
166
+
167
+ val query = DnsPacketCodec.parseUdpDnsQuery(packetBuffer, length, dnsAddress)
168
+ ?: continue
169
+ val responsePayload = if (
170
+ WebsiteBlockerStateStore.isBlockedHostname(policy, query.queryName)
171
+ ) {
172
+ DnsPacketCodec.buildBlockedDnsResponse(query.dnsPayload)
173
+ } else {
174
+ forwardDnsQuery(query.dnsPayload)
175
+ ?: DnsPacketCodec.buildServerFailureDnsResponse(query.dnsPayload)
176
+ }
177
+
178
+ val responsePacket = DnsPacketCodec.buildUdpDnsResponse(query, responsePayload)
179
+ try {
180
+ output.write(responsePacket)
181
+ } catch (_: Exception) {
182
+ break
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }.apply {
188
+ name = "ElizaWebsiteBlockerVpn"
189
+ isDaemon = true
190
+ start()
191
+ }
192
+ }
193
+
194
+ private fun stopTunnelLoop() {
195
+ tunnelRunning.set(false)
196
+ tunnelThread?.interrupt()
197
+ tunnelThread = null
198
+ try {
199
+ vpnInterface?.close()
200
+ } catch (_: Exception) {
201
+ }
202
+ vpnInterface = null
203
+ }
204
+
205
+ private fun forwardDnsQuery(queryPayload: ByteArray): ByteArray? {
206
+ val upstreamServers = resolveUpstreamDnsServers()
207
+ for (server in upstreamServers) {
208
+ try {
209
+ DatagramSocket().use { socket ->
210
+ protect(socket)
211
+ socket.soTimeout = 3_000
212
+ socket.connect(InetSocketAddress(server, 53))
213
+ socket.send(DatagramPacket(queryPayload, queryPayload.size))
214
+ val responseBuffer = ByteArray(4_096)
215
+ val responsePacket = DatagramPacket(responseBuffer, responseBuffer.size)
216
+ socket.receive(responsePacket)
217
+ return responseBuffer.copyOf(responsePacket.length)
218
+ }
219
+ } catch (_: Exception) {
220
+ }
221
+ }
222
+ return null
223
+ }
224
+
225
+ private fun resolveUpstreamDnsServers(): List<InetAddress> {
226
+ val connectivityManager =
227
+ applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
228
+ val network = connectivityManager?.activeNetwork
229
+ val linkProperties = connectivityManager?.getLinkProperties(network)
230
+ val dnsServers = linkProperties?.dnsServers
231
+ ?.filterIsInstance<Inet4Address>()
232
+ ?.filter { it.hostAddress != DNS_ADDRESS }
233
+ .orEmpty()
234
+ if (dnsServers.isNotEmpty()) {
235
+ return dnsServers
236
+ }
237
+ return listOf(
238
+ InetAddress.getByName("1.1.1.1"),
239
+ InetAddress.getByName("8.8.8.8"),
240
+ )
241
+ }
242
+
243
+ private fun scheduleStop(endsAtEpochMs: Long?) {
244
+ cancelScheduledStop()
245
+ if (endsAtEpochMs == null) {
246
+ return
247
+ }
248
+
249
+ val delayMs = endsAtEpochMs - System.currentTimeMillis()
250
+ if (delayMs <= 0) {
251
+ shouldClearStateOnStop = true
252
+ stopSelf()
253
+ return
254
+ }
255
+
256
+ val stopRunnable = Runnable {
257
+ shouldClearStateOnStop = true
258
+ stopSelf()
259
+ }
260
+ scheduledStop = stopRunnable
261
+ mainHandler.postDelayed(stopRunnable, delayMs)
262
+ }
263
+
264
+ private fun cancelScheduledStop() {
265
+ scheduledStop?.let { mainHandler.removeCallbacks(it) }
266
+ scheduledStop = null
267
+ }
268
+
269
+ private fun startForegroundNotification() {
270
+ val notification = buildNotification()
271
+ if (Build.VERSION.SDK_INT >= 34) {
272
+ startForeground(
273
+ NOTIFICATION_ID,
274
+ notification,
275
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
276
+ )
277
+ } else {
278
+ startForeground(NOTIFICATION_ID, notification)
279
+ }
280
+ }
281
+
282
+ private fun buildNotification(): Notification {
283
+ val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
284
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
285
+ manager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null
286
+ ) {
287
+ createNotificationChannel()
288
+ }
289
+
290
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
291
+ Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
292
+ .setContentTitle("Eliza Website Blocker")
293
+ .setContentText("Blocking ${blockedWebsites.joinToString(", ")}")
294
+ .setSmallIcon(android.R.drawable.ic_lock_lock)
295
+ .setOngoing(true)
296
+ .build()
297
+ } else {
298
+ @Suppress("DEPRECATION")
299
+ Notification.Builder(this)
300
+ .setContentTitle("Eliza Website Blocker")
301
+ .setContentText("Blocking ${blockedWebsites.joinToString(", ")}")
302
+ .setSmallIcon(android.R.drawable.ic_lock_lock)
303
+ .setOngoing(true)
304
+ .build()
305
+ }
306
+ }
307
+
308
+ private fun createNotificationChannel() {
309
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
310
+ return
311
+ }
312
+ val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
313
+ if (manager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) != null) {
314
+ return
315
+ }
316
+ manager.createNotificationChannel(
317
+ NotificationChannel(
318
+ NOTIFICATION_CHANNEL_ID,
319
+ "Eliza Website Blocker",
320
+ NotificationManager.IMPORTANCE_LOW,
321
+ ).apply {
322
+ description = "Foreground notification while website blocking is active"
323
+ },
324
+ )
325
+ }
326
+ }
@@ -0,0 +1,83 @@
1
+ export type WebsiteBlockerPermissionStatus = "granted" | "denied" | "not-determined" | "not-applicable";
2
+ export type WebsiteBlockerEngine = "hosts-file" | "vpn-dns" | "network-extension" | "content-blocker";
3
+ export type WebsiteBlockerElevationMethod = "osascript" | "pkexec" | "powershell-runas" | "vpn-consent" | "system-settings" | null;
4
+ export interface WebsiteBlockerPermissionResult {
5
+ status: WebsiteBlockerPermissionStatus;
6
+ canRequest: boolean;
7
+ reason?: string;
8
+ }
9
+ export interface WebsiteBlockerStatus {
10
+ available: boolean;
11
+ active: boolean;
12
+ hostsFilePath: string | null;
13
+ endsAt: string | null;
14
+ websites: string[];
15
+ requestedWebsites: string[];
16
+ blockedWebsites: string[];
17
+ allowedWebsites: string[];
18
+ matchMode: "exact" | "subdomain";
19
+ canUnblockEarly: boolean;
20
+ requiresElevation: boolean;
21
+ engine: WebsiteBlockerEngine;
22
+ platform: string;
23
+ supportsElevationPrompt: boolean;
24
+ elevationPromptMethod: WebsiteBlockerElevationMethod;
25
+ permissionStatus?: WebsiteBlockerPermissionStatus;
26
+ canRequestPermission?: boolean;
27
+ canOpenSystemSettings?: boolean;
28
+ reason?: string;
29
+ }
30
+ export interface StartWebsiteBlockOptions {
31
+ websites?: string[] | string;
32
+ durationMinutes?: number | string | null;
33
+ text?: string;
34
+ }
35
+ export type StartWebsiteBlockResult = {
36
+ success: true;
37
+ endsAt: string | null;
38
+ request: {
39
+ websites: string[];
40
+ durationMinutes: number | null;
41
+ };
42
+ } | {
43
+ success: false;
44
+ error: string;
45
+ status?: {
46
+ active: boolean;
47
+ endsAt: string | null;
48
+ websites: string[];
49
+ requiresElevation: boolean;
50
+ };
51
+ };
52
+ export type StopWebsiteBlockResult = {
53
+ success: true;
54
+ removed: boolean;
55
+ status: {
56
+ active: boolean;
57
+ endsAt: string | null;
58
+ websites: string[];
59
+ canUnblockEarly: boolean;
60
+ requiresElevation: boolean;
61
+ };
62
+ } | {
63
+ success: false;
64
+ error: string;
65
+ status?: {
66
+ active: boolean;
67
+ endsAt: string | null;
68
+ websites: string[];
69
+ canUnblockEarly: boolean;
70
+ requiresElevation: boolean;
71
+ };
72
+ };
73
+ export interface WebsiteBlockerPlugin {
74
+ getStatus(): Promise<WebsiteBlockerStatus>;
75
+ startBlock(options: StartWebsiteBlockOptions): Promise<StartWebsiteBlockResult>;
76
+ stopBlock(): Promise<StopWebsiteBlockResult>;
77
+ checkPermissions(): Promise<WebsiteBlockerPermissionResult>;
78
+ requestPermissions(): Promise<WebsiteBlockerPermissionResult>;
79
+ openSettings(): Promise<{
80
+ opened: boolean;
81
+ }>;
82
+ }
83
+ //# sourceMappingURL=definitions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { WebsiteBlockerPlugin } from "./definitions";
2
+ export * from "./definitions";
3
+ export declare const WebsiteBlocker: WebsiteBlockerPlugin;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAE1D,cAAc,eAAe,CAAC;AAK9B,eAAO,MAAM,cAAc,sBAK1B,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { registerPlugin } from "@capacitor/core";
2
+ export * from "./definitions";
3
+ const loadWeb = () => import("./web").then((module) => new module.WebsiteBlockerWeb());
4
+ export const WebsiteBlocker = registerPlugin("ElizaWebsiteBlocker", {
5
+ web: loadWeb,
6
+ });
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,cAAc,eAAe,CAAC;AAE9B,MAAM,OAAO,GAAG,GAAG,EAAE,CACnB,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC;AAEnE,MAAM,CAAC,MAAM,cAAc,GAAG,cAAc,CAC1C,qBAAqB,EACrB;IACE,GAAG,EAAE,OAAO;CACb,CACF,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { WebPlugin } from "@capacitor/core";
2
+ import type { StartWebsiteBlockOptions, StartWebsiteBlockResult, StopWebsiteBlockResult, WebsiteBlockerPermissionResult, WebsiteBlockerStatus } from "./definitions";
3
+ export declare class WebsiteBlockerWeb extends WebPlugin {
4
+ private apiBase;
5
+ private apiToken;
6
+ private authHeaders;
7
+ private canReachApi;
8
+ private requestJson;
9
+ getStatus(): Promise<WebsiteBlockerStatus>;
10
+ startBlock(options: StartWebsiteBlockOptions): Promise<StartWebsiteBlockResult>;
11
+ stopBlock(): Promise<StopWebsiteBlockResult>;
12
+ checkPermissions(): Promise<WebsiteBlockerPermissionResult>;
13
+ requestPermissions(): Promise<WebsiteBlockerPermissionResult>;
14
+ openSettings(): Promise<{
15
+ opened: boolean;
16
+ }>;
17
+ }
18
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,8BAA8B,EAC9B,oBAAoB,EACrB,MAAM,eAAe,CAAC;AAOvB,qBAAa,iBAAkB,SAAQ,SAAS;IAC9C,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,QAAQ;IAehB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,WAAW;YAeL,WAAW;IAqBnB,SAAS,IAAI,OAAO,CAAC,oBAAoB,CAAC;IAI1C,UAAU,CACd,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC;IAU7B,SAAS,IAAI,OAAO,CAAC,sBAAsB,CAAC;IAS5C,gBAAgB,IAAI,OAAO,CAAC,8BAA8B,CAAC;IAa3D,kBAAkB,IAAI,OAAO,CAAC,8BAA8B,CAAC;IAe7D,YAAY,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;CAWnD"}