@@ -6,8 +6,8 @@ import { run } from '@ember/runloop';
66import { firestore , database } from 'firebase/app' ;
77
88// TODO don't hardcode these, but having trouble otherwise
9- import { normalize as firestoreNormalize } from '../serializers /firestore' ;
10- import { normalize as databaseNormalize } from '../serializers /realtime-database' ;
9+ import { rootCollection as firestoreRootCollection } from '../adapters /firestore' ;
10+ import { rootCollection as realtimeDatabaseRootCollection } from '../adapters /realtime-database' ;
1111
1212const getService = ( object :Object ) => getOwner ( object ) . lookup ( 'service:realtime-listener' ) as RealtimeListenerService ;
1313const isFastboot = ( object :Object ) => {
@@ -56,6 +56,24 @@ function isFirestoreDocumentRefernce(arg: any): arg is firestore.DocumentReferen
5656 return arg . onSnapshot !== undefined ;
5757}
5858
59+ type SubscribeArgs = {
60+ model : any ;
61+ store : DS . Store ;
62+ modelName : never ;
63+ modelClass : never ;
64+ uniqueIdentifier : any ;
65+ serializer : any ;
66+ adapter : never ;
67+ }
68+
69+ type FirestoreQueryArgs = {
70+ query : firestore . Query | firestore . CollectionReference
71+ } & SubscribeArgs
72+
73+ type RealtimeDatabaseQueryArgs = {
74+ ref : database . Reference
75+ } & SubscribeArgs
76+
5977export default class RealtimeListenerService extends Service . extend ( {
6078
6179 routeSubscriptions : { } as { [ key :string ] : { [ key :string ] : ( ) => void } }
@@ -69,108 +87,22 @@ export default class RealtimeListenerService extends Service.extend({
6987 const query = model . get ( 'meta.query' ) as firestore . Query | database . Reference | undefined ;
7088 const ref = model . get ( '_internalModel._recordData._data._ref' ) as firestore . DocumentReference | database . Reference | undefined ;
7189 const uniqueIdentifier = model . toString ( ) ;
90+ const serializer = store . serializerFor ( modelName ) as any ; // TODO type
91+ const adapter = store . adapterFor ( modelName ) ;
92+ const args = { model, store, modelName, modelClass, uniqueIdentifier, serializer, adapter } ;
7293 if ( query ) {
7394 if ( isFirestoreQuery ( query ) ) {
74- const unsubscribe = query . onSnapshot ( snapshot => {
75- snapshot . docChanges ( ) . forEach ( change => run ( ( ) => {
76- const normalizedData = firestoreNormalize ( store , modelClass , change . doc ) ;
77- switch ( change . type ) {
78- case 'added' : {
79- const current = model . content . objectAt ( change . newIndex ) ;
80- if ( current == null || current . id !== change . doc . id ) {
81- const doc = store . push ( normalizedData ) as any ;
82- model . content . insertAt ( change . newIndex , doc . _internalModel ) ;
83- }
84- break ;
85- }
86- case 'modified' : {
87- const current = model . content . objectAt ( change . oldIndex ) ;
88- if ( current == null || current . id == change . doc . id ) {
89- if ( change . newIndex !== change . oldIndex ) {
90- model . content . removeAt ( change . oldIndex ) ;
91- model . content . insertAt ( change . newIndex , current )
92- }
93- }
94- store . push ( normalizedData ) ;
95- break ;
96- }
97- case 'removed' : {
98- const current = model . content . objectAt ( change . oldIndex ) ;
99- if ( current && current . id == change . doc . id ) {
100- model . content . removeAt ( change . oldIndex ) ;
101- }
102- break ;
103- }
104- }
105- } ) )
106- } ) ;
95+ const unsubscribe = runFirestoreCollectionListener ( { query, ...args } ) ;
10796 setRouteSubscription ( this , route , uniqueIdentifier , unsubscribe ) ;
10897 } else {
109- const onChildAdded = query . on ( 'child_added' , ( snapshot , priorKey ) => {
110- run ( ( ) => {
111- if ( snapshot ) {
112- const normalizedData = databaseNormalize ( store , modelClass , snapshot ) ;
113- const doc = store . push ( normalizedData ) as any ;
114- const existing = model . content . find ( ( record :any ) => record . id === doc . id ) ;
115- if ( existing ) { model . content . removeObject ( existing ) ; }
116- let insertIndex = 0 ;
117- if ( priorKey ) {
118- const record = model . content . find ( ( record :any ) => record . id === priorKey ) ;
119- insertIndex = model . content . indexOf ( record ) + 1 ;
120- }
121- const current = model . content . objectAt ( insertIndex ) ;
122- if ( current == null || current . id !== doc . id ) {
123- model . content . insertAt ( insertIndex , doc . _internalModel ) ;
124- }
125- }
126- } ) ;
127- } ) ;
128- const onChildRemoved = query . on ( 'child_removed' , snapshot => {
129- run ( ( ) => {
130- if ( snapshot ) {
131- const record = model . content . find ( ( record :any ) => record . id === snapshot . key )
132- if ( record ) { model . content . removeObject ( record ) ; }
133- }
134- } ) ;
135- } ) ;
136- const onChildChanged = query . on ( 'child_changed' , snapshot => {
137- run ( ( ) => {
138- if ( snapshot ) {
139- const normalizedData = databaseNormalize ( store , modelClass , snapshot ) ;
140- store . push ( normalizedData ) ;
141- }
142- } ) ;
143- } ) ;
144- const onChildMoved = query . on ( 'child_moved' , ( snapshot , priorKey ) => {
145- run ( ( ) => {
146- if ( snapshot ) {
147- const normalizedData = databaseNormalize ( store , modelClass , snapshot ) ;
148- const doc = store . push ( normalizedData ) as any ;
149- const existing = model . content . find ( ( record :any ) => record . id === doc . id ) ;
150- if ( existing ) { model . content . removeObject ( existing ) ; }
151- if ( priorKey ) {
152- const record = model . content . find ( ( record :any ) => record . id === priorKey ) ;
153- const index = model . content . indexOf ( record ) ;
154- model . content . insertAt ( index + 1 , doc . _internalModel ) ;
155- } else {
156- model . content . insertAt ( 0 , doc . _internalModel ) ;
157- }
158- }
159- } ) ;
160- } ) ;
161- const unsubscribe = ( ) => {
162- query . off ( 'child_added' , onChildAdded ) ;
163- query . off ( 'child_removed' , onChildRemoved ) ;
164- query . off ( 'child_changed' , onChildChanged ) ;
165- query . off ( 'child_moved' , onChildMoved ) ;
166- }
98+ const unsubscribe = runRealtimeDatabaseListListener ( { ref : query , ...args } ) ;
16799 setRouteSubscription ( this , route , uniqueIdentifier , unsubscribe ) ;
168100 }
169101 } else if ( ref ) {
170102 if ( isFirestoreDocumentRefernce ( ref ) ) {
171103 const unsubscribe = ref . onSnapshot ( doc => {
172104 run ( ( ) => {
173- const normalizedData = firestoreNormalize ( store , modelClass , doc ) ;
105+ const normalizedData = serializer . normalizeSingleResponse ( store , modelClass , doc ) ;
174106 store . push ( normalizedData ) ;
175107 } ) ;
176108 } ) ;
@@ -180,7 +112,7 @@ export default class RealtimeListenerService extends Service.extend({
180112 run ( ( ) => {
181113 if ( snapshot ) {
182114 if ( snapshot . exists ( ) ) {
183- const normalizedData = databaseNormalize ( store , modelClass , snapshot ) ;
115+ const normalizedData = serializer . normalizeSingleResponse ( store , modelClass , snapshot ) ;
184116 store . push ( normalizedData ) ;
185117 } else {
186118 const record = store . findRecord ( modelName , snapshot . key ! )
@@ -192,6 +124,19 @@ export default class RealtimeListenerService extends Service.extend({
192124 const unsubscribe = ( ) => ref . off ( 'value' , listener ) ;
193125 setRouteSubscription ( this , route , uniqueIdentifier , unsubscribe ) ;
194126 }
127+ } else {
128+ // this might be a findAll, findAll strips metadata :(
129+ if ( serializer . constructor . name == 'FirestoreSerializer' ) {
130+ firestoreRootCollection ( adapter , modelName ) . then ( query => {
131+ const unsubscribe = runFirestoreCollectionListener ( { query, ...args } ) ;
132+ setRouteSubscription ( this , route , uniqueIdentifier , unsubscribe ) ;
133+ } ) ;
134+ } else if ( serializer . constructor . name == 'RealtimeDatabaseSerializer' ) {
135+ realtimeDatabaseRootCollection ( adapter , modelName ) . then ( ref => {
136+ const unsubscribe = runRealtimeDatabaseListListener ( { ref, ...args } ) ;
137+ setRouteSubscription ( this , route , uniqueIdentifier , unsubscribe ) ;
138+ } ) ;
139+ }
195140 }
196141 }
197142
@@ -201,6 +146,105 @@ export default class RealtimeListenerService extends Service.extend({
201146
202147}
203148
149+ const runFirestoreCollectionListener = ( { query, model, store, serializer, modelClass} : FirestoreQueryArgs ) => {
150+ const unsubscribe = query . onSnapshot ( snapshot => {
151+ snapshot . docChanges ( ) . forEach ( change => run ( ( ) => {
152+ const normalizedData = serializer . normalizeSingleResponse ( store , modelClass , change . doc ) ;
153+ switch ( change . type ) {
154+ case 'added' : {
155+ const current = model . content . objectAt ( change . newIndex ) ;
156+ if ( current == null || current . id !== change . doc . id ) {
157+ const doc = store . push ( normalizedData ) as any ;
158+ model . content . insertAt ( change . newIndex , doc . _internalModel ) ;
159+ }
160+ break ;
161+ }
162+ case 'modified' : {
163+ const current = model . content . objectAt ( change . oldIndex ) ;
164+ if ( current == null || current . id == change . doc . id ) {
165+ if ( change . newIndex !== change . oldIndex ) {
166+ model . content . removeAt ( change . oldIndex ) ;
167+ model . content . insertAt ( change . newIndex , current )
168+ }
169+ }
170+ store . push ( normalizedData ) ;
171+ break ;
172+ }
173+ case 'removed' : {
174+ const current = model . content . objectAt ( change . oldIndex ) ;
175+ if ( current && current . id == change . doc . id ) {
176+ model . content . removeAt ( change . oldIndex ) ;
177+ }
178+ break ;
179+ }
180+ }
181+ } ) )
182+ } ) ;
183+ return unsubscribe ;
184+ }
185+
186+ const runRealtimeDatabaseListListener = ( { model, ref, serializer, store, modelClass} : RealtimeDatabaseQueryArgs ) => {
187+ const onChildAdded = ref . on ( 'child_added' , ( snapshot , priorKey ) => {
188+ run ( ( ) => {
189+ if ( snapshot ) {
190+ const normalizedData = serializer . normalizeSingleResponse ( store , modelClass , snapshot ) ;
191+ const doc = store . push ( normalizedData ) as any ;
192+ const existing = model . content . find ( ( record :any ) => record . id === doc . id ) ;
193+ if ( existing ) { model . content . removeObject ( existing ) ; }
194+ let insertIndex = 0 ;
195+ if ( priorKey ) {
196+ const record = model . content . find ( ( record :any ) => record . id === priorKey ) ;
197+ insertIndex = model . content . indexOf ( record ) + 1 ;
198+ }
199+ const current = model . content . objectAt ( insertIndex ) ;
200+ if ( current == null || current . id !== doc . id ) {
201+ model . content . insertAt ( insertIndex , doc . _internalModel ) ;
202+ }
203+ }
204+ } ) ;
205+ } ) ;
206+ const onChildRemoved = ref . on ( 'child_removed' , snapshot => {
207+ run ( ( ) => {
208+ if ( snapshot ) {
209+ const record = model . content . find ( ( record :any ) => record . id === snapshot . key )
210+ if ( record ) { model . content . removeObject ( record ) ; }
211+ }
212+ } ) ;
213+ } ) ;
214+ const onChildChanged = ref . on ( 'child_changed' , snapshot => {
215+ run ( ( ) => {
216+ if ( snapshot ) {
217+ const normalizedData = serializer . normalizeSingleResponse ( store , modelClass , snapshot ) ;
218+ store . push ( normalizedData ) ;
219+ }
220+ } ) ;
221+ } ) ;
222+ const onChildMoved = ref . on ( 'child_moved' , ( snapshot , priorKey ) => {
223+ run ( ( ) => {
224+ if ( snapshot ) {
225+ const normalizedData = serializer . normalizeSingleResponse ( store , modelClass , snapshot ) ;
226+ const doc = store . push ( normalizedData ) as any ;
227+ const existing = model . content . find ( ( record :any ) => record . id === doc . id ) ;
228+ if ( existing ) { model . content . removeObject ( existing ) ; }
229+ if ( priorKey ) {
230+ const record = model . content . find ( ( record :any ) => record . id === priorKey ) ;
231+ const index = model . content . indexOf ( record ) ;
232+ model . content . insertAt ( index + 1 , doc . _internalModel ) ;
233+ } else {
234+ model . content . insertAt ( 0 , doc . _internalModel ) ;
235+ }
236+ }
237+ } ) ;
238+ } ) ;
239+ const unsubscribe = ( ) => {
240+ ref . off ( 'child_added' , onChildAdded ) ;
241+ ref . off ( 'child_removed' , onChildRemoved ) ;
242+ ref . off ( 'child_changed' , onChildChanged ) ;
243+ ref . off ( 'child_moved' , onChildMoved ) ;
244+ }
245+ return unsubscribe ;
246+ }
247+
204248declare module '@ember/service' {
205249 interface Registry {
206250 "realtime-listener" : RealtimeListenerService ;
0 commit comments