moxxy/lib/service/service.dart

292 lines
10 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';
import 'package:moxlib/moxlib.dart';
import 'package:moxplatform/moxplatform.dart';
import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxyv2/i18n/strings.g.dart';
import 'package:moxxyv2/service/avatars.dart';
import 'package:moxxyv2/service/blocking.dart';
import 'package:moxxyv2/service/connectivity.dart';
import 'package:moxxyv2/service/connectivity_watcher.dart';
import 'package:moxxyv2/service/contacts.dart';
import 'package:moxxyv2/service/conversation.dart';
import 'package:moxxyv2/service/cryptography/cryptography.dart';
import 'package:moxxyv2/service/database/database.dart';
import 'package:moxxyv2/service/events.dart';
import 'package:moxxyv2/service/httpfiletransfer/httpfiletransfer.dart';
import 'package:moxxyv2/service/language.dart';
import 'package:moxxyv2/service/message.dart';
import 'package:moxxyv2/service/moxxmpp/disco.dart';
import 'package:moxxyv2/service/moxxmpp/omemo.dart';
import 'package:moxxyv2/service/moxxmpp/reconnect.dart';
import 'package:moxxyv2/service/moxxmpp/roster.dart';
import 'package:moxxyv2/service/moxxmpp/socket.dart';
import 'package:moxxyv2/service/moxxmpp/stream.dart';
import 'package:moxxyv2/service/notifications.dart';
import 'package:moxxyv2/service/omemo/omemo.dart';
import 'package:moxxyv2/service/preferences.dart';
import 'package:moxxyv2/service/roster.dart';
import 'package:moxxyv2/service/stickers.dart';
import 'package:moxxyv2/service/xmpp.dart';
import 'package:moxxyv2/shared/commands.dart';
import 'package:moxxyv2/shared/eventhandler.dart';
import 'package:moxxyv2/shared/events.dart';
import 'package:moxxyv2/shared/logging.dart';
import 'package:moxxyv2/shared/synchronized_queue.dart';
import 'package:moxxyv2/ui/events.dart' as ui_events;
Future<void> initializeServiceIfNeeded() async {
final logger = GetIt.I.get<Logger>();
final handler = MoxplatformPlugin.handler;
if (await handler.isRunning()) {
if (kDebugMode) {
logger.fine('Since kDebugMode is true, waiting 600ms before sending PreStartCommand');
sleep(const Duration(milliseconds: 600));
}
logger.info('Attaching to service...');
await handler.attach(ui_events.receiveIsolateEvent);
logger.info('Done');
// ignore: cascade_invocations
logger.info('Service is running. Sending pre start command');
await handler.getDataSender().sendData(
PerformPreStartCommand(
systemLocaleCode: WidgetsBinding.instance.platformDispatcher.locale.toLanguageTag(),
),
awaitable: false,
);
} else {
logger.info('Service is not running. Initializing service... ');
await handler.start(
entrypoint,
receiveUIEvent,
ui_events.handleIsolateEvent,
);
}
}
/// A middleware for packing an event into a [DataWrapper] and also
/// logging what we send.
void sendEvent(BackgroundEvent event, { String? id }) {
// NOTE: *S*erver to *F*oreground
GetIt.I.get<Logger>().fine('--> ${event.toJson()["type"]}');
GetIt.I.get<BackgroundService>().sendEvent(event, id: id);
}
void setupLogging() {
Logger.root.level = kDebugMode ? Level.ALL : Level.INFO;
Logger.root.onRecord.listen((record) {
final logMessageHeader = '[${record.level.name}] (${record.loggerName}) ${record.time}: ';
var msg = record.message;
do {
final tooLong = logMessageHeader.length + msg.length >= 967;
final line = tooLong ? msg.substring(0, 967 - logMessageHeader.length) : msg;
if (tooLong) {
msg = msg.substring(967 - logMessageHeader.length - 2);
} else {
msg = '';
}
final logMessage = logMessageHeader + line;
if (GetIt.I.isRegistered<UDPLogger>()) {
final udp = GetIt.I.get<UDPLogger>();
if (udp.isEnabled()) {
udp.sendLog(logMessage, record.time.millisecondsSinceEpoch, record.level.name);
}
}
// ignore: literal_only_boolean_expressions
if (/*kDebugMode*/ true) {
// ignore: avoid_print
print(logMessage);
}
} while (msg.isNotEmpty);
});
}
Future<void> initUDPLogger() async {
final prefs = await GetIt.I.get<PreferencesService>().getPreferences();
if (prefs.debugEnabled) {
Logger('initUDPLogger').finest('UDPLogger created');
final port = prefs.debugPort;
final ip = prefs.debugIp;
final passphrase = prefs.debugPassphrase;
if (port != 0 && ip.isNotEmpty && passphrase.isNotEmpty) {
await GetIt.I.get<UDPLogger>().init(passphrase, ip, port);
}
} else {
GetIt.I.get<UDPLogger>().setEnabled(false);
}
}
/// The entrypoint for all platforms after the platform specific initilization is done.
@pragma('vm:entry-point')
Future<void> entrypoint() async {
setupLogging();
setupBackgroundEventHandler();
// Register singletons
GetIt.I.registerSingleton<Logger>(Logger('MoxxyService'));
GetIt.I.registerSingleton<UDPLogger>(UDPLogger());
GetIt.I.registerSingleton<LanguageService>(LanguageService());
// Initialize the database
GetIt.I.registerSingleton<DatabaseService>(DatabaseService());
await GetIt.I.get<DatabaseService>().initialize();
GetIt.I.registerSingleton<PreferencesService>(PreferencesService());
GetIt.I.registerSingleton<BlocklistService>(BlocklistService());
GetIt.I.registerSingleton<NotificationsService>(NotificationsService());
GetIt.I.registerSingleton<HttpFileTransferService>(HttpFileTransferService());
GetIt.I.registerSingleton<AvatarService>(AvatarService());
GetIt.I.registerSingleton<RosterService>(RosterService());
GetIt.I.registerSingleton<ConversationService>(ConversationService());
GetIt.I.registerSingleton<MessageService>(MessageService());
GetIt.I.registerSingleton<OmemoService>(OmemoService());
GetIt.I.registerSingleton<CryptographyService>(CryptographyService());
GetIt.I.registerSingleton<ContactsService>(ContactsService());
GetIt.I.registerSingleton<StickersService>(StickersService());
final xmpp = XmppService();
GetIt.I.registerSingleton<XmppService>(xmpp);
await GetIt.I.get<NotificationsService>().init();
await GetIt.I.get<ContactsService>().init();
if (!kDebugMode) {
final enableDebug = (await GetIt.I.get<PreferencesService>().getPreferences()).debugEnabled;
Logger.root.level = enableDebug ? Level.ALL : Level.INFO;
}
// Init the UDPLogger
await initUDPLogger();
GetIt.I.registerSingleton<MoxxyReconnectionPolicy>(MoxxyReconnectionPolicy());
final connection = XmppConnection(
GetIt.I.get<MoxxyReconnectionPolicy>(),
MoxxyTCPSocketWrapper(),
)..registerManagers([
MoxxyStreamManagementManager(),
MoxxyDiscoManager(),
RosterManager(MoxxyRosterStateManager()),
MoxxyOmemoManager(),
PingManager(),
MessageManager(),
PresenceManager('http://moxxy.im'),
CSIManager(),
CarbonsManager(),
PubSubManager(),
VCardManager(),
UserAvatarManager(),
StableIdManager(),
MessageDeliveryReceiptManager(),
ChatMarkerManager(),
OOBManager(),
SFSManager(),
MessageRepliesManager(),
BlockingManager(),
ChatStateManager(),
HttpFileUploadManager(),
FileUploadNotificationManager(),
EmeManager(),
CryptographicHashManager(),
DelayedDeliveryManager(),
MessageRetractionManager(),
LastMessageCorrectionManager(),
MessageReactionsManager(),
StickersManager(),
])
..registerFeatureNegotiators([
ResourceBindingNegotiator(),
StartTlsNegotiator(),
StreamManagementNegotiator(),
CSINegotiator(),
RosterFeatureNegotiator(),
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
SaslScramNegotiator(9, '', '', ScramHashType.sha256),
SaslScramNegotiator(8, '', '', ScramHashType.sha1),
SaslPlainNegotiator(),
]);
GetIt.I.registerSingleton<XmppConnection>(connection);
GetIt.I.registerSingleton<ConnectivityWatcherService>(ConnectivityWatcherService());
GetIt.I.registerSingleton<ConnectivityService>(ConnectivityService());
await GetIt.I.get<ConnectivityService>().initialize();
GetIt.I.get<Logger>().finest('Done with xmpp');
final settings = await xmpp.getConnectionSettings();
// Ensure we can access translations here
// TODO(Unknown): This does *NOT* allow us to get the system's locale as we have no
// window here.
WidgetsFlutterBinding.ensureInitialized();
LocaleSettings.useDeviceLocale();
GetIt.I.get<Logger>().finest('Got settings');
if (settings != null) {
unawaited(GetIt.I.get<OmemoService>().initializeIfNeeded(settings.jid.toBare().toString()));
// The title of the notification will be changed as soon as the connection state
// of [XmppConnection] changes.
await connection.getManagerById<MoxxyStreamManagementManager>(smManager)!.loadState();
await xmpp.connect(settings, false);
} else {
GetIt.I.get<BackgroundService>().setNotification(
'Moxxy',
t.notifications.permanent.idle,
);
}
unawaited(GetIt.I.get<SynchronizedQueue<Map<String, dynamic>?>>().removeQueueLock());
sendEvent(ServiceReadyEvent());
}
@pragma('vm:entry-point')
Future<void> receiveUIEvent(Map<String, dynamic>? data) async {
await GetIt.I.get<SynchronizedQueue<Map<String, dynamic>?>>().add(data);
}
Future<void> handleUIEvent(Map<String, dynamic>? data) async {
// NOTE: *F*oreground to *S*ervice
final log = GetIt.I.get<Logger>();
if (data == null) {
log.warning('Received null from the UI isolate. Ignoring...');
return;
}
final id = data['id']! as String;
final command = getCommandFromJson(data['data']! as Map<String, dynamic>);
if (command == null) {
log.severe("Unknown command type ${data['type']}");
return;
}
if (command is LoginCommand) {
final redacted = {
'id': id,
'data': LoginCommand(
jid: command.jid,
password: '*******',
useDirectTLS: command.useDirectTLS,
).toJson()
};
log.fine('F2S: $redacted');
} else {
log.fine('F2S: $data');
}
unawaited(GetIt.I.get<EventHandler>().run(command, extra: id));
}