Skip to main content

flutter_cloudflare_dns 0.3.0 copy "flutter_cloudflare_dns: ^0.3.0" to clipboard
flutter_cloudflare_dns: ^0.3.0 copied to clipboard

Flutter toolkit for diagnosing and managing Cloudflare DNS: DoH health checks, safe API gateways, and Material 3 widgets.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_cloudflare_dns/flutter_cloudflare_dns.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Cloudflare DNS',
      debugShowCheckedModeBanner: false,
      theme: _buildTheme(Brightness.light),
      darkTheme: _buildTheme(Brightness.dark),
      themeMode: ThemeMode.system,
      home: const ExampleDashboard(),
    );
  }

  ThemeData _buildTheme(Brightness brightness) {
    final colors = ColorScheme.fromSeed(
      seedColor: const Color(0xFFF6821F),
      brightness: brightness,
    );
    return ThemeData(
      colorScheme: colors,
      scaffoldBackgroundColor: colors.surface,
      useMaterial3: true,
      appBarTheme: AppBarTheme(
        backgroundColor: colors.surface,
        foregroundColor: colors.onSurface,
        surfaceTintColor: colors.surfaceTint,
        centerTitle: false,
      ),
      bottomSheetTheme: BottomSheetThemeData(
        backgroundColor: colors.surfaceContainerLow,
        modalBackgroundColor: colors.surfaceContainerLow,
        showDragHandle: true,
        shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.vertical(top: Radius.circular(32)),
        ),
      ),
      inputDecorationTheme: InputDecorationTheme(
        filled: true,
        fillColor: colors.surfaceContainerHighest,
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(18),
          borderSide: BorderSide.none,
        ),
        enabledBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(18),
          borderSide: BorderSide.none,
        ),
        focusedBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(18),
          borderSide: BorderSide(color: colors.primary, width: 2),
        ),
      ),
    );
  }
}

class ExampleDashboard extends StatefulWidget {
  const ExampleDashboard({super.key});

  @override
  State<ExampleDashboard> createState() => _ExampleDashboardState();
}

class _ExampleDashboardState extends State<ExampleDashboard> {
  final gateway = DemoDnsGateway();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CloudflareDnsDashboard(
        gateway: gateway,
        diagnostics: DemoDiagnostics(gateway),
        initialZoneName: 'seungpyo.online',
        propagationCheckInterval: const Duration(seconds: 2),
      ),
    );
  }
}

/// Stable public-health result for a deterministic, offline-friendly demo.
class DemoDiagnostics extends DnsDiagnostics {
  DemoDiagnostics(this.gateway);

  /// Records still in the zone; a deleted record fades from the resolvers.
  final DemoDnsGateway gateway;
  final _polls = <String, int>{};

  @override
  Future<DnsHealthReport> check(
    String domain, {
    Iterable<DnsRecordType> types = const <DnsRecordType>[],
    Iterable<DnsRecord> expectedRecords = const <DnsRecord>[],
  }) async {
    // Long enough for the example to demonstrate the synchronized refresh
    // motion without making the dashboard feel artificially slow.
    await Future<void>.delayed(const Duration(milliseconds: 900));
    return DnsHealthReport(
      domain: domain,
      checkedAt: DateTime.now().toUtc(),
      answers: const <String, List<DnsRecord>>{},
      issues: const <DnsIssue>[],
      dnssecValidated: true,
    );
  }

  @override
  Future<List<DnsResolverComparison>> compareResolvers(
    Iterable<DnsRecord> records, {
    Map<String, Uri>? resolvers,
  }) async {
    await Future<void>.delayed(const Duration(milliseconds: 600));
    final list = records.toList(growable: false);
    // A saved or deleted record is watched alone; Google sees the change
    // from the third poll, Cloudflare at once.
    if (list.length == 1) {
      final record = list.single;
      final deleted = !gateway.contains(record);
      final key =
          '${deleted ? 'deleted:' : ''}${record.name}|${record.type.wireName}';
      final polls = _polls[key] = (_polls[key] ?? 0) + 1;
      final googleCaughtUp = polls >= 3;
      final googleHasRecord = deleted ? !googleCaughtUp : googleCaughtUp;
      return <DnsResolverComparison>[
        DnsResolverComparison(
          name: record.name,
          type: record.type,
          answers: <String, List<DnsRecord>>{
            'Cloudflare': deleted ? const <DnsRecord>[] : <DnsRecord>[record],
            'Google': googleHasRecord
                ? <DnsRecord>[record]
                : const <DnsRecord>[],
          },
        ),
      ];
    }
    return <DnsResolverComparison>[
      for (var i = 0; i < list.length; i++)
        DnsResolverComparison(
          name: list[i].name,
          type: list[i].type,
          answers: <String, List<DnsRecord>>{
            'Cloudflare': <DnsRecord>[list[i]],
            // The last record simulates a change Google has not seen yet.
            'Google': i == list.length - 1
                ? const <DnsRecord>[]
                : <DnsRecord>[list[i]],
          },
        ),
    ];
  }
}

/// In-memory gateway so the example is safe to run without a Cloudflare token.
///
/// It supports batches, so zone file imports are applied in one step.
class DemoDnsGateway implements BatchCapableGateway {
  /// Whether a record with [record]'s name, type, and content is in the zone.
  /// The dashboard watches records by their fully qualified name.
  bool contains(DnsRecord record) => records.any(
    (r) =>
        r.type == record.type &&
        r.canonicalContent == record.canonicalContent &&
        (r.name == record.name || record.name.startsWith('${r.name}.')),
  );

  final records = <DnsRecord>[
    const DnsRecord(
      id: 'web',
      type: DnsRecordType.a,
      name: 'seungpyo.online',
      content: '76.76.21.21',
      proxied: false,
    ),
    const DnsRecord(
      id: 'verify',
      type: DnsRecordType.txt,
      name: 'seungpyo.online',
      content: 'google-site-verification=example',
      proxied: false,
    ),
    DnsRecord.srv(
      id: 'minecraft',
      name: '_minecraft._tcp.seungpyo.online',
      priority: 0,
      weight: 0,
      port: 25565,
      target: 'mc.seungpyo.online',
    ),
    DnsRecord.mx(
      id: 'mail',
      name: 'seungpyo.online',
      priority: 10,
      mailServer: 'mail.seungpyo.online',
    ),
    DnsRecord.caa(
      id: 'caa',
      name: 'seungpyo.online',
      tag: 'issue',
      value: 'letsencrypt.org',
    ),
    DnsRecord.https(
      id: 'https',
      name: 'seungpyo.online',
      priority: 1,
      params: 'alpn="h3,h2"',
    ),
  ];

  @override
  Future<List<DnsZone>> listZones() async => const <DnsZone>[
    DnsZone(id: 'demo-zone', name: 'seungpyo.online', status: 'active'),
  ];

  @override
  Future<List<DnsRecord>> listRecords(String zoneId) async => List.of(records);

  @override
  Future<DnsRecord> createRecord(String zoneId, DnsRecord record) async {
    final created = record.copyWith(id: 'demo-${records.length + 1}');
    records.add(created);
    return created;
  }

  @override
  Future<DnsRecord> updateRecord(
    String zoneId,
    String recordId,
    DnsRecord record,
  ) async {
    final index = records.indexWhere((item) => item.id == recordId);
    final updated = record.copyWith(id: recordId);
    records[index] = updated;
    return updated;
  }

  @override
  Future<void> deleteRecord(String zoneId, String recordId) async {
    records.removeWhere((record) => record.id == recordId);
  }

  @override
  Future<DnsBatchResult> batch(
    String zoneId, {
    Iterable<DnsRecord> posts = const <DnsRecord>[],
    Iterable<DnsRecord> patches = const <DnsRecord>[],
    Iterable<String> deletes = const <String>[],
  }) async {
    final ids = deletes.toSet();
    records.removeWhere((record) => ids.contains(record.id));
    final updated = <DnsRecord>[
      for (final patch in patches) await updateRecord(zoneId, patch.id!, patch),
    ];
    final created = <DnsRecord>[
      for (final post in posts) await createRecord(zoneId, post),
    ];
    return DnsBatchResult(
      posts: created,
      patches: updated,
      deletes: ids.toList(growable: false),
    );
  }
}
1
likes
160
points
400
downloads
screenshot

Documentation

API reference

Publisher

verified publisherseungpyo.online

Weekly Downloads

Flutter toolkit for diagnosing and managing Cloudflare DNS: DoH health checks, safe API gateways, and Material 3 widgets.

Homepage
Repository (GitHub)
View/report issues

Topics

#cloudflare #dns #networking #material-design #ui

License

MIT (license)

Dependencies

flutter, http, http_parser

More

Packages that depend on flutter_cloudflare_dns