빌드하는 앱 유형에 따라 어떠한 사용자 또는 기기가 현재 온라인 상태인지 확인하는 것이 유용할 수 있습니다. 이러한 기능을 '접속 상태' 감지라고 합니다.
예를 들어 소셜 네트워크와 같은 앱을 빌드하거나 여러 IoT 기기를 배포할 경우 이 정보를 사용하여 채팅 가능한 온라인 상태인 친구 목록을 표시하거나 IoT 기기를 '최종 접속 시간' 순으로 정렬할 수 있습니다.
Firestore는 기본적으로 접속 상태를 지원하지 않지만 다른 Firebase 제품을 활용하여 접속 상태 시스템을 빌드할 수 있습니다.
솔루션: Cloud Functions 및 실시간 데이터베이스
Firebase 실시간 데이터베이스의 기본 접속 상태 기능에 Firestore를 연결하려면 Cloud Functions를 사용합니다.
실시간 데이터베이스를 사용하여 연결 상태를 보고한 후 Cloud Functions를 사용하여 이 데이터를 Firestore에 미러링합니다.
실시간 데이터베이스에서 접속 상태 사용
먼저 실시간 데이터베이스의 기존 접속 상태 시스템을 살펴보겠습니다.
웹
// Fetch the current user's ID from Firebase Authentication.
var uid = firebase.auth().currentUser.uid;
// Create a reference to this user's specific status node.
// This is where we will store data about being online/offline.
var userStatusDatabaseRef = firebase.database().ref('/status/' + uid);
// We'll create two constants which we will write to
// the Realtime database when this device is offline
// or online.
var isOfflineForDatabase = {
state: 'offline',
last_changed: firebase.database.ServerValue.TIMESTAMP,
};
var isOnlineForDatabase = {
state: 'online',
last_changed: firebase.database.ServerValue.TIMESTAMP,
};
// Create a reference to the special '.info/connected' path in
// Realtime Database. This path returns `true` when connected
// and `false` when disconnected.
firebase.database().ref('.info/connected').on('value', function(snapshot) {
// If we're not currently connected, don't do anything.
if (snapshot.val() == false) {
return;
};
// If we are currently connected, then use the 'onDisconnect()'
// method to add a set which will only trigger once this
// client has disconnected by closing the app,
// losing internet, or any other means.
userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
// The promise returned from .onDisconnect().set() will
// resolve as soon as the server acknowledges the onDisconnect()
// request, NOT once we've actually disconnected:
// https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect
// We can now safely set ourselves as 'online' knowing that the
// server will mark us as offline once we lose connection.
userStatusDatabaseRef.set(isOnlineForDatabase);
});
});
이 예시는 전체 실시간 데이터베이스 접속 상태 시스템으로서, 여러 연결 해제, 비정상 종료 등을 처리합니다.
Firestore에 연결
Firestore에서 비슷한 솔루션을 구현하려면 동일한 실시간 데이터베이스 코드를 사용한 후 Cloud Functions를 사용하여 실시간 데이터베이스와 Firestore를 동기화합니다.
프로젝트에 실시간 데이터베이스를 아직 추가하지 않았으면 지금 추가하고 위 접속 상태 솔루션을 포함합니다.
다음으로 다음 방법으로 접속 상태를 Firestore에 동기화합니다.
- 앱에서 기기가 오프라인 상태임을 파악하도록 로컬에서 오프라인 기기의 Firestore 캐시에 동기화합니다.
- 전역적으로 Cloud 함수로 사용하여 Cloud Firestore에 액세스하는 다른 모든 기기에서 이 특정한 기기가 오프라인 상태임을 파악하도록 합니다.
Firestore의 로컬 캐시 업데이트
첫 번째 문제, 즉 Firestore의 로컬 캐시 업데이트를 해결하는 데 필요한 변경사항을 살펴보겠습니다.
웹
// ...
var userStatusFirestoreRef = firebase.firestore().doc('/status/' + uid);
// Firestore uses a different server timestamp value, so we'll
// create two more constants for Firestore state.
var isOfflineForFirestore = {
state: 'offline',
last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};
var isOnlineForFirestore = {
state: 'online',
last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};
firebase.database().ref('.info/connected').on('value', function(snapshot) {
if (snapshot.val() == false) {
// Instead of simply returning, we'll also set Firestore's state
// to 'offline'. This ensures that our Firestore cache is aware
// of the switch to 'offline.'
userStatusFirestoreRef.set(isOfflineForFirestore);
return;
};
userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
userStatusDatabaseRef.set(isOnlineForDatabase);
// We'll also add Firestore set here for when we come online.
userStatusFirestoreRef.set(isOnlineForFirestore);
});
});
이렇게 변경하면 이제 로컬 Firestore 상태가 항상 기기의 온라인/오프라인 상태를 반영합니다. 따라서 /status/{uid}
문서를 리슨하고 이 데이터를 사용하여 연결 상태를 반영하도록 UI를 변경할 수 있습니다.
웹
userStatusFirestoreRef.onSnapshot(function(doc) {
var isOnline = doc.data().state == 'online';
// ... use isOnline
});
Firestore 전역 업데이트
이제 애플리케이션이 자신의 온라인 접속 상태를 올바르게 보고합니다. 하지만 '오프라인' 상태 쓰기는 로컬에서만 수행되고 연결이 복원될 때 동기화되지 않으므로 다른 Firestore 앱에서는 이 상태가 아직 정확하게 보고되지 않습니다. 이 문제를 해결하기 위해 실시간 데이터베이스의 status/{uid}
경로를 감시하는 Cloud 함수를 사용합니다. 실시간 데이터베이스 값이 변경되면 Firestore에 동기화되므로 모든 사용자 상태가 올바르게 인식됩니다.
Node.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// Since this code will be running in the Cloud Functions environment
// we call initialize Firestore without any arguments because it
// detects authentication from the environment.
const firestore = admin.firestore();
// Create a new function which is triggered on changes to /status/{uid}
// Note: This is a Realtime Database trigger, *not* Cloud Firestore.
exports.onUserStatusChanged = functions.database.ref('/status/{uid}').onUpdate(
async (change, context) => {
// Get the data written to Realtime Database
const eventStatus = change.after.val();
// Then use other event data to create a reference to the
// corresponding Firestore document.
const userStatusFirestoreRef = firestore.doc(`status/${context.params.uid}`);
// It is likely that the Realtime Database change that triggered
// this event has already been overwritten by a fast change in
// online / offline status, so we'll re-read the current data
// and compare the timestamps.
const statusSnapshot = await change.after.ref.once('value');
const status = statusSnapshot.val();
console.log(status, eventStatus);
// If the current timestamp for this data is newer than
// the data that triggered this event, we exit this function.
if (status.last_changed > eventStatus.last_changed) {
return null;
}
// Otherwise, we convert the last_changed field to a Date
eventStatus.last_changed = new Date(eventStatus.last_changed);
// ... and write it to Firestore.
return userStatusFirestoreRef.set(eventStatus);
});
이 함수를 배포하면 Firestore에서 실행되는 완전한 접속 상태 시스템이 완성됩니다. 다음은 where()
쿼리를 사용하여 온라인 또는 오프라인 상태로 전환하는 모든 사용자를 모니터링하는 예시입니다.
웹
firebase.firestore().collection('status')
.where('state', '==', 'online')
.onSnapshot(function(snapshot) {
snapshot.docChanges().forEach(function(change) {
if (change.type === 'added') {
var msg = 'User ' + change.doc.id + ' is online.';
console.log(msg);
// ...
}
if (change.type === 'removed') {
var msg = 'User ' + change.doc.id + ' is offline.';
console.log(msg);
// ...
}
});
});
제한사항
실시간 데이터베이스를 사용하여 Firestore 앱에 접속 상태를 추가하는 방법은 확장 가능하고 효율적이지만 몇 가지 제한사항이 있습니다.
- 디바운싱 - Firestore에서 실시간 변경사항을 리슨하는 경우 이 솔루션은 여러 변경사항을 트리거할 수 있습니다. 이러한 변경사항으로 인해 이벤트가 너무 많이 트리거되면 Firestore 이벤트를 수동으로 디바운싱합니다.
- 연결 - 이 구현에서는 Firestore가 아닌 실시간 데이터베이스에 대한 연결을 측정합니다. 각 데이터베이스에 대한 연결 상태가 동일하지 않으면 이 솔루션은 부정확한 접속 상태를 보고할 수 있습니다.
- Android - Android에서 실시간 데이터베이스는 비활성 상태가 60초간 지속되면 백엔드에서 연결을 끊습니다. 비활성 상태란 열린 상태의 리스너나 대기 중인 작업이 없다는 의미입니다. 연결을 열린 상태로 유지하려면 값 이벤트 리스너를
.info/connected
외의 경로에 추가하는 것이 좋습니다. 예를 들어 각 세션 시작 시FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced()
를 실행할 수 있습니다. 자세한 내용은 연결 상태 감지를 참조하세요.