Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
412 lines
12 KiB
JavaScript
Executable file
412 lines
12 KiB
JavaScript
Executable file
/* global EventDispatcher, IDBKeyRange, Promise */
|
|
|
|
/* exported MessageDB */
|
|
|
|
'use strict';
|
|
|
|
var MessageDB = (function() {
|
|
var DB_NAME = 'wappush';
|
|
var DB_VERSION = 2;
|
|
var MESSAGE_STORE_NAME = 'message';
|
|
var VIEWED_STORE_NAME = 'viewed';
|
|
var KEY_PATH = 'timestamp';
|
|
var ID_INDEX = 'id';
|
|
var ID_INDEX_KEYPATH = 'id';
|
|
|
|
var db = null;
|
|
|
|
/**
|
|
* Turns an IndexedDB transaction in a promise that is resolved when the
|
|
* transaction completes or is rejected if it fails.
|
|
*
|
|
* @param {Object} transaction An IDBTransaction object.
|
|
* @return {Promise} A promise that is resolved when the transaction
|
|
* completes or is rejected if an error occurs.
|
|
*/
|
|
function mdb_transactionPromise(transaction) {
|
|
return new Promise(function(resolve, reject) {
|
|
transaction.oncomplete = function mdb_onTransactionComplete(event) {
|
|
resolve();
|
|
};
|
|
transaction.onerror = function mdb_onTransactionError(event) {
|
|
reject(event.target.error);
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete all the messages that have been marked as viewed, this is done at
|
|
* startup to get rid of all the messages that have already been shown to the
|
|
* user.
|
|
*
|
|
* @return {Object} A promise for this operation
|
|
*/
|
|
function mdb_deleteViewedMessages() {
|
|
var transaction =
|
|
db.transaction([ MESSAGE_STORE_NAME, VIEWED_STORE_NAME ], 'readwrite');
|
|
var promise = mdb_transactionPromise(transaction);
|
|
var store = transaction.objectStore(MESSAGE_STORE_NAME);
|
|
var viewedStore = transaction.objectStore(VIEWED_STORE_NAME);
|
|
var req = viewedStore.openCursor();
|
|
|
|
req.onsuccess = event => {
|
|
var cursor = event.target.result;
|
|
|
|
if (cursor) {
|
|
store.delete(cursor.value.timestamp);
|
|
cursor.delete();
|
|
cursor.continue();
|
|
}
|
|
};
|
|
|
|
return promise;
|
|
}
|
|
|
|
/**
|
|
* Opens the database, creates it if it has not been created already. Returns
|
|
* a promise that is resolved with the value of the database object or
|
|
* rejected with an error object.
|
|
*
|
|
* @return {Object} A promise for this operation.
|
|
*/
|
|
function mdb_open() {
|
|
if (db) {
|
|
return Promise.resolve(db);
|
|
}
|
|
|
|
var indexedDB;
|
|
|
|
try {
|
|
indexedDB = window.indexedDB || window.webkitIndexedDB ||
|
|
window.mozIndexedDB || window.msIndexedDB;
|
|
} catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
|
|
if (!indexedDB) {
|
|
return Promise.reject(new Error('IndexedDB is not available'));
|
|
}
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
var req;
|
|
|
|
try {
|
|
req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
} catch (e) {
|
|
reject(e);
|
|
return;
|
|
}
|
|
|
|
req.onsuccess = function mdb_opened(event) {
|
|
db = event.target.result;
|
|
mdb_deleteViewedMessages().then(() => resolve(db));
|
|
};
|
|
req.onerror = function mdb_openError(event) {
|
|
reject(event.target.error);
|
|
};
|
|
req.onupgradeneeded = function mdb_upgradeNeeded(event) {
|
|
var db = event.target.result;
|
|
var store = null;
|
|
|
|
switch (event.oldVersion) {
|
|
case 0:
|
|
store = db.createObjectStore(
|
|
MESSAGE_STORE_NAME, { keyPath: KEY_PATH }
|
|
);
|
|
store.createIndex(ID_INDEX, ID_INDEX_KEYPATH, { unique: false });
|
|
/* falls through */
|
|
case 1:
|
|
store = db.createObjectStore(
|
|
VIEWED_STORE_NAME, { keyPath: KEY_PATH }
|
|
);
|
|
break; // We're done with the upgrade
|
|
default:
|
|
event.target.transaction.abort();
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Process a message executing the associated action. This deals with
|
|
* messages with a 'delete' action by deleting all coresponding message and
|
|
* with new messages by storing them.
|
|
*
|
|
* @param {Object} store The messages' object store.
|
|
* @param {Object} message The message to be processed.
|
|
* @returns {Boolean} Returns true if the message has been stored, false
|
|
* if it's been deleted.
|
|
*/
|
|
function mdb_processMessage(store, message) {
|
|
/* If the message has a 'delete' action delete all messages with the
|
|
* corresponding 'si-id' field and drop the current message. All other
|
|
* messages should be stored. */
|
|
if (message.action === 'delete') {
|
|
mdb_deleteById(store, message.id);
|
|
return false;
|
|
} else {
|
|
store.put(message);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a message to the message database according to the out-of-order
|
|
* delivery rules specified in WAP-167 6.2. Returns a promise that is
|
|
* resolved when the message has been stored. This will dispatch all the
|
|
* appropriate events.
|
|
*
|
|
* @fires new
|
|
* @fires update
|
|
* @fires discard
|
|
* @fires delete
|
|
*
|
|
* @param {Object} message A parsed WAP Push message to be processed.
|
|
*
|
|
* @return {Object} A promise for this operation.
|
|
*/
|
|
function mdb_put(message) {
|
|
return mdb_open().then(function mdb_openResolved(db) {
|
|
var transaction = db.transaction(MESSAGE_STORE_NAME, 'readwrite');
|
|
var promise = mdb_transactionPromise(transaction);
|
|
var store = transaction.objectStore(MESSAGE_STORE_NAME);
|
|
var stored;
|
|
|
|
if (!message.id) {
|
|
// The message has no 'si-id' field, store it
|
|
store.put(message);
|
|
mdb_dispatchEvent('new', message);
|
|
return promise;
|
|
}
|
|
|
|
if (!message.created) {
|
|
/* The message has a 'si-id' field but no creation time, process it and
|
|
* store it if needed */
|
|
stored = mdb_processMessage(store, message);
|
|
mdb_dispatchEvent(stored ? 'new' : 'discard', message);
|
|
return promise;
|
|
}
|
|
|
|
/* The message has both a 'si-id' and a 'created' field, make it go
|
|
* through the out-of-order delivery logic. This will check if the
|
|
* message is new, updates an existing one or is just outdated and
|
|
* needs to be discarded. */
|
|
var index = store.index(ID_INDEX);
|
|
var req = index.openCursor(IDBKeyRange.only(message.id));
|
|
|
|
req.onsuccess = function mdb_cursorSuccess(event) {
|
|
var cursor = event.target.result;
|
|
|
|
if (cursor) {
|
|
var storedMessage = cursor.value;
|
|
|
|
/* If this is a new version of an existing message, expire the
|
|
* previous message and flag this message as updated. Older versions
|
|
* are discarded right away with no further action required. */
|
|
if (storedMessage.created) {
|
|
if (storedMessage.created < message.created) {
|
|
cursor.delete(storedMessage);
|
|
message.timestamp = storedMessage.timestamp;
|
|
stored = mdb_processMessage(store, message);
|
|
mdb_dispatchEvent(stored ? 'update' : 'discard', message);
|
|
} else {
|
|
mdb_dispatchEvent('discard', message);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
cursor.continue();
|
|
} else {
|
|
/* We found no matching message with a 'created' property set,
|
|
* process the message and store it if needed */
|
|
stored = mdb_processMessage(store, message);
|
|
mdb_dispatchEvent(stored ? 'new' : 'discard', message);
|
|
}
|
|
};
|
|
|
|
return promise;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Deletes all messages with the specified si-id. Generates a 'delete'
|
|
* event for every message that was deleted.
|
|
*
|
|
* @param {Object} store The messages' object store.
|
|
* @param {String} id The si-id of the messages to be deleted.
|
|
*/
|
|
function mdb_deleteById(store, id) {
|
|
var index = store.index(ID_INDEX);
|
|
var req = index.openCursor(id);
|
|
|
|
req.onsuccess = function mdb_openCursorSuccess(event) {
|
|
var cursor = event.target.result;
|
|
|
|
if (cursor) {
|
|
var message = cursor.value;
|
|
|
|
cursor.delete().onsuccess = function mdb_onDeleted() {
|
|
mdb_dispatchEvent('delete', message);
|
|
};
|
|
cursor.continue();
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Retrieves a message and passes it to the success callback as its sole
|
|
* parameter. Once retrieved the message is marked for removal and deleted
|
|
* the next time the application is started except if it's a CP message in
|
|
* which case it can only be deleted explicitly. A promise is returned that
|
|
* will resolve to the message. If the message is not found then the promise
|
|
* will resolve to the null value.
|
|
*
|
|
* @param {Number} timestamp The timestamp identifiying the message to be
|
|
* retrieved.
|
|
*
|
|
* @return {Object} A promise for this operation.
|
|
*/
|
|
function mdb_retrieve(timestamp) {
|
|
return mdb_open().then(function mdb_openResolved(db) {
|
|
var transaction =
|
|
db.transaction([ MESSAGE_STORE_NAME, VIEWED_STORE_NAME ], 'readwrite');
|
|
var promise = mdb_transactionPromise(transaction);
|
|
|
|
var store = transaction.objectStore(MESSAGE_STORE_NAME);
|
|
var req = store.get(timestamp.toString());
|
|
var message = null;
|
|
|
|
req.onsuccess = function mdb_gotMessage(event) {
|
|
if (event.target.result) {
|
|
message = event.target.result;
|
|
|
|
if (message.type != 'text/vnd.wap.connectivity-xml') {
|
|
var viewedStore = transaction.objectStore(VIEWED_STORE_NAME);
|
|
|
|
/* Mark message as viewed, we'll delete it the next time we
|
|
* open the application. */
|
|
viewedStore.put(message);
|
|
}
|
|
}
|
|
};
|
|
|
|
return promise.then(() => {
|
|
if (message) {
|
|
return message;
|
|
} else {
|
|
return Promise.reject(
|
|
new Error('Message with id ' + timestamp + ' not found')
|
|
);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Deletes all the messages with the specified timestamp.
|
|
*
|
|
* @fires delete
|
|
*
|
|
* @param {String} timestamp The timestamp used to identify the messages to
|
|
* be deleted.
|
|
*
|
|
* @return {Promise} A promise for this operation.
|
|
*/
|
|
function mdb_deleteByTimestamp(timestamp) {
|
|
return mdb_open().then(function mdb_deleteCallback(db) {
|
|
var transaction = db.transaction(MESSAGE_STORE_NAME, 'readwrite');
|
|
var promise = mdb_transactionPromise(transaction);
|
|
var store = transaction.objectStore(MESSAGE_STORE_NAME);
|
|
|
|
timestamp = timestamp.toString();
|
|
store.get(timestamp).onsuccess = function(event) {
|
|
var message = event.target.result;
|
|
|
|
store.delete(timestamp).onsuccess = function mdb_onDeleted() {
|
|
mdb_dispatchEvent('delete', message);
|
|
};
|
|
};
|
|
|
|
return promise;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Removes all messages from the database. Returns a promise that is
|
|
* resolved when the operation has completed. This does not fire events.
|
|
*
|
|
* @return {Object} A promise for this operation.
|
|
*/
|
|
function mdb_clear() {
|
|
return mdb_open().then(function mdb_openResolved(db) {
|
|
var transaction =
|
|
db.transaction([ MESSAGE_STORE_NAME, VIEWED_STORE_NAME ], 'readwrite');
|
|
var promise = mdb_transactionPromise(transaction);
|
|
var store = transaction.objectStore(MESSAGE_STORE_NAME);
|
|
var viewedStore = transaction.objectStore(VIEWED_STORE_NAME);
|
|
|
|
store.clear();
|
|
viewedStore.clear();
|
|
|
|
return promise;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Dispatches a new event to the registered event handlers.
|
|
*
|
|
* @param {String} type The event type.
|
|
* @param {Object} target The event target, usually a message.
|
|
*/
|
|
function mdb_dispatchEvent(type, target) {
|
|
/* We explicitly use the MessageDB singleton as the EventDispatcher code
|
|
* requires the `this' reference to operate. */
|
|
MessageDB.emit(type, target);
|
|
}
|
|
|
|
/**
|
|
* New event, emitted when a new message has been stored in the database
|
|
*
|
|
* @event MessageDB#new
|
|
* @type {Object}
|
|
*/
|
|
|
|
/**
|
|
* Update event, emitted when an existing message was updated.
|
|
*
|
|
* @event MessageDB#update
|
|
* @type {Object}
|
|
*/
|
|
|
|
/**
|
|
* Discard event, emitted when a message was discarded instea of being
|
|
* stored in the database.
|
|
*
|
|
* @event MessageDB#discard
|
|
*/
|
|
|
|
/**
|
|
* Delete event : emitted when a message has been deleted
|
|
*
|
|
* @event MessageDB@delete
|
|
* @type {Object}
|
|
*/
|
|
|
|
/**
|
|
* List of events which can be listened to on this object.
|
|
*/
|
|
var mdb_allowedEvents = [
|
|
'new',
|
|
'update',
|
|
'discard',
|
|
'delete'
|
|
];
|
|
|
|
return EventDispatcher.mixin({
|
|
put: mdb_put,
|
|
retrieve: mdb_retrieve,
|
|
deleteByTimestamp: mdb_deleteByTimestamp,
|
|
clear: mdb_clear
|
|
}, mdb_allowedEvents);
|
|
})();
|