Skip to content

Instantly share code, notes, and snippets.

@giautm
Last active September 15, 2018 07:53
Show Gist options
  • Save giautm/d349986204c4ec20f99e06c561ec1354 to your computer and use it in GitHub Desktop.
Save giautm/d349986204c4ec20f99e06c561ec1354 to your computer and use it in GitHub Desktop.
Real-time chat with firebase, support groups & direct chat.
import firebase from 'firebase'
export function defaultDirectKey(users) {
return users.sort().join('-')
}
class Firechat {
constructor(firebaseRef, options = {}) {
this.directKey = options.directKey || defaultDirectKey
this.maximumMessagesFetch = options.maximumMessagesFetch || 100
this.firebaseRef = firebaseRef
this.directMessagesRef = firebaseRef.child('direct-messages')
this.groupMessagesRef = firebaseRef.child('group-messages')
this.groupMetadataRef = firebaseRef.child('group-metadata')
this.usersRef = firebaseRef.child('users')
}
get uid() {
return (firebase.auth().currentUser || {}).uid
}
get timestamp() {
return firebase.database.ServerValue.TIMESTAMP
}
async gotoDirectMessage(otherUsers) {
const users = [...otherUsers, this.uid]
const directKey = this.directKey(users)
const snapshotGroup = await this.directMessagesRef.child(directKey).once('value')
let groupId = snapshotGroup.val()
if (!groupId) {
groupId = await this.createGroup('DirectMessage', 'direct', users)
const { snapshot } = await this.directMessagesRef
.child(directKey)
.transaction((currentData) => {
if (currentData === null) {
return groupId
}
return undefined
})
if (groupId !== snapshot.val()) {
// NOTE(giautm): Có direct messages group khác đã được tạo trước đó.
await this.deleteGroup(groupId)
groupId = snapshot.val()
}
}
return this.fetchGroupMetadata(groupId)
}
async fetchGroupMetadata(groupId) {
const snapshot = await this.groupMetadataRef
.child(groupId)
.once('value')
return snapshot.val()
}
async createGroup(name, type, users) {
const groupRef = this.groupMetadataRef.push()
await groupRef.set({
createdAt: this.timestamp,
createdByUserId: this.uid,
id: groupRef.key,
name,
type,
users: users.reduce((u, k) => ({
...u,
[k]: true,
}), {}),
})
// NOTE(giautm): This should happen on server-side or cloud function.
await Promise.all(users.map(uid => this.usersRef.child(
`${uid}/groups/${groupRef.key}`,
).set(true)))
return groupRef.key
}
async deleteGroup(groupId) {
const groupRef = this.groupMetadataRef.child(groupId)
// NOTE(giautm): This should happen on server-side or cloud function.
const snapshot = await groupRef.child('users').once('value')
const users = snapshot.val()
await users.map(uid => this.usersRef.child(
`${uid}/groups/${groupId}`,
).remove())
return groupRef.remove()
}
sendMessage(groupId, message) {
const messageRef = this.groupMessagesRef.child(groupId).push()
return messageRef.set({
...message,
createdAt: this.timestamp,
user: this.uid,
})
}
sendMessages(groupId, messages) {
return Promise.all(messages.map(m => this.sendMessage(groupId, m)))
}
onMessage = (groupId, callback) => this.groupMessagesRef.child(groupId)
.limitToLast(this.maximumMessagesFetch)
.on('child_added', snapshot => callback(this.parseMessage(snapshot)));
onGroupAdded = callback => this.usersRef.child(`${this.uid}/groups`)
.on('child_added', snapshot => this.parseGroup(snapshot.key).then(callback))
onGroupRemoved = callback => this.usersRef.child(`${this.uid}/groups`)
.on('child_removed', snapshot => this.parseGroup(snapshot.key).then(callback))
parseGroup = async (groupId) => {
const snapshot = await this.groupMetadataRef.child(groupId).once('value')
const { createdAt, ...rest } = snapshot.val()
return ({
...rest,
createdAt: new Date(createdAt),
})
};
parseMessage = (snapshot) => {
const { createdAt, text, user } = snapshot.val()
const { key: _id } = snapshot
return ({
_id,
createdAt: new Date(createdAt),
text,
user,
})
};
}
export default Firechat

Firebase Chat

Data structure

- group-messages/
  - <groupId>
    - <messageId>
      - userId: string The id of the user that sent the message.
      - name: string The name of the user that sent the message.
      - text: string The content of the message.
      - createdAt: string The time at which the message was sent.
- group-metadata/
  - <groupId>
    - id - The id of the group.
    - name - The public display name of the group.
    - type - The type of group, direct, public or private.
    - createdAt - The time at which the group was created.
    - createdByUserId - The id of the user that created the group.
    - users - A list of group User
- direct-messages/
  - <directKey> - A key that composited from array sorted of User's IDs.
    - <groupId> - ID of group
- users/
  - <userId>
    - id - The id of the user.
    - name - The display name of the user.
    - groups - A list of currently active groups.

Kịch bản

User A muốn chat với User B

B1: Tính directKey bằng các sắp xếp UserID của A & B theo thứ tự tăng dần, sau đó nối lại với nhau phân cách bằng kí tự -. Optional step: Băm chuỗi nhận được bằng MD5 để đảm bảo độ dài khoá.

function directKey(...args) {
  return args.sort().join('-');
}

B2: Kiểm tra giá trị groupId ở đường dẫn /direct-messages/<directKey>/. Nếu tồn tại giá trị, thực hiện bước B2.2

B2.1: Khi chưa có giá trị tại khoá trên.

  • Tạo group mới bằng cách push thêm node vào /group-metadata/ với các thông tin metadata.
  • Cập nhật giá trị groupId mới vào đường dẫn /direct-messages/<directKey>/<groupId>
  • Thêm groupId vào /users/<currentUserId>/groups

B2.2: Direct user đến groupId tương ứng.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment