arcane_admin 1.5.12 copy "arcane_admin: ^1.5.12" to clipboard
arcane_admin: ^1.5.12 copied to clipboard

Firebase Admin support for Arcane Servers

arcane_admin #

arcane_admin provides Firebase Admin support for Arcane servers. It wraps the Google Cloud and Firebase admin APIs behind a single initializer and exposes ready-to-use helpers for:

  • Firestore
  • Cloud Storage
  • Firebase Cloud Messaging
  • Cloud Tasks
  • JWT validation for authenticated Cloud Tasks requests
  • Eventarc document and storage event handling

The package also re-exports fire_api, so you can use the Firestore and Storage types from the same import.

Installation #

Add the package to your pubspec.yaml:

dependencies:
  arcane_admin: ^1.5.8

Then fetch dependencies:

dart pub get

Quick Start #

Call ArcaneAdmin.initialize() once during server startup before using any of the admin services.

import 'package:arcane_admin/arcane_admin.dart';

Future<void> main() async {
  await ArcaneAdmin.initialize();

  print("Initialized Firebase admin for ${ArcaneAdmin.projectId}");
}

By default, initialization uses Application Default Credentials and attempts to discover the active Google Cloud project automatically.

Authentication #

ArcaneAdmin.initialize() supports three authentication modes:

  1. apiKey
  2. credentials
  3. Application Default Credentials from the environment

If you do not provide apiKey or credentials, the package falls back to Google Application Default Credentials.

Application Default Credentials #

This is the simplest setup for most servers and Cloud Run / GCE / GKE environments.

export GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/service-account.json
import 'package:arcane_admin/arcane_admin.dart';

Future<void> main() async {
  await ArcaneAdmin.initialize();
}

Service Account Credentials #

You can also pass a service account explicitly.

import 'dart:io';

import 'package:arcane_admin/arcane_admin.dart';
import 'package:googleapis_auth/auth_io.dart';

Future<void> main() async {
  await ArcaneAdmin.initialize(
    credentials: ServiceAccountCredentials.fromJson(
      File("creds.json").readAsStringSync(),
    ),
  );
}

Full Initialization Options #

All initialization fields are optional except where your deployment requires a specific override.

import 'package:arcane_admin/arcane_admin.dart';

Future<void> main() async {
  await ArcaneAdmin.initialize(
    projectId: "my-project-id",
    defaultStorageBucket: "my-project-id.appspot.com",
    credentials: null,
    database: "(default)",
    apiKey: null,
    cloudTasksRegion: "us-central1",
    firestoreBasePath: "",
    firestoreBaseUrl: "https://firestore.googleapis.com/",
  );
}

Initialization Notes #

  • projectId is resolved automatically when possible.
  • defaultStorageBucket defaults to <projectId>.firebasestorage.app.
  • If your Firebase project still uses the older appspot.com bucket naming, set defaultStorageBucket explicitly.
  • apiKey works for some operations, but it is generally not a good default for admin/server environments.
  • JWT validation for Cloud Tasks is strongest when a service account email is available.

Available Admin Clients #

After initialization, the following static clients are available:

  • ArcaneAdmin.firestore
  • ArcaneAdmin.storage
  • ArcaneAdmin.messaging
  • ArcaneAdmin.tasks
  • ArcaneAdmin.validation
  • ArcaneAdmin.events
  • ArcaneAdmin.client

Useful helpers are also exposed:

  • ArcaneAdmin.projectId
  • ArcaneAdmin.defaultStorageBucket
  • ArcaneAdmin.serviceAccountEmail
  • ArcaneAdmin.bucket([bucketName])
  • ArcaneAdmin.ref(path, bucket: "...")

Firestore #

Firestore support is powered by fire_api.

Use the database client directly:

import 'package:arcane_admin/arcane_admin.dart';

Future<void> readUser() async {
  DocumentSnapshot snapshot = await ArcaneAdmin.firestore
      .collection("users")
      .doc("dan")
      .get();

  if (!snapshot.exists) {
    print("User not found");
    return;
  }

  print(snapshot.data);
}

Writing a document:

import 'package:arcane_admin/arcane_admin.dart';

Future<void> writeUser() async {
  await ArcaneAdmin.firestore.collection("users").doc("dan").set({
    "name": "Dan",
    "role": "admin",
    "enabled": true,
  });
}

Because fire_api is re-exported, common Firestore and Storage types can be imported from package:arcane_admin/arcane_admin.dart.

Cloud Storage #

Cloud Storage is also backed by fire_api.

Read and write a file in the default bucket:

import 'dart:typed_data';

import 'package:arcane_admin/arcane_admin.dart';

Future<void> storageExample() async {
  FireStorageRef file = ArcaneAdmin.ref("backups/config.json");

  await file.write(Uint8List.fromList([1, 2, 3, 4]));
  Uint8List bytes = await file.read();

  print("Read ${bytes.length} bytes");
}

Use a custom bucket:

import 'package:arcane_admin/arcane_admin.dart';

Future<void> customBucketExample() async {
  FireStorageRef file = ArcaneAdmin.ref(
    "exports/report.json",
    bucket: "my-custom-bucket",
  );

  await file.delete();
}

You can also access the storage API manually:

ArcaneAdmin.storage
    .bucket("my-custom-bucket")
    .ref("images/avatar.png")
    .read();

Messaging #

Use ArcaneAdmin.messaging to send Firebase Cloud Messaging notifications.

Send To A Single Device Token #

import 'package:arcane_admin/arcane_admin.dart';

Future<void> sendTokenMessage(String token) async {
  await ArcaneAdmin.messaging.send(
    FTokenMessage(
      token: token,
      data: {"screen": "orders", "orderId": "1234"},
      notification: FNotification(
        title: "Order updated",
        body: "Your order is ready for pickup.",
      ),
      android: FAndroidConfig(priority: AndroidConfigPriority.high),
      apns: FApnsConfig(
        headers: {"apns-priority": "10"},
        payload: FApnsPayload(
          aps: FAps(sound: "default", interruptionLevel: "time-sensitive"),
        ),
      ),
    ),
  );
}

Send To A Topic #

import 'package:arcane_admin/arcane_admin.dart';

Future<void> sendTopicMessage() async {
  await ArcaneAdmin.messaging.send(
    FTopicMessage(
      topic: "system-alerts",
      notification: FNotification(
        title: "Maintenance window",
        body: "Scheduled maintenance starts in 10 minutes.",
      ),
      data: {"type": "maintenance"},
    ),
  );
}

Multicast Example #

import 'package:arcane_admin/arcane_admin.dart';

Future<void> sendMulticast(List<String> tokens) async {
  await ArcaneAdmin.messaging.sendMulticast(
    FMulticastMessage(
      tokens: tokens,
      data: {"event": "refresh"},
      notification: FNotification(
        title: "Sync requested",
        body: "Please refresh your local cache.",
      ),
    ),
  );
}

Messaging Notes #

  • sendAll and sendMulticast support up to 500 messages/tokens per call.
  • Message payload helpers are available for Android, APNs, and WebPush configuration.
  • All sends target projects/<projectId> automatically after initialization.

Cloud Tasks #

You can enqueue authenticated HTTP Cloud Tasks with ArcaneAdmin.tasks.scheduleTask.

import 'package:arcane_admin/arcane_admin.dart';

Future<void> scheduleTask() async {
  String? taskName = await ArcaneAdmin.tasks.scheduleTask(
    queue: "barbequeue",
    url: "https://api.example.com/internal/process-order",
    body: {
      "orderId": "1234",
      "force": true,
    },
    scheduleTime: DateTime.now().add(const Duration(minutes: 5)),
  );

  print("Created task: $taskName");
}

Cloud Tasks Notes #

  • Requests are sent as JSON POST bodies.
  • The package attaches an OIDC token using the active service account when one is available.
  • scheduleTime defaults to "now" if omitted.
  • cloudTasksRegion defaults to us-central1, but you can override it during initialization.

Validating Cloud Tasks Requests #

When Cloud Tasks calls your server, validate the bearer token before processing the request.

import 'package:arcane_admin/arcane_admin.dart';
import 'package:shelf/shelf.dart';

Future<Response> onSomeTask(Request request) async {
  bool valid = await ArcaneAdmin.validation.validateGCPRequestJWT(
    request,
    verifyAudience: "https://api.example.com/internal/process-order",
  );

  if (!valid) {
    return Response.forbidden("Unauthenticated");
  }

  return Response.ok("Job complete");
}

If you pass verifyAudience, it must match the target URL used when the task was created.

Eventarc Helpers #

The package includes helpers for handling Firestore document events and Cloud Storage events from Eventarc in shelf handlers.

Firestore Document Event Example #

import 'package:arcane_admin/arcane_admin.dart';
import 'package:shelf/shelf.dart';

Future<Response> onDocumentWebhook(Request request) {
  return request.documentEvent((ArcaneDocumentEvent event) async {
    print("Document path: ${event.documentPath}");
    print("IDs: ${event.ids}");
    print("Before: ${event.before}");
    print("After: ${event.after}");

    String? userId = event.ids["users"];
    return Response.ok("Processed change for $userId");
  });
}

ArcaneDocumentEvent includes:

  • documentPath
  • ids
  • before
  • after
  • request
  • rawEventData

Storage Event Example #

import 'package:arcane_admin/arcane_admin.dart';
import 'package:shelf/shelf.dart';

Future<Response> onStorageWebhook(Request request) {
  return request.storageEvent((ArcaneStorageEvent event) async {
    print("Bucket: ${event.bucket}");
    print("Path: ${event.path}");
    print("Content type: ${event.contentType}");

    return Response.ok("Storage event received");
  });
}

Typical Server Startup Example #

import 'package:arcane_admin/arcane_admin.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;

Future<void> main() async {
  await ArcaneAdmin.initialize(
    projectId: "my-project-id",
    cloudTasksRegion: "us-central1",
  );

  Handler app = (Request request) {
    if (request.url.path == "health") {
      return Future.value(Response.ok("ok"));
    }

    return Future.value(Response.notFound("Not found"));
  };

  await io.serve(app, "0.0.0.0", 8080);
}

Best Practices #

  • Initialize once during process startup.
  • Prefer service account credentials or Application Default Credentials for server environments.
  • Set defaultStorageBucket explicitly if your Firebase project uses a non-default bucket name.
  • Validate Cloud Tasks JWTs on every internal task endpoint.
  • Use ArcaneAdmin.firestore and ArcaneAdmin.storage when you need direct access to the underlying APIs.

Package Notes #

  • Firestore and Storage support come from fire_api / fire_api_dart.
  • Messaging uses the Firebase Cloud Messaging v1 API.
  • Cloud Tasks uses authenticated HTTP tasks with OIDC tokens when possible.
  • Event helpers are designed for shelf-based backends.

License #

See LICENSE.