h-1.flutter.0/lib/services/mothership_chat_client.dart
2026-03-01 20:45:29 +09:00

120 lines
4 KiB
Dart

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import '../models/chat_message.dart';
import 'chat_repository.dart';
import 'mothership_client.dart';
class MothershipChatClient {
MothershipChatClient({ChatRepository? repository, MothershipClient? baseClient, http.Client? httpClient})
: _repository = repository ?? ChatRepository(),
_baseClient = baseClient ?? MothershipClient(),
_httpClient = httpClient;
final ChatRepository _repository;
final MothershipClient _baseClient;
final http.Client? _httpClient;
Future<void> sync() async {
await Future.wait([_pushPending(), _fetchInbound()]);
}
Future<void> _pushPending() async {
final config = await _baseClient.loadConfig();
if (config == null) {
debugPrint('[ChatSync] skip push: config missing');
return;
}
final clientId = await _baseClient.ensureClientId();
final pending = await _repository.pendingOutbound();
if (pending.isEmpty) return;
final client = _httpClient ?? http.Client();
try {
final payload = {
'clientId': clientId,
'messages': pending
.map((m) => {
'messageId': m.messageId,
'body': m.body,
'createdAt': m.createdAt.millisecondsSinceEpoch,
})
.toList(),
};
final res = await client.post(
config.chatSendUri,
headers: {
'content-type': 'application/json',
'x-api-key': config.apiKey,
},
body: jsonEncode(payload),
);
if (res.statusCode >= 200 && res.statusCode < 300) {
await _repository.markSynced(pending.map((e) => e.messageId).toList());
debugPrint('[ChatSync] pushed ${pending.length} msgs');
} else {
debugPrint('[ChatSync] push failed ${res.statusCode} ${res.body}');
}
} catch (err) {
debugPrint('[ChatSync] push error $err');
} finally {
if (_httpClient == null) client.close();
}
}
Future<void> _fetchInbound() async {
final config = await _baseClient.loadConfig();
if (config == null) return;
final clientId = await _baseClient.ensureClientId();
final client = _httpClient ?? http.Client();
try {
final uri = config.chatPendingUri.replace(queryParameters: {'clientId': clientId});
final res = await client.get(uri, headers: {'x-api-key': config.apiKey});
if (res.statusCode >= 200 && res.statusCode < 300) {
final decoded = jsonDecode(res.body) as Map<String, dynamic>;
final messages = (decoded['messages'] as List?) ?? [];
final ackIds = <String>[];
for (final raw in messages.cast<Map>()) {
final msg = ChatMessage(
messageId: raw['messageId'] as String,
clientId: clientId,
direction: ChatDirection.inbound,
body: raw['body'] as String,
createdAt: DateTime.fromMillisecondsSinceEpoch((raw['createdAt'] as int?) ?? 0, isUtc: true),
synced: true,
);
await _repository.upsertInbound(msg);
ackIds.add(msg.messageId);
}
if (ackIds.isNotEmpty) {
await _ack(config, clientId, ackIds);
}
} else {
debugPrint('[ChatSync] fetch failed ${res.statusCode} ${res.body}');
}
} catch (err) {
debugPrint('[ChatSync] fetch error $err');
} finally {
if (_httpClient == null) client.close();
}
}
Future<void> _ack(MothershipEndpointConfig config, String clientId, List<String> ids) async {
final client = _httpClient ?? http.Client();
try {
await client.post(
config.chatAckUri,
headers: {
'content-type': 'application/json',
'x-api-key': config.apiKey,
},
body: jsonEncode({'clientId': clientId, 'delivered': ids}),
);
} catch (err) {
debugPrint('[ChatSync] ack error $err');
} finally {
if (_httpClient == null) client.close();
}
}
}