getEventByPushNotification method

Future<Event?> getEventByPushNotification(
  1. PushNotification notification, {
  2. bool storeInDatabase = true,
  3. Duration timeoutForServerRequests = const Duration(seconds: 8),
  4. bool returnNullIfSeen = true,
})

Fetches the corresponding Event object from a notification including a full Room object with the sender User object in it. Returns null if this push notification is not corresponding to an existing event. The client does not need to be initialized first. If it is not initialized, it will only fetch the necessary parts of the database. This should make it possible to run this parallel to another client with the same client name. This also checks if the given event has a readmarker and returns null in this case.

Implementation

Future<Event?> getEventByPushNotification(
  PushNotification notification, {
  bool storeInDatabase = true,
  Duration timeoutForServerRequests = const Duration(seconds: 8),
  bool returnNullIfSeen = true,
}) async {
  // Get access token if necessary:
  final database = _database ??= await databaseBuilder?.call(this);
  if (!isLogged()) {
    if (database == null) {
      throw Exception(
          'Can not execute getEventByPushNotification() without a database');
    }
    final clientInfoMap = await database.getClient(clientName);
    final token = clientInfoMap?.tryGet<String>('token');
    if (token == null) {
      throw Exception('Client is not logged in.');
    }
    accessToken = token;
  }

  // Check if the notification contains an event at all:
  final eventId = notification.eventId;
  final roomId = notification.roomId;
  if (eventId == null || roomId == null) return null;

  // Create the room object:
  final room = getRoomById(roomId) ??
      await database?.getSingleRoom(this, roomId) ??
      Room(
        id: roomId,
        client: this,
      );
  final roomName = notification.roomName;
  final roomAlias = notification.roomAlias;
  if (roomName != null) {
    room.setState(Event(
      eventId: 'TEMP',
      stateKey: '',
      type: EventTypes.RoomName,
      content: {'name': roomName},
      room: room,
      senderId: 'UNKNOWN',
      originServerTs: DateTime.now(),
    ));
  }
  if (roomAlias != null) {
    room.setState(Event(
      eventId: 'TEMP',
      stateKey: '',
      type: EventTypes.RoomCanonicalAlias,
      content: {'alias': roomAlias},
      room: room,
      senderId: 'UNKNOWN',
      originServerTs: DateTime.now(),
    ));
  }

  // Load the event from the notification or from the database or from server:
  SDNEvent? sdnEvent;
  final content = notification.content;
  final sender = notification.sender;
  final type = notification.type;
  if (content != null && sender != null && type != null) {
    sdnEvent = SDNEvent(
      content: content,
      senderId: sender,
      type: type,
      originServerTs: DateTime.now(),
      eventId: eventId,
      roomId: roomId,
    );
  }
  sdnEvent ??= await database
      ?.getEventById(eventId, room)
      .timeout(timeoutForServerRequests);

  try {
    sdnEvent ??= await getOneRoomEvent(roomId, eventId)
        .timeout(timeoutForServerRequests);
  } on SDNException catch (_) {
    // No access to the SDNEvent. Search in /notifications
    final notificationsResponse = await getNotifications();
    sdnEvent ??= notificationsResponse.notifications
        .firstWhereOrNull((notification) =>
            notification.roomId == roomId &&
            notification.event.eventId == eventId)
        ?.event;
  }

  if (sdnEvent == null) {
    throw Exception('Unable to find event for this push notification!');
  }

  // If the event was already in database, check if it has a read marker
  // before displaying it.
  if (returnNullIfSeen) {
    if (room.fullyRead == sdnEvent.eventId) {
      return null;
    }
    final readMarkerEvent = await database
        ?.getEventById(room.fullyRead, room)
        .timeout(timeoutForServerRequests);
    if (readMarkerEvent != null &&
        readMarkerEvent.originServerTs.isAfter(
          sdnEvent.originServerTs
            // As origin server timestamps are not always correct data in
            // a federated environment, we add 10 minutes to the calculation
            // to reduce the possibility that an event is marked as read which
            // isn't.
            ..add(Duration(minutes: 10)),
        )) {
      return null;
    }
  }

  // Load the sender of this event
  try {
    await room
        .requestUser(sdnEvent.senderId)
        .timeout(timeoutForServerRequests);
  } catch (e, s) {
    Logs().w('Unable to request user for push helper', e, s);
    final senderDisplayName = notification.senderDisplayName;
    if (senderDisplayName != null && sender != null) {
      room.setState(User(sender, displayName: senderDisplayName, room: room));
    }
  }

  // Create Event object and decrypt if necessary
  var event = Event.fromSDNEvent(
    sdnEvent,
    room,
    status: EventStatus.sent,
  );

  final encryption = this.encryption;
  if (event.type == EventTypes.Encrypted && encryption != null) {
    var decrypted = await encryption.decryptRoomEvent(roomId, event);
    if (decrypted.messageType == MessageTypes.BadEncrypted &&
        prevBatch != null) {
      await oneShotSync();
      decrypted = await encryption.decryptRoomEvent(roomId, event);
    }
    event = decrypted;
  }

  if (storeInDatabase) {
    await database?.transaction(() async {
      await database.storeEventUpdate(
          EventUpdate(
            roomID: roomId,
            type: EventUpdateType.timeline,
            content: event.toJson(),
          ),
          this);
    });
  }

  return event;
}