From b4c7b85ed71e4c7ae4745185232df2f585702c63 Mon Sep 17 00:00:00 2001 From: Ken Sheedlo Date: Thu, 10 Jan 2019 10:54:30 -0800 Subject: [PATCH] Use visibility API instead of beforeunload https://github.com/fusionjs/fusion-plugin-universal-events/pull/158 --- README.md | 2 +- src/__tests__/test.browser.js | 11 ++++++++--- src/browser.js | 8 +++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 15550857..787784b7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The `fusion-plugin-universal-events` is commonly required by other Fusion.js plu It's useful for when you want to collect data about user actions or other metrics, and send them in bulk to the server to minimize the number of HTTP requests. -For convenience, this plugin automatically flushes its queue on page unload. +For convenience, this plugin automatically flushes its queue before page unload on `document.visibilityState === 'hidden'`. If you need to use the universal event emitter from React, use [`fusion-plugin-universal-events-react`](https://github.com/fusionjs/fusion-plugin-universal-events-react) diff --git a/src/__tests__/test.browser.js b/src/__tests__/test.browser.js index 36c052a6..76d96f40 100644 --- a/src/__tests__/test.browser.js +++ b/src/__tests__/test.browser.js @@ -6,6 +6,7 @@ * @flow */ +/* eslint-env browser */ import test from 'tape-cup'; import App from 'fusion-core'; @@ -20,6 +21,10 @@ import { inMemoryBatchStorage as store, } from '../storage/index.js'; +// Set document.visibilityState to test flushBeforeTerminated +Object.defineProperty(document, 'visibilityState', {value: 'hidden'}); +const visibilitychangeEvent = new Event('visibilitychange'); + /* Test helpers */ function getApp(fetch: Fetch) { const app = new App('el', el => el); @@ -82,7 +87,7 @@ test('Browser EventEmitter', async t => { emitted = true; }); emitter.emit('a', {x: 1}); - emitter.flush(); + window.dispatchEvent(visibilitychangeEvent); emitter.teardown(); return next(); }; @@ -107,7 +112,7 @@ test('Browser EventEmitter adds events back to queue if they fail to send', asyn const emitter = events.from(ctx); t.equal(emitter, events); emitter.emit('a', {x: 1}); - emitter.flush(); + window.dispatchEvent(visibilitychangeEvent); emitter.teardown(); return next(); }; @@ -130,7 +135,7 @@ test('Browser EventEmitter adds events back to queue if they fail to send 2', as const emitter = events.from(ctx); t.equal(emitter, events); emitter.emit('a', {x: 1}); - emitter.flush(); + window.dispatchEvent(visibilitychangeEvent); emitter.teardown(); return next(); }; diff --git a/src/browser.js b/src/browser.js index b2150328..86ab7625 100644 --- a/src/browser.js +++ b/src/browser.js @@ -35,11 +35,11 @@ class UniversalEmitter extends Emitter { this.flush = this.flushInternal.bind(this); this.fetch = fetch; this.setFrequency(5000); - window.addEventListener('beforeunload', this.flush); + window.addEventListener('visibilitychange', this.flushBeforeTerminated); } setFrequency(frequency: number): void { window.clearInterval(this.interval); - this.interval = setInterval(this.flush, frequency); + this.interval = setInterval(this.flushInternal, frequency); } emit(type: mixed, payload: mixed): void { payload = super.mapEvent(type, payload); @@ -50,6 +50,8 @@ class UniversalEmitter extends Emitter { from(): UniversalEmitter { return this; } + flushBeforeTerminated = () => + document.visibilityState === 'hidden' && this.flushInternal(); async flushInternal(): Promise { const items = this.storage.getAndClear(); if (items.length === 0) return; @@ -73,7 +75,7 @@ class UniversalEmitter extends Emitter { } } teardown(): void { - window.removeEventListener('beforeunload', this.flush); + window.removeEventListener('visibilitychange', this.flushBeforeTerminated); clearInterval(this.interval); this.interval = null; }