VulpesOS/gaia/apps/wappush/js/messagedb.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

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);
})();