@@ -32,7 +32,6 @@ import { FirebaseApp } from './public-types';
3232import * as firebaseUtil from '@firebase/util' ;
3333import { SinonStub , stub , useFakeTimers } from 'sinon' ;
3434import * as indexedDb from './indexeddb' ;
35- import { base64Encode , isIndexedDBAvailable } from '@firebase/util' ;
3635
3736declare module '@firebase/component' {
3837 interface NameServiceMapping {
@@ -99,38 +98,37 @@ describe('HeartbeatServiceImpl', () => {
9998 */
10099 it ( `triggerHeartbeat() stores a heartbeat` , async ( ) => {
101100 await heartbeatService . triggerHeartbeat ( ) ;
102- expect ( heartbeatService . _heartbeatsCache ?. length ) . to . equal ( 1 ) ;
103- const heartbeat1 = heartbeatService . _heartbeatsCache ?. [ 0 ] ;
104- expect ( heartbeat1 ?. userAgent ) . to . equal ( USER_AGENT_STRING_1 ) ;
101+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats . length ) . to . equal ( 1 ) ;
102+ const heartbeat1 = heartbeatService . _heartbeatsCache ?. heartbeats [ 0 ] ;
103+ expect ( heartbeat1 ?. agent ) . to . equal ( USER_AGENT_STRING_1 ) ;
105104 expect ( heartbeat1 ?. date ) . to . equal ( '1970-01-01' ) ;
106- expect ( writeStub ) . to . be . calledWith ( [ heartbeat1 ] ) ;
105+ expect ( writeStub ) . to . be . calledWith ( { heartbeats : [ heartbeat1 ] } ) ;
107106 } ) ;
108107 it ( `triggerHeartbeat() doesn't store another heartbeat on the same day` , async ( ) => {
109- expect ( heartbeatService . _heartbeatsCache ?. length ) . to . equal ( 1 ) ;
108+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats . length ) . to . equal ( 1 ) ;
110109 await heartbeatService . triggerHeartbeat ( ) ;
111- expect ( heartbeatService . _heartbeatsCache ?. length ) . to . equal ( 1 ) ;
110+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats . length ) . to . equal ( 1 ) ;
112111 } ) ;
113112 it ( `triggerHeartbeat() does store another heartbeat on a different day` , async ( ) => {
114- expect ( heartbeatService . _heartbeatsCache ?. length ) . to . equal ( 1 ) ;
113+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats . length ) . to . equal ( 1 ) ;
115114 clock . tick ( 24 * 60 * 60 * 1000 ) ;
116115 await heartbeatService . triggerHeartbeat ( ) ;
117- expect ( heartbeatService . _heartbeatsCache ?. length ) . to . equal ( 2 ) ;
118- expect ( heartbeatService . _heartbeatsCache ?. [ 1 ] . date ) . to . equal (
116+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats . length ) . to . equal ( 2 ) ;
117+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats [ 1 ] . date ) . to . equal (
119118 '1970-01-02'
120119 ) ;
121120 } ) ;
122121 it ( `triggerHeartbeat() stores another entry for a different user agent` , async ( ) => {
123122 userAgentString = USER_AGENT_STRING_2 ;
124- expect ( heartbeatService . _heartbeatsCache ?. length ) . to . equal ( 2 ) ;
123+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats . length ) . to . equal ( 2 ) ;
125124 clock . tick ( 2 * 24 * 60 * 60 * 1000 ) ;
126125 await heartbeatService . triggerHeartbeat ( ) ;
127- expect ( heartbeatService . _heartbeatsCache ?. length ) . to . equal ( 3 ) ;
128- expect ( heartbeatService . _heartbeatsCache ?. [ 2 ] . date ) . to . equal (
126+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats . length ) . to . equal ( 3 ) ;
127+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats [ 2 ] . date ) . to . equal (
129128 '1970-01-03'
130129 ) ;
131130 } ) ;
132131 it ( 'getHeartbeatHeaders() gets stored heartbeats and clears heartbeats' , async ( ) => {
133- const deleteStub = stub ( heartbeatService . _storage , 'deleteAll' ) ;
134132 const heartbeatHeaders = firebaseUtil . base64Decode (
135133 await heartbeatService . getHeartbeatsHeader ( )
136134 ) ;
@@ -140,10 +138,13 @@ describe('HeartbeatServiceImpl', () => {
140138 expect ( heartbeatHeaders ) . to . include ( '1970-01-02' ) ;
141139 expect ( heartbeatHeaders ) . to . include ( '1970-01-03' ) ;
142140 expect ( heartbeatHeaders ) . to . include ( `"version":2` ) ;
143- expect ( heartbeatService . _heartbeatsCache ) . to . equal ( null ) ;
141+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats ) . to . be . empty ;
142+ expect ( writeStub ) . to . be . calledWith ( {
143+ lastSentHeartbeatDate : '1970-01-01' ,
144+ heartbeats : [ ]
145+ } ) ;
144146 const emptyHeaders = await heartbeatService . getHeartbeatsHeader ( ) ;
145147 expect ( emptyHeaders ) . to . equal ( '' ) ;
146- expect ( deleteStub ) . to . be . called ;
147148 } ) ;
148149 } ) ;
149150 describe ( 'If IndexedDB has entries' , ( ) => {
@@ -154,11 +155,11 @@ describe('HeartbeatServiceImpl', () => {
154155 const mockIndexedDBHeartbeats = [
155156 // Chosen so one will exceed 30 day limit and one will not.
156157 {
157- userAgent : 'old-user-agent' ,
158+ agent : 'old-user-agent' ,
158159 date : '1969-12-01'
159160 } ,
160161 {
161- userAgent : 'old-user-agent' ,
162+ agent : 'old-user-agent' ,
162163 date : '1969-12-31'
163164 }
164165 ] ;
@@ -197,61 +198,149 @@ describe('HeartbeatServiceImpl', () => {
197198 */
198199 it ( `new heartbeat service reads from indexedDB cache` , async ( ) => {
199200 const promiseResult = await heartbeatService . _heartbeatsCachePromise ;
200- if ( isIndexedDBAvailable ( ) ) {
201- expect ( promiseResult ) . to . deep . equal ( mockIndexedDBHeartbeats ) ;
202- expect ( heartbeatService . _heartbeatsCache ) . to . deep . equal (
203- mockIndexedDBHeartbeats
204- ) ;
201+ if ( firebaseUtil . isIndexedDBAvailable ( ) ) {
202+ expect ( promiseResult ) . to . deep . equal ( {
203+ heartbeats : mockIndexedDBHeartbeats
204+ } ) ;
205+ expect ( heartbeatService . _heartbeatsCache ) . to . deep . equal ( {
206+ heartbeats : mockIndexedDBHeartbeats
207+ } ) ;
205208 } else {
206209 // In Node or other no-indexed-db environments it will fail the
207210 // `canUseIndexedDb` check and return an empty array.
208- expect ( promiseResult ) . to . deep . equal ( [ ] ) ;
209- expect ( heartbeatService . _heartbeatsCache ) . to . deep . equal ( [ ] ) ;
211+ expect ( promiseResult ) . to . deep . equal ( {
212+ heartbeats : [ ]
213+ } ) ;
214+ expect ( heartbeatService . _heartbeatsCache ) . to . deep . equal ( {
215+ heartbeats : [ ]
216+ } ) ;
210217 }
211218 } ) ;
212219 it ( `triggerHeartbeat() writes new heartbeats and retains old ones newer than 30 days` , async ( ) => {
213220 userAgentString = USER_AGENT_STRING_2 ;
214221 clock . tick ( 3 * 24 * 60 * 60 * 1000 ) ;
215222 await heartbeatService . triggerHeartbeat ( ) ;
216- if ( isIndexedDBAvailable ( ) ) {
217- expect ( writeStub ) . to . be . calledWith ( [
218- // The first entry exceeds the 30 day retention limit.
219- mockIndexedDBHeartbeats [ 1 ] ,
220- { userAgent : USER_AGENT_STRING_2 , date : '1970-01-04' }
221- ] ) ;
223+ if ( firebaseUtil . isIndexedDBAvailable ( ) ) {
224+ expect ( writeStub ) . to . be . calledWith ( {
225+ heartbeats : [
226+ // The first entry exceeds the 30 day retention limit.
227+ mockIndexedDBHeartbeats [ 1 ] ,
228+ { agent : USER_AGENT_STRING_2 , date : '1970-01-04' }
229+ ]
230+ } ) ;
222231 } else {
223- expect ( writeStub ) . to . be . calledWith ( [
224- { userAgent : USER_AGENT_STRING_2 , date : '1970-01-04' }
225- ] ) ;
232+ expect ( writeStub ) . to . be . calledWith ( {
233+ heartbeats : [ { agent : USER_AGENT_STRING_2 , date : '1970-01-04' } ]
234+ } ) ;
226235 }
227236 } ) ;
228237 it ( 'getHeartbeatHeaders() gets stored heartbeats and clears heartbeats' , async ( ) => {
229- const deleteStub = stub ( heartbeatService . _storage , 'deleteAll' ) ;
230238 const heartbeatHeaders = firebaseUtil . base64Decode (
231239 await heartbeatService . getHeartbeatsHeader ( )
232240 ) ;
233- if ( isIndexedDBAvailable ( ) ) {
241+ if ( firebaseUtil . isIndexedDBAvailable ( ) ) {
234242 expect ( heartbeatHeaders ) . to . include ( 'old-user-agent' ) ;
235243 expect ( heartbeatHeaders ) . to . include ( '1969-12-31' ) ;
236244 }
237245 expect ( heartbeatHeaders ) . to . include ( USER_AGENT_STRING_2 ) ;
238246 expect ( heartbeatHeaders ) . to . include ( '1970-01-04' ) ;
239247 expect ( heartbeatHeaders ) . to . include ( `"version":2` ) ;
240- expect ( heartbeatService . _heartbeatsCache ) . to . equal ( null ) ;
248+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats ) . to . be . empty ;
249+ expect ( writeStub ) . to . be . calledWith ( {
250+ lastSentHeartbeatDate : '1970-01-01' ,
251+ heartbeats : [ ]
252+ } ) ;
241253 const emptyHeaders = await heartbeatService . getHeartbeatsHeader ( ) ;
242254 expect ( emptyHeaders ) . to . equal ( '' ) ;
243- expect ( deleteStub ) . to . be . called ;
255+ } ) ;
256+ } ) ;
257+
258+ describe ( 'If IndexedDB records that a header was sent today' , ( ) => {
259+ let heartbeatService : HeartbeatServiceImpl ;
260+ let writeStub : SinonStub ;
261+ const userAgentString = USER_AGENT_STRING_1 ;
262+ const mockIndexedDBHeartbeats = [
263+ // Chosen so one will exceed 30 day limit and one will not.
264+ {
265+ agent : 'old-user-agent' ,
266+ date : '1969-12-01'
267+ } ,
268+ {
269+ agent : 'old-user-agent' ,
270+ date : '1969-12-31'
271+ }
272+ ] ;
273+ before ( ( ) => {
274+ const container = new ComponentContainer ( 'heartbeatTestContainer' ) ;
275+ container . addComponent (
276+ new Component (
277+ 'app' ,
278+ ( ) =>
279+ ( {
280+ options : { appId : 'an-app-id' } ,
281+ name : 'an-app-name'
282+ } as FirebaseApp ) ,
283+ ComponentType . VERSION
284+ )
285+ ) ;
286+ container . addComponent (
287+ new Component (
288+ 'platform-logger' ,
289+ ( ) => ( { getPlatformInfoString : ( ) => userAgentString } ) ,
290+ ComponentType . VERSION
291+ )
292+ ) ;
293+ stub ( indexedDb , 'readHeartbeatsFromIndexedDB' ) . resolves ( {
294+ lastSentHeartbeatDate : '1970-01-01' ,
295+ heartbeats : [ ...mockIndexedDBHeartbeats ]
296+ } ) ;
297+ heartbeatService = new HeartbeatServiceImpl ( container ) ;
298+ } ) ;
299+ beforeEach ( ( ) => {
300+ useFakeTimers ( ) ;
301+ writeStub = stub ( heartbeatService . _storage , 'overwrite' ) ;
302+ } ) ;
303+ it ( `new heartbeat service reads from indexedDB cache` , async ( ) => {
304+ const promiseResult = await heartbeatService . _heartbeatsCachePromise ;
305+ if ( firebaseUtil . isIndexedDBAvailable ( ) ) {
306+ expect ( promiseResult ) . to . deep . equal ( {
307+ lastSentHeartbeatDate : '1970-01-01' ,
308+ heartbeats : mockIndexedDBHeartbeats
309+ } ) ;
310+ expect ( heartbeatService . _heartbeatsCache ) . to . deep . equal ( {
311+ lastSentHeartbeatDate : '1970-01-01' ,
312+ heartbeats : mockIndexedDBHeartbeats
313+ } ) ;
314+ } else {
315+ // In Node or other no-indexed-db environments it will fail the
316+ // `canUseIndexedDb` check and return an empty array.
317+ expect ( promiseResult ) . to . deep . equal ( {
318+ heartbeats : [ ]
319+ } ) ;
320+ expect ( heartbeatService . _heartbeatsCache ) . to . deep . equal ( {
321+ heartbeats : [ ]
322+ } ) ;
323+ }
324+ } ) ;
325+ it ( `triggerHeartbeat() will skip storing new data` , async ( ) => {
326+ await heartbeatService . triggerHeartbeat ( ) ;
327+ expect ( writeStub ) . to . not . be . called ;
328+ if ( firebaseUtil . isIndexedDBAvailable ( ) ) {
329+ expect ( heartbeatService . _heartbeatsCache ?. heartbeats ) . to . deep . equal (
330+ mockIndexedDBHeartbeats
331+ ) ;
332+ }
244333 } ) ;
245334 } ) ;
246335
247336 describe ( 'countBytes()' , ( ) => {
248337 it ( 'counts how many bytes there will be in a stringified, encoded header' , ( ) => {
249338 const heartbeats = [
250- { userAgent : generateUserAgentString ( 1 ) , dates : generateDates ( 1 ) } ,
251- { userAgent : generateUserAgentString ( 3 ) , dates : generateDates ( 2 ) }
339+ { agent : generateUserAgentString ( 1 ) , dates : generateDates ( 1 ) } ,
340+ { agent : generateUserAgentString ( 3 ) , dates : generateDates ( 2 ) }
252341 ] ;
253342 let size : number = 0 ;
254- const headerString = base64Encode (
343+ const headerString = firebaseUtil . base64urlEncodeWithoutPadding (
255344 JSON . stringify ( { version : 2 , heartbeats } )
256345 ) ;
257346 // Use independent methods to validate our byte count method matches.
@@ -272,7 +361,7 @@ describe('HeartbeatServiceImpl', () => {
272361 describe ( '_extractHeartbeatsForHeader()' , ( ) => {
273362 it ( 'returns empty heartbeatsToKeep if it cannot get under maxSize' , ( ) => {
274363 const heartbeats = [
275- { userAgent : generateUserAgentString ( 1 ) , date : '2022-01-01' }
364+ { agent : generateUserAgentString ( 1 ) , date : '2022-01-01' }
276365 ] ;
277366 const { unsentEntries, heartbeatsToSend } = extractHeartbeatsForHeader (
278367 heartbeats ,
@@ -283,11 +372,11 @@ describe('HeartbeatServiceImpl', () => {
283372 } ) ;
284373 it ( 'splits heartbeats array' , ( ) => {
285374 const heartbeats = [
286- { userAgent : generateUserAgentString ( 20 ) , date : '2022-01-01' } ,
287- { userAgent : generateUserAgentString ( 4 ) , date : '2022-01-02' }
375+ { agent : generateUserAgentString ( 20 ) , date : '2022-01-01' } ,
376+ { agent : generateUserAgentString ( 4 ) , date : '2022-01-02' }
288377 ] ;
289378 const sizeWithHeartbeat0Only = countBytes ( [
290- { userAgent : heartbeats [ 0 ] . userAgent , dates : [ heartbeats [ 0 ] . date ] }
379+ { agent : heartbeats [ 0 ] . agent , dates : [ heartbeats [ 0 ] . date ] }
291380 ] ) ;
292381 const { unsentEntries, heartbeatsToSend } = extractHeartbeatsForHeader (
293382 heartbeats ,
@@ -299,12 +388,12 @@ describe('HeartbeatServiceImpl', () => {
299388 it ( 'splits the first heartbeat if needed' , ( ) => {
300389 const uaString = generateUserAgentString ( 20 ) ;
301390 const heartbeats = [
302- { userAgent : uaString , date : '2022-01-01' } ,
303- { userAgent : uaString , date : '2022-01-02' } ,
304- { userAgent : uaString , date : '2022-01-03' }
391+ { agent : uaString , date : '2022-01-01' } ,
392+ { agent : uaString , date : '2022-01-02' } ,
393+ { agent : uaString , date : '2022-01-03' }
305394 ] ;
306395 const sizeWithHeartbeat0Only = countBytes ( [
307- { userAgent : heartbeats [ 0 ] . userAgent , dates : [ heartbeats [ 0 ] . date ] }
396+ { agent : heartbeats [ 0 ] . agent , dates : [ heartbeats [ 0 ] . date ] }
308397 ] ) ;
309398 const { unsentEntries, heartbeatsToSend } = extractHeartbeatsForHeader (
310399 heartbeats ,
0 commit comments