diff --git a/docs/reference/dev/firestore.md b/docs/reference/dev/firestore.md index a629d271..210fd41f 100644 --- a/docs/reference/dev/firestore.md +++ b/docs/reference/dev/firestore.md @@ -48,6 +48,10 @@ methods: required: false type: A [Buffer](https://redux-saga.js.org/docs/api/#buffer) object description: Defaults to `buffers.none()`. Optional Buffer object to buffer messages on this channel. If not provided, messages will not buffered on this channel. See [redux-saga documentation](https://redux-saga.js.org/docs/api/#buffers) for more information for what options are available. + - name: snapshotListenOptions + required: false + type: A [SnapshotListenOptions](https://firebase.google.com/docs/reference/js/firebase.firestore.SnapshotListenOptions) object + description: Options to control the circumstances when the channel will emit events. output: A redux-saga [Channel](https://redux-saga.github.io/redux-saga/docs/advanced/Channels.html) which emits every time the data at `pathOrRef` in firestore changes. example: | ```js @@ -163,7 +167,7 @@ methods: - name: options required: true type: Object - description: "An object to configure how the collection should be synchronised. It must contain at least the `successActionCreator` which must take either a [DocumentSnapshot](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot) or a [QuerySnapshot](https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot) as argument. The other possible options are `failureActionCreator` which is called on channel errors and `transform` which is an optional transformer function to be applied to the value before it's passed to the action creator. Default to the identity function (`x => x`)." + description: "An object to configure how the collection should be synchronised. It must contain at least the `successActionCreator` which must take either a [DocumentSnapshot](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot) or a [QuerySnapshot](https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot) as argument. The other possible options are `failureActionCreator` which is called on channel errors, `transform` which is an optional transformer function to be applied to the value before it's passed to the action creator(default to the identity function (`x => x`).) and `snapshotListenOptions` which is an [SnapshotListenOptions](https://firebase.google.com/docs/reference/js/firebase.firestore.SnapshotListenOptions) object to opt into updates when only metadata changes." output: example: | ```js @@ -190,7 +194,7 @@ methods: - name: options required: true type: Object - description: "An object to configure how the document should be synchronised. It must contain at least the `successActionCreator` which must take either a [DocumentSnapshot](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot) or a [QuerySnapshot](https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot) as argument. The other possible options are `failureActionCreator` which is called on channel errors and `transform` which is an optional transformer function to be applied to the value before it's passed to the action creator. Default to the identity function (`x => x`)." + description: "An object to configure how the document should be synchronised. It must contain at least the `successActionCreator` which must take either a [DocumentSnapshot](https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentSnapshot) or a [QuerySnapshot](https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot) as argument. The other possible options are `failureActionCreator` which is called on channel errors, `transform` which is an optional transformer function to be applied to the value before it's passed to the action creator(default to the identity function (`x => x`).) and `snapshotListenOptions` which is an [SnapshotListenOptions](https://firebase.google.com/docs/reference/js/firebase.firestore.SnapshotListenOptions) object to opt into updates when only metadata changes." output: example: | ```js diff --git a/src/firestore.js b/src/firestore.js index 190ccce8..7d382673 100644 --- a/src/firestore.js +++ b/src/firestore.js @@ -33,14 +33,21 @@ function* addDocument(collectionRef, data) { return yield call([collection, collection.add], data) } -function channel(pathOrRef, type = 'collection', buffer = buffers.none()) { +function channel( + pathOrRef, + type = 'collection', + buffer = buffers.none(), + snapshotListenOptions, +) { const ref = type === 'collection' ? getCollectionRef(this, pathOrRef) : getDocumentRef(this, pathOrRef) const channel = eventChannel(emit => { - const unsubscribe = ref.onSnapshot(emit) + const unsubscribe = snapshotListenOptions + ? ref.onSnapshot(snapshotListenOptions, emit) + : ref.onSnapshot(emit) // Returns unsubscribe function return unsubscribe @@ -76,12 +83,24 @@ function* updateDocument(documentRef, ...args) { } function* syncCollection(pathOrRef, options) { - const channel = yield call(this.firestore.channel, pathOrRef, 'collection') + const channel = yield call( + this.firestore.channel, + pathOrRef, + 'collection', + undefined, + options.snapshotListenOptions, + ) yield fork(syncChannel, channel, options) } function* syncDocument(pathOrRef, options) { - const channel = yield call(this.firestore.channel, pathOrRef, 'document') + const channel = yield call( + this.firestore.channel, + pathOrRef, + 'document', + undefined, + options.snapshotListenOptions, + ) yield fork(syncChannel, channel, options) } diff --git a/src/firestore.test.js b/src/firestore.test.js index 103785f2..160a05b4 100644 --- a/src/firestore.test.js +++ b/src/firestore.test.js @@ -125,6 +125,24 @@ describe('firestore', () => { expect(document.onSnapshot.mock.calls.length).toBe(1) }) + + it('respects snapshot listen options for collections', () => { + const type = 'collection' + const options = { includeMetadataChanges: true } + firestoreModule.channel.call(context, 'foo', type, undefined, options) + + expect(collection.onSnapshot.mock.calls.length).toBe(1) + expect(collection.onSnapshot.mock.calls[0][0]).toBe(options) + }) + + it('respects snapshot listen options for documents', () => { + const type = 'document' + const options = { includeMetadataChanges: true } + firestoreModule.channel.call(context, 'foo', type, undefined, options) + + expect(document.onSnapshot.mock.calls.length).toBe(1) + expect(document.onSnapshot.mock.calls[0][0]).toBe(options) + }) }) describe('deleteDocument(documentRef)', () => { @@ -252,7 +270,7 @@ describe('firestore', () => { const iterator = firestoreModule.syncCollection.call(context, path, options) expect(iterator.next().value).toEqual( - call(context.firestore.channel, path, 'collection'), + call(context.firestore.channel, path, 'collection', undefined, undefined), ) const chan = 'eeeerqd' @@ -272,7 +290,34 @@ describe('firestore', () => { const iterator = firestoreModule.syncCollection.call(context, collection, options) expect(iterator.next().value).toEqual( - call(context.firestore.channel, collection, 'collection'), + call(context.firestore.channel, collection, 'collection', undefined, undefined), + ) + + const chan = 'eeeerqd' + expect(iterator.next(chan)).toEqual({ + done: false, + value: fork(syncChannel, chan, options), + }) + + expect(iterator.next()).toEqual({ + done: true, + value: undefined, + }) + }) + + it('respects snapshot listen options', () => { + const path = 'skddksl' + const options = { snapshotListenOptions: { includeMetadataChanges: true } } + const iterator = firestoreModule.syncCollection.call(context, path, options) + + expect(iterator.next().value).toEqual( + call( + context.firestore.channel, + path, + 'collection', + undefined, + options.snapshotListenOptions, + ), ) const chan = 'eeeerqd' @@ -295,7 +340,34 @@ describe('firestore', () => { const iterator = firestoreModule.syncDocument.call(context, path, options) expect(iterator.next().value).toEqual( - call(context.firestore.channel, path, 'document'), + call(context.firestore.channel, path, 'document', undefined, undefined), + ) + + const chan = 'eeeerqd' + expect(iterator.next(chan)).toEqual({ + done: false, + value: fork(syncChannel, chan, options), + }) + + expect(iterator.next()).toEqual({ + done: true, + value: undefined, + }) + }) + + it('respects snapshot listen options', () => { + const path = 'skddksl' + const options = { snapshotListenOptions: { includeMetadataChanges: true } } + const iterator = firestoreModule.syncDocument.call(context, path, options) + + expect(iterator.next().value).toEqual( + call( + context.firestore.channel, + path, + 'document', + undefined, + options.snapshotListenOptions, + ), ) const chan = 'eeeerqd'