flyerhq/react-native-firebase-chat-core

View on GitHub
src/utils.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { FirebaseAuthTypes } from '@react-native-firebase/auth'
import firestore, {
  FirebaseFirestoreTypes,
} from '@react-native-firebase/firestore'

import { FirebaseChatCoreConfig, Room, User } from './types'

export let ROOMS_COLLECTION_NAME = 'rooms'
export let USERS_COLLECTION_NAME = 'users'

/** Sets custom config to change default names for rooms
 * and users collections. Also see {@link FirebaseChatCoreConfig}. */
export const setConfig = (config: FirebaseChatCoreConfig) => {
  ROOMS_COLLECTION_NAME = config.roomsCollectionName
  USERS_COLLECTION_NAME = config.usersCollectionName
}

/** Creates {@link User} in Firebase to store name and avatar used on rooms list */
export const createUserInFirestore = async (user: User) => {
  await firestore().collection(USERS_COLLECTION_NAME).doc(user.id).set({
    createdAt: firestore.FieldValue.serverTimestamp(),
    firstName: user.firstName,
    imageUrl: user.imageUrl,
    lastName: user.lastName,
    lastSeen: user.lastSeen,
    metadata: user.metadata,
    role: user.role,
    updatedAt: firestore.FieldValue.serverTimestamp(),
  })
}

/** Removes {@link User} from `users` collection in Firebase */
export const deleteUserFromFirestore = async (userId: string) => {
  await firestore().collection(USERS_COLLECTION_NAME).doc(userId).delete()
}

/** Fetches user from Firebase and returns a promise */
export const fetchUser = async (userId: string, role?: User['role']) => {
  const doc = await firestore()
    .collection(USERS_COLLECTION_NAME)
    .doc(userId)
    .get()

  const data = doc.data()!

  const user: User = {
    // Ignore types here, not provided by the Firebase library
    // type-coverage:ignore-next-line
    createdAt: data.createdAt?.toMillis() ?? undefined,
    // type-coverage:ignore-next-line
    firstName: data.firstName ?? undefined,
    id: doc.id,
    // type-coverage:ignore-next-line
    imageUrl: data.imageUrl ?? undefined,
    // type-coverage:ignore-next-line
    lastName: data.lastName ?? undefined,
    // type-coverage:ignore-next-line
    lastSeen: data.lastSeen?.toMillis() ?? undefined,
    // type-coverage:ignore-next-line
    metadata: data.metadata ?? undefined,
    role,
    // type-coverage:ignore-next-line
    updatedAt: data.updatedAt?.toMillis() ?? undefined,
  }

  return user
}

/** Returns an array of {@link Room}s created from Firebase query.
 * If room has 2 participants, sets correct room name and image. */
export const processRoomsQuery = async ({
  firebaseUser,
  query,
}: {
  firebaseUser: FirebaseAuthTypes.User
  query: FirebaseFirestoreTypes.QuerySnapshot
}) => {
  const promises = query.docs.map(async (doc) =>
    processRoomDocument({ doc, firebaseUser })
  )

  return await Promise.all(promises)
}

/** Returns a {@link Room} created from Firebase document */
export const processRoomDocument = async ({
  doc,
  firebaseUser,
}: {
  doc:
    | FirebaseFirestoreTypes.DocumentSnapshot<FirebaseFirestoreTypes.DocumentData>
    | FirebaseFirestoreTypes.QueryDocumentSnapshot<FirebaseFirestoreTypes.DocumentData>
  firebaseUser: FirebaseAuthTypes.User
}) => {
  const data = doc.data()!

  // Ignore types here, not provided by the Firebase library
  // type-coverage:ignore-next-line
  const createdAt = data.createdAt?.toMillis() ?? undefined
  const id = doc.id
  // type-coverage:ignore-next-line
  const updatedAt = data.updatedAt?.toMillis() ?? undefined

  // type-coverage:ignore-next-line
  let imageUrl = data.imageUrl ?? undefined
  let lastMessages
  // type-coverage:ignore-next-line
  let name = data.name ?? undefined
  // type-coverage:ignore-next-line
  const metadata = data.metadata ?? undefined
  // type-coverage:ignore-next-line
  const type = data.type as Room['type']
  // type-coverage:ignore-next-line
  const userIds = data.userIds as string[]
  const userRoles =
    // type-coverage:ignore-next-line
    (data.userRoles as Record<string, User['role']>) ?? undefined

  const users = await Promise.all(
    userIds.map((userId) => fetchUser(userId, userRoles?.[userId]))
  )

  if (type === 'direct') {
    const otherUser = users.find((u) => u.id !== firebaseUser.uid)

    if (otherUser) {
      imageUrl = otherUser.imageUrl
      name = `${otherUser.firstName ?? ''} ${otherUser.lastName ?? ''}`.trim()
    }
  }

  // type-coverage:ignore-next-line
  if (data.lastMessages && data.lastMessages instanceof Array) {
    // type-coverage:ignore-next-line
    lastMessages = data.lastMessages.map((lm: any) => {
      // type-coverage:ignore-next-line
      const author = users.find((u) => u.id === lm.authorId) ?? {
        // type-coverage:ignore-next-line
        id: lm.authorId as string,
      }

      return {
        // type-coverage:ignore-next-line
        ...(lm ?? {}),
        author,
        // type-coverage:ignore-next-line
        createdAt: lm.createdAt?.toMillis() ?? undefined,
        // type-coverage:ignore-next-line
        id: lm.id ?? '',
        // type-coverage:ignore-next-line
        updatedAt: lm.updatedAt?.toMillis() ?? undefined,
      }
    })
  }

  const room: Room = {
    createdAt,
    id,
    imageUrl,
    lastMessages,
    metadata,
    name,
    type,
    updatedAt,
    users,
  }

  return room
}