zuraffa_permissions 1.0.0 copy "zuraffa_permissions: ^1.0.0" to clipboard
zuraffa_permissions: ^1.0.0 copied to clipboard

Typed permission requests for the Zuraffa ecosystem.

zuraffa_permissions #

Typed permission requests for the Zuraffa ecosystem — the foundation layer every capability package (notifications, file_picker, auth, …) builds on.

Usage #

final permissions = PermissionService();

final status = await permissions.request('camera');
if (status == PermissionStatus.permanentlyDenied) {
  await permissions.openSettings();
}

Design #

  • PermissionPort — the technology-agnostic contract (check/request/openSettings); platform adapters (zuraffa_permissions_android/ios/…) implement it as federated siblings.
  • 11 built-in scopes — camera, photos, notifications, locationWhenInUse/Always, microphone, storage, biometrics, contacts, calendar, tracking — zero-config; custom scopes register through PermissionScopeRegistry.
  • PermissionStatus — granted / denied / permanentlyDenied / undetermined / restricted / limited. Requesting a permanently-denied scope never re-prompts; the caller routes to settings.
  • InMemoryPermissionAdapter — pure-Dart default so permission logic tests without a platform.
  • Entities generated via the zfa CLI (Zorphy); registerPermissionDependencies wires port + registry + service onto GetIt.

Roles & access control (built on Zuraffa) #

Beyond the runtime port, the package models an RBAC layer entirely on Zuraffa patterns: Permission, Role, UserPermission, UserRole and RolePermission are Zorphy entities (FR-001); persistence flows through Zuraffa datasources/repositories with a pure-Dart InMemoryPermissionStore default (FR-002); and every business rule — CheckPermissionUseCase, ListUserPermissionsUseCase, AssignRoleToUserUseCase, RemoveRoleFromUserUseCase, GrantPermissionToUserUseCase, RevokePermissionFromUserUseCase, GrantPermissionToRoleUseCase — is a Zuraffa UseCase resolvable from GetIt via registerPermissionDependencies (FR-003/FR-004).

registerPermissionDependencies(getIt);
final check = getIt<CheckPermissionUseCase>();
final allowed = await check.execute(
  const CheckPermissionParams(userId: 'u1', permissionId: 'perm.camera.read'),
  null,
);

Federated platform packages #

The repository ships the real platform implementations as federated sub-packages (the zikzak_inappwebview pattern):

packages/
├── zuraffa_permissions/                      # app-facing package (this one)
├── zuraffa_permissions_platform_interface/   # MethodChannel protocol + PermissionPort bridge
├── zuraffa_permissions_android/              # Kotlin — ActivityCompat, rationale-based permanent-denial detection
├── zuraffa_permissions_ios/                  # Swift — AVFoundation, Photos (.limited), UNUserNotificationCenter, CoreLocation, Contacts, EventKit, LocalAuthentication
└── zuraffa_permissions_macos/                # Swift — same frameworks via AppKit; NSWorkspace settings

Wiring the native stack #

The pure-Dart PermissionPort contract is unchanged. Apps register the real platform stack through the shared DI seam:

import 'package:zuraffa_permissions_platform_interface/zuraffa_permissions_platform_interface.dart';

registerPermissionDependencies(
  getIt,
  port: MethodChannelPermissionAdapter(),  // routes to the native plugin
);

Wire statuses travel the channel as stable strings (granted/denied/permanentlyDenied/undetermined/restricted/limited); the adapter maps them onto the typed PermissionStatus enum with unknown values degrading to undetermined (forward-compatible).

Platform notes:

  • Android: the permanently-denied detection uses the shouldShowRequestPermissionRationale heuristic (rationale=false after a prior request ⇒ settings-only). Storage scopes resolve to granted on Android 11+ scoped storage; notifications need no runtime permission before API 33.
  • iOS/macOS: photos report the .limited state as limited; location uses bounded-poll resolution for requestWhenInUseAuthorization (no completion API); locationAlways distinguishes always from when-in-use as limited.