@react-native-firebase/database
Version:
React Native Firebase - The Firebase Realtime Database is a cloud-hosted database. Data is stored as JSON and synchronized in realtime to every connected client. React Native Firebase provides native integration with the Android & iOS Firebase SDKs, suppo
546 lines (488 loc) • 15.6 kB
JavaScript
import {
getApp,
getDatabase,
connectDatabaseEmulator,
enableLogging,
goOnline,
goOffline,
ref,
set,
update,
setWithPriority,
remove,
setPriority,
onDisconnect,
onValue,
onChildAdded,
onChildChanged,
onChildMoved,
onChildRemoved,
runTransaction,
} from '@react-native-firebase/app/lib/internal/web/firebaseDatabase';
import { guard, getWebError, emitEvent } from '@react-native-firebase/app/lib/internal/web/utils';
import { getQueryInstance } from './query';
// Converts a DataSnapshot to an object.
function snapshotToObject(snapshot) {
const childKeys = [];
if (snapshot.hasChildren()) {
snapshot.forEach(childSnapshot => {
childKeys.push(childSnapshot.key);
});
}
return {
key: snapshot.key,
exists: snapshot.exists(),
hasChildren: snapshot.hasChildren(),
childrenCount: snapshot.size,
childKeys,
priority: snapshot.priority,
value: snapshot.val(),
};
}
function getDatabaseWebError(error) {
// Possible to override messages/codes here if necessary.
return getWebError(error);
}
// Converts a DataSnapshot and previous child name to an object.
function snapshotWithPreviousChildToObject(snapshot, previousChildName) {
return {
snapshot: snapshotToObject(snapshot),
previousChildName,
};
}
const appInstances = {};
const databaseInstances = {};
const onDisconnectRef = {};
const listeners = {};
const emulatorForApp = {};
function getCachedAppInstance(appName) {
return (appInstances[appName] ??= getApp(appName));
}
// Returns a cached Database instance.
function getCachedDatabaseInstance(appName, dbURL) {
let instance = databaseInstances[`${appName}|${dbURL}`];
if (!instance) {
instance = getDatabase(getCachedAppInstance(appName), dbURL);
// Relying on internals here so need to be careful between SDK versions.
if (emulatorForApp[appName] && !instance._instanceStarted) {
const { host, port } = emulatorForApp[appName];
connectDatabaseEmulator(instance, host, port);
emulatorForApp[appName].connected = true;
}
}
return instance;
}
// Returns a cached onDisconnect instance.
function getCachedOnDisconnectInstance(ref) {
return (onDisconnectRef[ref.key] ??= onDisconnect(ref));
}
export default {
/**
* Reconnects to the server.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @returns {Promise<void>}
*/
goOnline(appName, dbURL) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
goOnline(db);
});
},
/**
* Disconnects from the server.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @returns {Promise<void>}
*/
goOffline(appName, dbURL) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
goOffline(db);
});
},
setPersistenceEnabled() {
if (__DEV__) {
// eslint-disable-next-line no-console
console.warn(
'The Firebase Database `setPersistenceEnabled` method is not available in the this environment.',
);
}
return Promise.resolve();
},
/**
* Sets the logging enabled state.
* @param {string} appName - The app name, not used.
* @param {string} dbURL - The database URL, not used.
* @param {boolean} enabled - The logging enabled state.
*/
setLoggingEnabled(_app, _dbURL, enabled) {
return guard(async () => {
enableLogging(enabled);
});
},
setPersistenceCacheSizeBytes() {
// no-op on other platforms
return Promise.resolve();
},
/**
* Connects to the Firebase database emulator.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} host - The emulator host.
* @param {number} port - The emulator
* @returns {Promise<void>}
*/
useEmulator(appName, dbURL, host, port) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
connectDatabaseEmulator(db, host, port);
emulatorForApp[appName] = { host, port };
});
},
/**
* Reference
*/
/**
* Sets a value at the specified path.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @param {object} props - The properties
* @returns {Promise<void>}
*/
set(appName, dbURL, path, props) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
const value = props.value;
await set(dbRef, value);
});
},
/**
* Updates the specified path with the provided values.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @param {object} props - The properties
* @returns {Promise<void>}
*/
update(appName, dbURL, path, props) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
const values = props.values;
await update(dbRef, values);
});
},
/**
* Sets a value at the specified path with a priority.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @param {object} props - The properties, including value and priority.
* @returns {Promise<void>}
*/
setWithPriority(appName, dbURL, path, props) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
const value = props.value;
const priority = props.priority;
await setWithPriority(dbRef, value, priority);
});
},
/**
* Removes the nodd at the specified path.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @returns {Promise<void>}
*/
remove(appName, dbURL, path) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
await remove(dbRef);
});
},
/**
* Sets the priority of the node at the specified path.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @param {object} props - The properties, including priority.
* @returns {Promise<void>}
*/
setPriority(appName, dbURL, path, props) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
const priority = props.priority;
await setPriority(dbRef, priority);
});
},
/**
* Query
*/
/**
* Listens for data changes at the specified path once.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @param {object} modifiers - The modifiers.
* @param {string} eventType - The event type.
* @returns {Promise<object>}
*/
once(appName, dbURL, path, modifiers, eventType) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
const queryRef = getQueryInstance(dbRef, modifiers);
if (eventType === 'value') {
const snapshot = await new Promise((resolve, reject) => {
onValue(queryRef, resolve, reject, { onlyOnce: true });
});
return snapshotToObject(snapshot);
} else {
let fn = null;
if (eventType === 'child_added') {
fn = onChildAdded;
} else if (eventType === 'child_changed') {
fn = onChildChanged;
} else if (eventType === 'child_removed') {
fn = onChildRemoved;
} else if (eventType === 'child_moved') {
fn = onChildMoved;
}
if (fn) {
const { snapshot, previousChildName } = await new Promise((resolve, reject) => {
fn(
queryRef,
(snapshot, previousChildName) => {
resolve({ snapshot, previousChildName });
},
reject,
{ onlyOnce: true },
);
});
return snapshotWithPreviousChildToObject(snapshot, previousChildName);
}
}
const snapshot = await get(dbRef, modifiers);
return snapshot;
});
},
on(appName, dbURL, props) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const { key, modifiers, path, eventType, registration } = props;
const { eventRegistrationKey } = registration;
const dbRef = ref(db, path);
const queryRef = getQueryInstance(dbRef, modifiers);
function sendEvent(data) {
const event = {
eventName: 'database_sync_event',
body: {
data,
key,
registration,
eventType,
},
};
emitEvent('database_sync_event', event);
}
function sendError(error) {
const event = {
eventName: 'database_sync_event',
body: {
key,
registration,
error: getDatabaseWebError(error),
},
};
emitEvent('database_sync_event', event);
}
let listener = null;
// Ignore if the listener already exists.
if (listeners[eventRegistrationKey]) {
return;
}
if (eventType === 'value') {
listener = onValue(queryRef, snapshot => sendEvent(snapshotToObject(snapshot)), sendError);
} else {
let fn = null;
if (eventType === 'child_added') {
fn = onChildAdded;
} else if (eventType === 'child_changed') {
fn = onChildChanged;
} else if (eventType === 'child_removed') {
fn = onChildRemoved;
} else if (eventType === 'child_moved') {
fn = onChildMoved;
}
if (fn) {
listener = fn(
queryRef,
(snapshot, previousChildName) => {
sendEvent(snapshotWithPreviousChildToObject(snapshot, previousChildName));
},
sendError,
);
}
}
listeners[eventRegistrationKey] = listener;
});
},
off(_queryKey, eventRegistrationKey) {
const listener = listeners[eventRegistrationKey];
if (listener) {
listener();
delete listeners[eventRegistrationKey];
}
},
keepSynced() {
return rejectPromiseWithCodeAndMessage(
'unsupported',
'This operation is not supported on this environment.',
);
},
/**
* OnDisconnect
*/
/**
* Cancels the onDisconnect instance at the specified path.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @returns {Promise<void>}
*/
onDisconnectCancel(appName, dbURL, path) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
const instance = getCachedOnDisconnectInstance(dbRef);
await instance.cancel();
// Delete the onDisconnect instance from the cache.
delete onDisconnectRef[dbRef.key];
});
},
/**
* Sets a value to be written to the database on disconnect.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @returns {Promise<void>}
*/
onDisconnectRemove(appName, dbURL, path) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
const instance = getCachedOnDisconnectInstance(dbRef);
await instance.remove();
});
},
/**
* Sets a value to be written to the database on disconnect.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @param {object} props - The properties, including value.
* @returns {Promise<void>}
*/
onDisconnectSet(appName, dbURL, path, props) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
const instance = getCachedOnDisconnectInstance(dbRef);
const value = props.value;
await instance.set(value);
});
},
/**
* Sets a value to be written to the database on disconnect with a priority.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @param {object} props - The properties, including value and priority.
* @returns {Promise<void>}
*/
onDisconnectSetWithPriority(appName, dbURL, path, props) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
const instance = getCachedOnDisconnectInstance(dbRef);
const value = props.value;
const priority = props.priority;
await instance.setWithPriority(value, priority);
});
},
/**
* Updates the specified path with the provided values on disconnect.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} path - The path.
* @param {object} props - The properties, including values.
* @returns {Promise<void>}
*/
onDisconnectUpdate(appName, dbURL, path, props) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
const instance = getCachedOnDisconnectInstance(dbRef);
const values = props.values;
await instance.update(values);
});
},
/**
* Transaction
*/
transactionStart(appName, dbURL, path, transactionId, applyLocally, userExecutor) {
return guard(async () => {
const db = getCachedDatabaseInstance(appName, dbURL);
const dbRef = ref(db, path);
try {
const { committed, snapshot } = await runTransaction(dbRef, userExecutor, {
applyLocally,
});
const event = {
body: {
committed,
type: 'complete',
snapshot: snapshotToObject(snapshot),
},
appName,
id: transactionId,
eventName: 'database_transaction_event',
};
emitEvent('database_transaction_event', event);
} catch (e) {
const event = {
body: {
committed: false,
type: 'error',
error: getDatabaseWebError(e),
},
appName,
id: transactionId,
eventName: 'database_transaction_event',
};
emitEvent('database_transaction_event', event);
}
});
},
/**
* Commits the transaction with the specified updates.
* @param {string} appName - The app name.
* @param {string} dbURL - The database URL.
* @param {string} transactionId - The transaction ID.
* @param {object} updates - The updates.
* @returns {Promise<void>}
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async transactionTryCommit(appName, dbURL, transactionId, updates) {
// We don't need to implement this as for 'Other' platforms
// we pass the users transaction function to the Firebase JS SDK directly.
throw new Error('Not implemented');
},
};