import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart'; /// デバイスID取得サービス /// レート制限・ライセンス管理のためのデバイス識別に使用 /// /// ## 安定性の保証 /// - Android: ANDROID_ID をハッシュ化して使用(Factory Reset まで不変) /// - iOS: SharedPreferences に UUID を永続化(identifierForVendor は /// 全ベンダーアプリ削除後の再インストールで変わるため不使用) /// - その他/エラー時: SharedPreferences に UUID を永続化 class DeviceService { static final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin(); static String? _cachedDeviceId; static const _prefKey = 'ponshu_device_id'; /// デバイス固有のIDを取得(SHA256ハッシュ化) static Future getDeviceId() async { if (_cachedDeviceId != null) return _cachedDeviceId!; final prefs = await SharedPreferences.getInstance(); // SharedPreferencesに永続化済みならそれを使う(再インストール後も同じIDを返す) final stored = prefs.getString(_prefKey); if (stored != null && stored.isNotEmpty) { _cachedDeviceId = stored; debugPrint('[Device] Using persisted device ID: ${stored.substring(0, 8)}...'); return _cachedDeviceId!; } // 初回: ハードウェアIDから生成してSharedPreferencesに保存 final id = await _generateAndPersist(prefs); _cachedDeviceId = id; return id; } static Future _generateAndPersist(SharedPreferences prefs) async { String deviceIdentifier; try { if (defaultTargetPlatform == TargetPlatform.android) { final info = await _deviceInfo.androidInfo; // ANDROID_ID: アプリ再インストールでは変わらない。Factory Resetで変わる。 deviceIdentifier = 'android-${info.id}'; } else { // iOS / その他: UUIDを生成して永続化 // identifierForVendor は全ベンダーアプリ削除後の再インストールで変わるため不使用 deviceIdentifier = 'uuid-${const Uuid().v4()}'; } } catch (e) { debugPrint('[Device] Failed to get hardware ID, using UUID fallback: $e'); deviceIdentifier = 'fallback-${const Uuid().v4()}'; } final id = sha256.convert(utf8.encode(deviceIdentifier)).toString(); await prefs.setString(_prefKey, id); debugPrint('[Device] Generated and persisted device ID: ${id.substring(0, 8)}...'); return id; } /// デバイス情報をリセット(テスト用) static Future reset() async { _cachedDeviceId = null; final prefs = await SharedPreferences.getInstance(); await prefs.remove(_prefKey); } }