Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Background sync queue callbacks #666

Merged
merged 10 commits into from
Jul 11, 2017
33 changes: 33 additions & 0 deletions lib/deprecate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
* Warns users that an old property is deprecated in favor of a new property
* and aliases the old name to the new name.
* @param {Object} obj The object containing the methods.
* @param {string} oldName The method to deprecate.
* @param {string} newName The new method replacing the deprecated method.
* @param {string} ctx The context project/object to identify the method names.
*/
export default (obj, oldName, newName, ctx) => {
if (Object.prototype.hasOwnProperty.call(obj, oldName)) {
/* eslint-disable no-console */
console.warn(`In ${ctx}: ` +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late comment, but I'd suggest using the warn() method of lib/log-helper here. It will keep the messages in the console consistent, and you can include some of the detailed information in the optional second parameter, like:

logHelper.warn(`${oldName} is deprecated; use ${newName} instead`, {Context: ctx});

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know. Done.

`${oldName} is deprecated, use ${newName} instead`);
/* eslint-enable no-console */

obj[newName] = obj[oldName];
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import Queue from './background-sync-queue';
* failed requests.</caption>
* let bgQueue = new workbox.backgroundSync.QueuePlugin({
* callbacks: {
* onResponse: async(hash, res) => {
* replayDidSucceed: async(hash, res) => {
* self.registration.showNotification('Background sync demo', {
* body: 'Product has been purchased.',
* icon: '/images/shop-icon-384.png',
* });
* },
* onRetryFailure: (hash) => {},
* replayDidFail: (hash) => {},
* requestWillEnqueue: (reqData) => {},
* requestWillDequeue: (reqData) => {},
* },
* });
*
Expand Down
73 changes: 46 additions & 27 deletions packages/workbox-background-sync/src/lib/background-sync-queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,43 +34,62 @@ class Queue {
*
* @param {Object} [input]
* @param {Number} [input.maxRetentionTime = 5 days] Time for which a queued
* request will live in the queue(irespective of failed/success of replay).
* @param {Object} [input.callbacks] Callbacks for successfull/ failed
* replay of a request.
* request will live in the queue(irrespective of failed/success of replay).
* @param {Object} [input.callbacks] Callbacks for successfull/failed
* replay of a request as well as modifying before enqueue/dequeue-ing.
* @param {Fuction} [input.callbacks.replayDidSucceed]
* Invoked with params (hash:string, response:Response) after a request is
* successfully replayed.
* @param {Fuction<string>} [input.callbacks.replayDidFail]
* Invoked with param (hash:string) after a replay attempt has failed.
* @param {Fuction<Object>} [input.callbacks.requestWillEnqueue]
* Invoked with param (reqData:Object) before a failed request is saved to
* the queue. Use this to modify the saved data.
* @param {Fuction<Object>} [input.callbacks.requestWillDequeue]
* Invoked with param (reqData:Object) before a failed request is retrieved
* from the queue. Use this to modify the data before the request is replayed.
* @param {string} [input.queueName] Queue name inside db in which
* requests will be queued.
* @param {BroadcastChannel=} [input.broadcastChannel] BroadcastChannel
* which will be used to publish messages when the request will be queued.
*/
constructor({maxRetentionTime = maxAge, callbacks, queueName,
broadcastChannel, dbName = defaultDBName} = {}) {
if(queueName) {
isType({queueName}, 'string');
}
constructor({
broadcastChannel,
callbacks,
queueName,
dbName = defaultDBName,
maxRetentionTime = maxAge,
} = {}) {
if(queueName) {
isType({queueName}, 'string');
}

if(maxRetentionTime) {
isType({maxRetentionTime}, 'number');
}
if(maxRetentionTime) {
isType({maxRetentionTime}, 'number');
}

if(broadcastChannel) {
isInstance({broadcastChannel}, BroadcastChannel);
}
if(broadcastChannel) {
isInstance({broadcastChannel}, BroadcastChannel);
}

isType({dbName}, 'string');
isType({dbName}, 'string');

this._dbName = dbName;
this._queue = new RequestQueue({
config: {
maxAge: maxRetentionTime,
},
queueName,
idbQDb: new IDBHelper(this._dbName, 1, 'QueueStore'),
broadcastChannel,
});
this._requestManager = new RequestManager({callbacks,
queue: this._queue});
this._dbName = dbName;
this._queue = new RequestQueue({
config: {
maxAge: maxRetentionTime,
},
queueName,
idbQDb: new IDBHelper(this._dbName, 1, 'QueueStore'),
broadcastChannel,
callbacks,
});
this._requestManager = new RequestManager({
callbacks,
queue: this._queue,
});

this.cleanupQueue();
this.cleanupQueue();
}

/**
Expand Down
21 changes: 15 additions & 6 deletions packages/workbox-background-sync/src/lib/request-manager.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import deprecate from '../../../../lib/deprecate';
import {putResponse} from './response-manager';
import {getFetchableRequest} from './queue-utils';
import {tagNamePrefix, replayAllQueuesTag} from './constants';
Expand All @@ -18,8 +19,15 @@ class RequestManager {
*
* @private
*/
constructor({callbacks, queue}) {
this._globalCallbacks = callbacks || {};
constructor({callbacks, queue} = {}) {
callbacks = callbacks || {};

// Rename deprecated callbacks.
const ctx = 'workbox-background-sync.RequestManager.callbacks';
deprecate(callbacks, 'onResponse', 'replayDidSucceed', ctx);
deprecate(callbacks, 'onRetryFailure', 'replayDidFail', ctx);

this._globalCallbacks = callbacks;
this._queue = queue;
this.attachSyncHandler();
}
Expand Down Expand Up @@ -70,8 +78,8 @@ class RequestManager {
response: response.clone(),
idbQDb: this._queue.idbQDb,
});
if (this._globalCallbacks.onResponse)
this._globalCallbacks.onResponse(hash, response);
if (this._globalCallbacks.replayDidSucceed)
this._globalCallbacks.replayDidSucceed(hash, response);
}
} catch(err) {
return Promise.reject(err);
Expand All @@ -94,8 +102,9 @@ class RequestManager {
try {
await this.replayRequest(hash);
} catch (err) {
if(this._globalCallbacks.onRetryFailure)
this._globalCallbacks.onRetryFailure(hash, err);
if (this._globalCallbacks.replayDidFail) {
this._globalCallbacks.replayDidFail(hash, err);
}
failedItems.push(err);
}
}
Expand Down
40 changes: 30 additions & 10 deletions packages/workbox-background-sync/src/lib/request-queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ class RequestQueue {
queueName = defaultQueueName + '_' + _queueCounter++,
idbQDb,
broadcastChannel,
callbacks,
}) {
this._isQueueNameAddedToAllQueue = false;
this._queueName = queueName;
this._config = config;
this._idbQDb = idbQDb;
this._broadcastChannel = broadcastChannel;
this._globalCallbacks = callbacks || {};
this._queue = [];
this.initQueue();
}
Expand Down Expand Up @@ -86,6 +88,7 @@ class RequestQueue {
* preferably when network comes back
*
* @param {Request} request request object to be queued by this
* @return {Promise}
*
* @memberOf Queue
* @private
Expand All @@ -94,17 +97,23 @@ class RequestQueue {
isInstance({request}, Request);

const hash = `${request.url}!${Date.now()}!${_requestCounter++}`;
const queuableRequest =
await getQueueableRequest({
request,
config: this._config,
});
const reqData = await getQueueableRequest({
request,
config: this._config,
});

// Apply the `requestWillEnqueue` callback so plugins can modify the
// request data before it's stored in IndexedDB.
if (this._globalCallbacks.requestWillEnqueue) {
this._globalCallbacks.requestWillEnqueue(reqData);
}

try{
this._queue.push(hash);

// add to queue
this.saveQueue();
this._idbQDb.put(hash, queuableRequest);
this._idbQDb.put(hash, reqData);
await this.addQueueNameToAllQueues();
// register sync
self.registration &&
Expand All @@ -117,31 +126,42 @@ class RequestQueue {
id: hash,
url: request.url,
});
} catch(e) {

return hash;
} catch (err) {
// broadcast the failure of request added to the queue
broadcastMessage({
broadcastChannel: this._broadcastChannel,
type: broadcastMessageFailedType,
id: hash,
url: request.url,
});

return err;
}
}

/**
* get the Request from the queue at a particular index
*
* @param {string} hash hash of the request at the given index
* @return {Request} request object corresponding to given hash
* @return {Promise<Object>} request object corresponding to given hash
* @memberOf Queue
* @private
*/
async getRequestFromQueue({hash}) {
isType({hash}, 'string');

if(this._queue.includes(hash)) {
const req = await this._idbQDb.get(hash);
return req;
const reqData = await this._idbQDb.get(hash);

// Apply the `requestWillDequeue` callback so plugins can modify the
// stored data before it's converted back into a request to be replayed.
if (this._globalCallbacks.requestWillDequeue) {
this._globalCallbacks.requestWillDequeue(reqData);
}

return reqData;
}
}

Expand Down
49 changes: 36 additions & 13 deletions packages/workbox-background-sync/test/browser/request-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,11 @@
*/

/* eslint-env mocha, browser */
/* global chai, workbox */
/* global chai, sinon, workbox */

'use strict';
describe('request-manager test', () => {
let responseAchieved = 0;
const callbacks = {
onResponse: function() {
responseAchieved ++;
},
};

const callbacks = {};
let queue;
let reqManager;

Expand Down Expand Up @@ -56,13 +50,42 @@ describe('request-manager test', () => {
});

it('check replay', async function() {
const backgroundSyncQueue
= new workbox.backgroundSync.test.BackgroundSyncQueue({
callbacks,
});
sinon.spy(self, 'fetch');

callbacks.replayDidSucceed = sinon.spy();
callbacks.replayDidFail = sinon.spy();

const backgroundSyncQueue =
new workbox.backgroundSync.test.BackgroundSyncQueue({callbacks});

await backgroundSyncQueue.pushIntoQueue({request: new Request('/__echo/counter')});
await backgroundSyncQueue.pushIntoQueue({request: new Request('/__echo/counter')});
await backgroundSyncQueue._requestManager.replayRequests();
chai.assert.equal(responseAchieved, 2);

// Asset replayDidSucceed callback was called with the correct arguments.
chai.assert.equal(callbacks.replayDidSucceed.callCount, 2);
chai.assert(callbacks.replayDidSucceed.alwaysCalledWith(
sinon.match.string, sinon.match.instanceOf(Response)));

// Assert fetch was called for each replayed request.
chai.assert(self.fetch.calledTwice);

await backgroundSyncQueue.pushIntoQueue({request: new Request('/__test/404')});
try {
await backgroundSyncQueue._requestManager.replayRequests();
} catch (err) {
// Error is expected due to 404 response.
}

// Asset replayDidFail callback was called with the correct arguments.
chai.assert.equal(callbacks.replayDidSucceed.callCount, 2);
chai.assert.equal(callbacks.replayDidFail.callCount, 1);
chai.assert(callbacks.replayDidFail.alwaysCalledWith(
sinon.match.string, sinon.match.instanceOf(Response)));

delete callbacks.replayDidSucceed;
delete callbacks.replayDidFail;

self.fetch.restore();
});
});
Loading