426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
223 lines
7.8 KiB
Swift
223 lines
7.8 KiB
Swift
import Capacitor
|
|
import FamilyControls
|
|
import Foundation
|
|
import UIKit
|
|
|
|
@objc(ElizaAppBlockerPlugin)
|
|
public class ElizaAppBlockerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
public let identifier = "ElizaAppBlockerPlugin"
|
|
public let jsName = "ElizaAppBlocker"
|
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
|
|
CAPPluginMethod(name: "getInstalledApps", returnType: CAPPluginReturnPromise),
|
|
CAPPluginMethod(name: "selectApps", returnType: CAPPluginReturnPromise),
|
|
CAPPluginMethod(name: "blockApps", returnType: CAPPluginReturnPromise),
|
|
CAPPluginMethod(name: "unblockApps", returnType: CAPPluginReturnPromise),
|
|
CAPPluginMethod(name: "getStatus", returnType: CAPPluginReturnPromise),
|
|
]
|
|
|
|
@objc public override func checkPermissions(_ call: CAPPluginCall) {
|
|
call.resolve(buildPermissionResult())
|
|
}
|
|
|
|
@objc public override func requestPermissions(_ call: CAPPluginCall) {
|
|
if #available(iOS 16.0, *) {
|
|
Task { @MainActor in
|
|
var reasonOverride: String?
|
|
do {
|
|
try await AuthorizationCenter.shared.requestAuthorization(for: .individual)
|
|
} catch {
|
|
reasonOverride = error.localizedDescription
|
|
}
|
|
call.resolve(buildPermissionResult(reasonOverride: reasonOverride))
|
|
}
|
|
return
|
|
}
|
|
|
|
AuthorizationCenter.shared.requestAuthorization { result in
|
|
let reasonOverride: String?
|
|
switch result {
|
|
case .success:
|
|
reasonOverride = nil
|
|
case .failure(let error):
|
|
reasonOverride = error.localizedDescription
|
|
}
|
|
|
|
call.resolve(self.buildPermissionResult(reasonOverride: reasonOverride))
|
|
}
|
|
}
|
|
|
|
@objc func getInstalledApps(_ call: CAPPluginCall) {
|
|
call.resolve([
|
|
"apps": [],
|
|
])
|
|
}
|
|
|
|
@objc func selectApps(_ call: CAPPluginCall) {
|
|
guard let presenter = bridge?.viewController else {
|
|
call.reject("Could not present the iPhone app picker.")
|
|
return
|
|
}
|
|
|
|
Task { @MainActor in
|
|
FamilyActivityPickerBridge.present(from: presenter) { tokens, cancelled in
|
|
let apps = tokens.enumerated().compactMap { index, token -> JSObject? in
|
|
guard let tokenData = AppBlockerShared.serializeToken(token) else {
|
|
return nil
|
|
}
|
|
var object = JSObject()
|
|
object["packageName"] = ""
|
|
object["displayName"] = "Selected App \(index + 1)"
|
|
object["tokenData"] = tokenData
|
|
return object
|
|
}
|
|
|
|
call.resolve([
|
|
"apps": JSArray(apps),
|
|
"cancelled": cancelled,
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func blockApps(_ call: CAPPluginCall) {
|
|
if AuthorizationCenter.shared.authorizationStatus != .approved {
|
|
call.resolve([
|
|
"success": false,
|
|
"endsAt": NSNull(),
|
|
"error": nullable(permissionReason(for: AuthorizationCenter.shared.authorizationStatus)),
|
|
"blockedCount": 0,
|
|
])
|
|
return
|
|
}
|
|
|
|
let durationMinutes = call.getInt("durationMinutes")
|
|
if let durationMinutes, durationMinutes > 0 {
|
|
call.resolve([
|
|
"success": false,
|
|
"endsAt": NSNull(),
|
|
"error": "Timed iPhone app blocking requires a DeviceActivity extension. Omit durationMinutes for an indefinite block, then call unblockApps when the block should end.",
|
|
"blockedCount": 0,
|
|
])
|
|
return
|
|
}
|
|
|
|
let tokenDataArray = (call.getArray("appTokens") ?? []).compactMap { $0 as? String }
|
|
let tokens = AppBlockerShared.deserializeTokens(tokenDataArray)
|
|
guard !tokens.isEmpty else {
|
|
call.resolve([
|
|
"success": false,
|
|
"endsAt": NSNull(),
|
|
"error": "Select at least one iPhone app to block.",
|
|
"blockedCount": 0,
|
|
])
|
|
return
|
|
}
|
|
|
|
AppBlockerShared.startBlock(tokens: tokens)
|
|
call.resolve([
|
|
"success": true,
|
|
"endsAt": NSNull(),
|
|
"blockedCount": tokens.count,
|
|
])
|
|
}
|
|
|
|
@objc func unblockApps(_ call: CAPPluginCall) {
|
|
AppBlockerShared.stopBlock()
|
|
call.resolve([
|
|
"success": true,
|
|
])
|
|
}
|
|
|
|
@objc func getStatus(_ call: CAPPluginCall) {
|
|
let state = AppBlockerShared.loadState()
|
|
let permission = buildPermissionResult()
|
|
let permissionStatus = permission["status"] as? String ?? "not-determined"
|
|
let reason = permission["reason"] as? String
|
|
let blockedCount = state?.tokenDataArray.count ?? 0
|
|
let active = permissionStatus == "granted" && blockedCount > 0
|
|
|
|
call.resolve([
|
|
"status": active ? "active" : "inactive",
|
|
"available": true,
|
|
"active": active,
|
|
"platform": "ios",
|
|
"engine": "family-controls",
|
|
"capabilities": appBlockerCapabilities(),
|
|
"blockedCount": blockedCount,
|
|
"blockedPackageNames": [],
|
|
"endsAt": nullable(AppBlockerShared.endsAtString(for: state)),
|
|
"permissionStatus": permissionStatus,
|
|
"canRequest": permission["canRequest"] as? Bool ?? false,
|
|
"canOpenSettings": permission["canOpenSettings"] as? Bool ?? true,
|
|
"settingsTarget": nullable(permission["settingsTarget"]),
|
|
"reason": nullable(reason),
|
|
])
|
|
}
|
|
|
|
private func buildPermissionResult(reasonOverride: String? = nil) -> [String: Any] {
|
|
let status = AuthorizationCenter.shared.authorizationStatus
|
|
let mappedStatus: String
|
|
let canRequest: Bool
|
|
let settingsTarget: Any
|
|
|
|
switch status {
|
|
case .approved:
|
|
mappedStatus = "granted"
|
|
canRequest = false
|
|
settingsTarget = NSNull()
|
|
case .notDetermined:
|
|
mappedStatus = "not-determined"
|
|
canRequest = true
|
|
settingsTarget = "screenTime"
|
|
default:
|
|
mappedStatus = "denied"
|
|
canRequest = true
|
|
settingsTarget = "screenTime"
|
|
}
|
|
|
|
var result: [String: Any] = [
|
|
"status": mappedStatus,
|
|
"canRequest": canRequest,
|
|
"canOpenSettings": mappedStatus != "granted",
|
|
"settingsTarget": settingsTarget,
|
|
"engine": "family-controls",
|
|
"capabilities": appBlockerCapabilities(),
|
|
]
|
|
|
|
if let reason = reasonOverride ?? permissionReason(for: status) {
|
|
result["reason"] = reason
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
private func appBlockerCapabilities() -> [String: Any] {
|
|
[
|
|
"canSelectApps": true,
|
|
"canBlockApps": true,
|
|
"canScheduleTimedBlocks": false,
|
|
"canUnblockEarly": true,
|
|
"requiresFamilyControls": true,
|
|
"requiresUsageAccess": false,
|
|
"requiresOverlay": false,
|
|
]
|
|
}
|
|
|
|
private func permissionReason(for status: AuthorizationStatus) -> String? {
|
|
switch status {
|
|
case .approved:
|
|
return nil
|
|
case .notDetermined:
|
|
return "Authorize Family Controls before Eliza can choose and shield apps on this iPhone."
|
|
default:
|
|
return "Family Controls access is currently denied for this app. Re-run authorization on this iPhone developer build."
|
|
}
|
|
}
|
|
|
|
private func nullable(_ value: Any?) -> Any {
|
|
value ?? NSNull()
|
|
}
|
|
}
|