I want to backup IndexedDB into json file, and restore it on another machine / browser.
the goal is to fully backup and import website session (localStorage, cookies etc...)
I think that dumpIndexedDb() function works correctly,
the problem occur when I try to get this data and restore it back using restoreIndexedDbDump(data). there's no errors.
but only few databases restored.
I'm pretty sure I was missed something with indexedDB api in the restore operation.
thanks in advance.
function dumpDB(dbName) {
return new Promise((resolve, reject) => {
var request = indexedDB.open(dbName)
request.onerror = e => reject(e)
request.onsuccess = (e) => {
var data = {} // { dbVersion: 1, oName: {keyPath: 'key', data: [{},{}]} }
var db = e.target.result
var dbVersion = db.version
data['dbVersion'] = dbVersion
data['objectStores'] = {}
var oStores = db.objectStoreNames
for (let osName of oStores) {
var transaction = db.transaction(osName, "readonly")
var oStore = transaction.objectStore(osName)
var keyPath = oStore.keyPath
data['objectStores'][osName] = {}
data['objectStores'][osName]['keyPath'] = keyPath
var request = oStore.getAll()
request.onerror = e => reject(e)
request.onsuccess = (e) => {
data['objectStores'][osName]['data'] = e.target.result
}
}
db.close()
resolve(data)
}
})
}
async function dumpIndexedDb() {
var data = {}
var dbs = await indexedDB.databases() // not working in firefox, tested in chrome.
for (let db of dbs) {
var dbData = await dumpDB(db.name)
data[db.name] = dbData
}
return data // {dbName: { dbVersion: 1, oName: {keyPath: 'key', data: [{},{}]} } }
}
function restoreObjectStore(dbName, version, oName, keyPath, data) {
return new Promise((resolve, reject) => {
request = window.indexedDB.open(dbName, version);
request.onerror = e => reject(e)
request.onupgradeneeded = function (e) {
try {
var db = e.target.result
if (keyPath) var objectStore = db.createObjectStore(oName, { keyPath: keyPath, autoIncrement: false })
else var objectStore = db.createObjectStore(oName, { autoIncrement: false })
for (let row of data) {
console.log(`adding row of ${oName}`)
}
objectStore.transaction.commit()
db.close()
resolve("ok")
} catch(e) {
reject(e)
}
}
})
}
async function restoreIndexedDbDump(data) {
for (let dbName in data) {
console.log(`deleting db ${dbName}`)
indexedDB.deleteDatabase(dbName)
for (let oStoreName in data[dbName]['objectStores']) {
let odata = data[dbName]['objectStores'][oStoreName]['data']
let dbVersion = data[dbName]['dbVersion']
if ( data[dbName]['objectStores'][oStoreName]['keyPath'] ) {
var keyPath = data[dbName]['objectStores'][oStoreName]['keyPath']
await restoreObjectStore(dbName, dbVersion, oStoreName, keyPath, odata)
}
else {
await restoreObjectStore(dbName, dbVersion, oStoreName, null, odata)
}
}
}
}
async function testBackupandRestore() {
var data = await dumpIndexedDb()
await restoreIndexedDbDump(data)
}
Related
I have a firebase callable function like
exports.getUserInfo = functions.https.onCall(async (data, context) => {
const userIdRef = store.collection('UserID').doc('Document');
let res = null;
try {
res = await store.runTransaction(async t => {
const doc = await t.get(userIdRef);
currentUserId = doc.data().ID;
const newUserIdNum = currentUserId + 1;
await t.update(userIdRef, {ID: newUserIdNum});
let initUserData = {
'userID': newUserId,
}
return initUserData ;
});
console.log('Transaction success');
} catch (e) {
console.error('Transaction failure:', e);
}
return res;
})
but I am not sure how to create unit test about this. I want to mock UserID/Documment before I call
test.wrap(getUserInfo), like
const myFunctions = require('../index.js')
const getUserInfoWrapped = test.wrap(myFunctions.getUserInfo);
var assert = require('assert');
describe('User', function() {
describe('getUserInfo()', function() {
it('should create user', async function() {
// sample code, this won't work
const snapshot = test.firestore.makeDocumentSnapshot({ID: 1001}, 'UserID/Document');
const data = {}
const result = await getUserInfoWrapped (data, {});
assert.equal(result.userID, "1002");
});
});
});
seems this case not covered by firebase document
Reference:
https://firebase.google.com/docs/functions/unit-testing
https://github.com/firebase/firebase-functions-test/blob/c77aa92d345b8e4fb5ad98534989eb8dcf7d9bc4/spec/providers/https.spec.ts
how can I change the code so that the computer1 code connects to the code of computer 2(computer1 and computer2 are not the same computers but on the same network).
It works locally but not when it's two different computers
computer 1 and computer2 code that is used for the connection is defined below
this is the code that does the networking on computer1
async function createConnection() {
abortButton.disabled = false;
sendFileButton.disabled = true;
localConnection = new RTCPeerConnection();//this is the line I think I need to change
console.log('Created local peer connection object localConnection');
sendChannel = localConnection.createDataChannel('sendDataChannel');
sendChannel.binaryType = 'arraybuffer';
console.log('Created send data channel');
sendChannel.addEventListener('open', onSendChannelStateChange);
sendChannel.addEventListener('close', onSendChannelStateChange);
sendChannel.addEventListener('error', onError);
localConnection.addEventListener('icecandidate', async event => {
console.log('Local ICE candidate: ', event.candidate);
await localConnection.addIceCandidate(event.candidate);
});
this is the part of the program that have the role to receive the request and the file data on the computer2
async function server(){
remoteConnection = new RTCPeerConnection();
alert("start");
console.log('Created remote peer connection object remoteConnection');
remoteConnection.addEventListener('icecandidate', async event => {
console.log('Remote ICE candidate: ', event.candidate);
await localConnection.addIceCandidate(event.candidate);
});
remoteConnection.addEventListener('datachannel', receiveChannelCallback);
}
the code that I modified(main.js)
/* eslint no-unused-expressions: 0 */
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
let localConnection;
let remoteConnection;
let sendChannel;
let receiveChannel;
let fileReader;
const bitrateDiv = document.querySelector('div#bitrate');
const fileInput = document.querySelector('input#fileInput');
const abortButton = document.querySelector('button#abortButton');
const downloadAnchor = document.querySelector('a#download');
const sendProgress = document.querySelector('progress#sendProgress');
const receiveProgress = document.querySelector('progress#receiveProgress');
const statusMessage = document.querySelector('span#status');
const sendFileButton = document.querySelector('button#sendFile');
let receiveBuffer = [];
let receivedSize = 0;
let bytesPrev = 0;
let timestampPrev = 0;
let timestampStart;
let statsInterval = null;
let bitrateMax = 0;
server();
sendFileButton.addEventListener('click', () => createConnection());
fileInput.addEventListener('change', handleFileInputChange, false);
abortButton.addEventListener('click', () => {
if (fileReader && fileReader.readyState === 1) {
console.log('Abort read!');
fileReader.abort();
}
});
async function handleFileInputChange() {
const file = fileInput.files[0];
if (!file) {
console.log('No file chosen');
} else {
sendFileButton.disabled = false;
}
}
async function server(){
//const servers = {
//iceServers: [
// {
// urls: ['stun:stun1.l.google.com:19302', //'stun:stun2.l.google.com:19302'],
// },
//],
//iceCandidatePoolSize: 10,
//};
remoteConnection = new RTCPeerConnection();
alert("start");
console.log('Created remote peer connection object remoteConnection');
remoteConnection.addEventListener('icecandidate', async event => {
console.log('Remote ICE candidate: ', event.candidate);
await localConnection.addIceCandidate(event.candidate);
});
remoteConnection.addEventListener('datachannel', receiveChannelCallback);
}
async function createConnection() {
abortButton.disabled = false;
sendFileButton.disabled = true;
//const servers = {
//iceServers: [
// {
// urls: ['stun:stun1.l.google.com:19302', //'stun:stun2.l.google.com:19302'],
// },
//],
//iceCandidatePoolSize: 10,
//};
localConnection = new RTCPeerConnection();
console.log('Created local peer connection object localConnection');
sendChannel = localConnection.createDataChannel('sendDataChannel');
sendChannel.binaryType = 'arraybuffer';
console.log('Created send data channel');
sendChannel.addEventListener('open', onSendChannelStateChange);
sendChannel.addEventListener('close', onSendChannelStateChange);
sendChannel.addEventListener('error', onError);
localConnection.addEventListener('icecandidate', async event => {
console.log('Local ICE candidate: ', event.candidate);
await localConnection.addIceCandidate(event.candidate);
});
try {
const offer = await localConnection.createOffer();
await gotLocalDescription(offer);
} catch (e) {
console.log('Failed to create session description: ', e);
}
fileInput.disabled = true;
}
function sendData() {
const file = fileInput.files[0];
console.log(`File is ${[file.name, file.size, file.type, file.lastModified].join(' ')}`);
// Handle 0 size files.
statusMessage.textContent = '';
downloadAnchor.textContent = '';
if (file.size === 0) {
bitrateDiv.innerHTML = '';
statusMessage.textContent = 'File is empty, please select a non-empty file';
closeDataChannels();
return;
}
sendProgress.max = file.size;
receiveProgress.max = file.size;
const chunkSize = 16384;
fileReader = new FileReader();
let offset = 0;
fileReader.addEventListener('error', error => console.error('Error reading file:', error));
fileReader.addEventListener('abort', event => console.log('File reading aborted:', event));
fileReader.addEventListener('load', e => {
console.log('FileRead.onload ', e);
sendChannel.send(e.target.result);
offset += e.target.result.byteLength;
sendProgress.value = offset;
if (offset < file.size) {
readSlice(offset);
}
});
const readSlice = o => {
console.log('readSlice ', o);
const slice = file.slice(offset, o + chunkSize);
fileReader.readAsArrayBuffer(slice);
};
readSlice(0);
}
function closeDataChannels() {
console.log('Closing data channels');
sendChannel.close();
console.log(`Closed data channel with label: ${sendChannel.label}`);
sendChannel = null;
if (receiveChannel) {
receiveChannel.close();
console.log(`Closed data channel with label: ${receiveChannel.label}`);
receiveChannel = null;
}
localConnection.close();
remoteConnection.close();
localConnection = null;
remoteConnection = null;
console.log('Closed peer connections');
// re-enable the file select
fileInput.disabled = false;
abortButton.disabled = true;
sendFileButton.disabled = false;
}
async function gotLocalDescription(desc) {
await localConnection.setLocalDescription(desc);
console.log(`Offer from localConnection\n ${desc.sdp}`);
await remoteConnection.setRemoteDescription(desc);
try {
const answer = await remoteConnection.createAnswer();
await gotRemoteDescription(answer);
} catch (e) {
console.log('Failed to create session description: ', e);
}
}
async function gotRemoteDescription(desc) {
await remoteConnection.setLocalDescription(desc);
console.log(`Answer from remoteConnection\n ${desc.sdp}`);
await localConnection.setRemoteDescription(desc);
}
function receiveChannelCallback(event) {
console.log('Receive Channel Callback');
receiveChannel = event.channel;
receiveChannel.binaryType = 'arraybuffer';
receiveChannel.onmessage = onReceiveMessageCallback;
receiveChannel.onopen = onReceiveChannelStateChange;
receiveChannel.onclose = onReceiveChannelStateChange;
receivedSize = 0;
bitrateMax = 0;
downloadAnchor.textContent = '';
downloadAnchor.removeAttribute('download');
if (downloadAnchor.href) {
URL.revokeObjectURL(downloadAnchor.href);
downloadAnchor.removeAttribute('href');
}
}
function onReceiveMessageCallback(event) {
console.log(`Received Message ${event.data.byteLength}`);
receiveBuffer.push(event.data);
receivedSize += event.data.byteLength;
receiveProgress.value = receivedSize;
// we are assuming that our signaling protocol told
// about the expected file size (and name, hash, etc).
const file = fileInput.files[0];
if (receivedSize === file.size) {
const received = new Blob(receiveBuffer);
receiveBuffer = [];
downloadAnchor.href = URL.createObjectURL(received);
downloadAnchor.download = file.name;
downloadAnchor.textContent =
`Click to download '${file.name}' (${file.size} bytes)`;
downloadAnchor.style.display = 'block';
const bitrate = Math.round(receivedSize * 8 /
((new Date()).getTime() - timestampStart));
bitrateDiv.innerHTML =
`<strong>Average Bitrate:</strong> ${bitrate} kbits/sec (max: ${bitrateMax} kbits/sec)`;
if (statsInterval) {
clearInterval(statsInterval);
statsInterval = null;
}
closeDataChannels();
}
}
function onSendChannelStateChange() {
if (sendChannel) {
const {readyState} = sendChannel;
console.log(`Send channel state is: ${readyState}`);
if (readyState === 'open') {
sendData();
}
}
}
function onError(error) {
if (sendChannel) {
console.error('Error in sendChannel:', error);
return;
}
console.log('Error in sendChannel which is already closed:', error);
}
async function onReceiveChannelStateChange() {
if (receiveChannel) {
const readyState = receiveChannel.readyState;
console.log(`Receive channel state is: ${readyState}`);
if (readyState === 'open') {
timestampStart = (new Date()).getTime();
timestampPrev = timestampStart;
statsInterval = setInterval(displayStats, 500);
await displayStats();
}
}
}
// display bitrate statistics.
async function displayStats() {
if (remoteConnection && remoteConnection.iceConnectionState === 'connected') {
const stats = await remoteConnection.getStats();
let activeCandidatePair;
stats.forEach(report => {
if (report.type === 'transport') {
activeCandidatePair = stats.get(report.selectedCandidatePairId);
}
});
if (activeCandidatePair) {
if (timestampPrev === activeCandidatePair.timestamp) {
return;
}
// calculate current bitrate
const bytesNow = activeCandidatePair.bytesReceived;
const bitrate = Math.round((bytesNow - bytesPrev) * 8 /
(activeCandidatePair.timestamp - timestampPrev));
bitrateDiv.innerHTML = `<strong>Current Bitrate:</strong> ${bitrate} kbits/sec`;
timestampPrev = activeCandidatePair.timestamp;
bytesPrev = bytesNow;
if (bitrate > bitrateMax) {
bitrateMax = bitrate;
}
}
}
}
Thanks for helping
You can read about peer connections where every peer connection is handled by the RTCPeerConnection object and it defines how the peer connection is set up and how it should include the information about the ICE servers to use.
You can define the ICE servers as following:
localConnection = new RTCPeerConnection({iceServers: [{ url: "stun:"+ ip +":8003" }]})
or
localConnection = new RTCPeerConnection({iceServers: [{ url: + ip + ':port' }]})
or
var servers = {
iceTransportPolicy: 'relay',
iceServers: [{
urls: "turn:[" + ip + "]:3478",
username: "username",
credential: "password"
}
]};
var localConnection = new RTCPeerConnection(servers);
Moreover, You can find the full code here or
here under the folder name filetransfer .
I have a nodeJS server and want to set up a connection and export function to post messages to queue from the js file.
const amqp = require("amqplib");
const url = process.env.RABBITMQ_SERVER;
let channel = null;
amqp.connect(url, (e, conn) =>
conn.createChannel((e, ch) => {
channel = ch;
})
);
module.exports = publishToQueue = (
data,
queueName = process.env.RABBITMQ_QUEUE
) => channel.sendToQueue(queueName, new Buffer.from(data));
process.on("exit", code => {
ch.close();
console.log("closing rabbitmq channel");
});
But when I try to import and use it, I've got empty object {}
hooks: {
beforeCreate (instance) {
console.log(amqp)
amqp(JSON.stringify(instance.toJSON()))
}
}
UPDATE:
thanks HosseinAbha's answer, i've ended up with creating a class and set up connection in constuctor
const amqp = require("amqplib");
const url = process.env.RABBITMQ_SERVER;
class RABBITMQ {
constructor () {
this.connection = null
this.channel = null
this.connect()
}
async connect () {
try {
this.connection = await amqp.connect(url)
this.channel = await this.connection.createChannel()
await this.channel.assertQueue(process.env.RABBITMQ_QUEUE)
await this.channel.bindQueue(process.env.RABBITMQ_QUEUE, process.env.RABBITMQ_EXCHANGE)
await this.channel.assertExchange(process.env.RABBITMQ_EXCHANGE, 'fanout', { durable: true })
} catch (err){
console.log(err)
throw new Error('Connection failed')
}
}
async postData (data) {
if (!this.connection) await this.connect()
try {
this.channel.publish(process.env.RABBITMQ_EXCHANGE, `${data.type}.${data.event_type}`, new Buffer.from(JSON.stringify(data)))
} catch (err){
console.error(err)
}
}
}
module.exports = new RABBITMQ()
Your publishToQueue function should return a promise and it should connect to rabbitMQ before doing anything. Your function should be something like this:
const connectToChannel = async () => {
try {
let connection = await amqp.connect(url)
return connection.createChannel()
} catch (e) {
console.error('failed to create amqp channel: ', e)
}
}
let channel;
module.exports = publishToQueue = async (
data,
queueName = process.env.RABBITMQ_QUEUE
) => {
if (channel == null) {
channel = await connectToChannel();
}
return channel.sendToQueue(queueName, Buffer.from(data));
}
You also don't need to instantiate the Buffer and Buffer.from is enough.
I have a row with value like this below:
{
"id": 1,
"token": "abcd"
}
How do I delete and save the value without "token" so it becomes this?
{
"id": 1
}
Do I need to first get the object, modify it then save back?
Maybe this will help you:
function patch(db, id, delta) {
return new Promise((resolve, reject) => {
const tx = db.transaction('mystore', 'readwrite');
tx.onerror = (event) => reject(event.target.error);
tx.oncomplete = () => resolve();
const store = tx.objectStore('mystore');
const request = store.get(id);
request.onsuccess = (event) => {
const object = event.target.result;
if (!object) {
reject(new Error(`No matching object for ${id}`));
return;
}
for (const prop in delta) {
if (typeof delta[prop] === 'undefined') {
delete object[prop];
} else {
object[prop] = delta[prop];
}
}
store.put(object);
};
});
}
async function dostuff() {
let db;
const id = 1;
const delta = {
token: undefined
};
try {
db = await connect();
await patch(db, id, delta);
} finally {
if (db) {
db.close();
}
}
}
I am opening a database using indexedDB using javascript for keeping a diary entry. I am doing three things with the database: display all entry, add entry, and remove entry.
It fails when it tries to create a transaction because 'db' is 'undefined.' I was expecting db to be defined by the time the code tries to create a transaction.
Error is caused at the line with code:
const getObjectStore = (storeName, mode) => {
console.log('db: ', db);
const tx = db.transaction(storeName, mode); // error is thrown because db is undefined
return tx.objectStore(storeName);
};
I tried to put console.log at different places to see what is wrong. I found that db takes time before it gets defined by an onsuccess response or onupgradeneeded response. I don't see any other way to define db before it is used. Can you show me how I can assure myself for the assignment of db before it is used?
Outputs:
> adding something in the diary
> db from getObjectStore: undefined
> Uncaught TypeError: Cannot read property 'transaction' of undefined
at getObjectStore (script.js:35)
at Object.setAnEntry [as add] (script.js:62)
at script.js:81
at script.js:215
getObjectStore # script.js:35
setAnEntry # script.js:62
(anonymous) # script.js:81
(anonymous) # script.js:215
> there is indexeddb!
> db: IDBDatabase {name: "medDiary", version: 1, objectStoreNames: DOMStringList, onabort: null, onclose: null, …}
All the code i am working with:
// DATABASE FOR THE DIARY
const Dairy = (() => {
if (window.indexedDB) {
let db;
const DB_NAME = 'Dairy';
const DB_VERSION = 1;
const DB_STORE_NAME = 'diaries';
const request = window.indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => {
console.log('Error requesting to open database permission denied.');
};
request.onsuccess = (event) => {
console.log('there is indexeddb!');
db = event.target.result;
console.log('db: ', db);
db.onerror = (evt) => {
console.error(`Database error: ${evt.target.errorCode}`);
};
};
request.onupgradeneeded = (event) => {
db = request.result;
const store = event.currentTarget.result.createObjectStore(DB_STORE_NAME, { keyPath: 'date' });
store.createIndex('subject', 'subject', { unique: false });
store.createIndex('date', 'date', { unique: true });
store.createIndex('description', 'description', { unique: false });
};
const getObjectStore = (storeName, mode) => {
console.log('db from getObjectStore: ', db);
const tx = db.transaction(storeName, mode);
return tx.objectStore(storeName);
};
const getAllEntries = () => new Promise((resolve) => {
const result = [];
const store = getObjectStore(DB_STORE_NAME, 'readonly');
const req = store.openCursor();
req.onsuccess = (evt) => {
const cursor = evt.target.result;
if (cursor) {
const retrive = store.get(cursor.key);
retrive.onsuccess = function (evt) {
const value = evt.target.result;
result.append(value);
};
cursor.continue();
} else {
console.log('No more entries');
resolve(result);
}
};
});
const setAnEntry = (value) => {
const store = getObjectStore(DB_STORE_NAME, 'readwrite');
store.put(value);
};
const removeAnEntry = (value) => {
const store = getObjectStore(DB_STORE_NAME, 'readwrite');
store.remove(value);
};
return {
all: getAllEntries,
add: setAnEntry,
remove: removeAnEntry,
};
}
alert("Your browser doesn't support a stable version of IndexedDB. Dairy related features will not be available.");
return {};
})();
console.log('adding something in the diary');
console.log('... ', Dairy.add({ subject: 'hello', description: 'bluh bluh', date: Date() }));
console.log('show all: ', Dairy.all());
You can use a promise to open the DB:
let promise = new Promise(function(resolve, reject)
{
//check for support
if (!('indexedDB' in window)) {
//console.log('This browser doesn\'t support IndexedDB');
reject("indexedDB not supported");
}
var request = indexedDB.open(dbName, dbVersion);
request.onerror = function (event) {
reject("Error opening DB");
};
request.onsuccess = function (event) {
console.log("opened!");
db = request.result;
resolve(true);
};
});
And then you can use:
promise
.then(
result => {
your stuff here sine the DB is now open!!
},
error => console.log(error)
)
The parameter passed to resolve will be available as 'result'.
To use in your module format:
Add init function:
async function init()
{
let promise = new Promise(function(resolve, reject)
{
//check for support
if (!('indexedDB' in window)) {
//console.log('This browser doesn\'t support IndexedDB');
reject("indexedDB not supported");
}
var request = indexedDB.open(dbName, dbVersion);
request.onerror = function (event) {
reject("Error opening DB");
};
request.onsuccess = function (event) {
console.log("opened!");
db = request.result;
resolve(true);
};
});
let result = await promise;
}
Your module rewritten:
const Dairy = (() => {
if (window.indexedDB)
{
let db;
const DB_NAME = 'Dairy';
const DB_VERSION = 1;
const DB_STORE_NAME = 'diaries';
async function init()
{
let promise = new Promise(function(resolve, reject)
{
const request = window.indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => {
reject('Error requesting to open database permission denied.');
};
request.onsuccess = (event) => {
console.log('there is indexeddb!');
db = event.target.result;
console.log('db: ', db);
db.onerror = (evt) => {
reject("Database error: ${evt.target.errorCode}");
};
resolve(true);
};
request.onupgradeneeded = (event) => {
db = request.result;
const store = event.currentTarget.result.createObjectStore(DB_STORE_NAME, { keyPath: 'date' });
store.createIndex('subject', 'subject', { unique: false });
store.createIndex('date', 'date', { unique: true });
store.createIndex('description', 'description', { unique: false });
};
});
return promise;
}
const getObjectStore = (storeName, mode) => {
console.log('db from getObjectStore: ', db);
const tx = db.transaction(storeName, mode);
return tx.objectStore(storeName);
};
const getAllEntries = () => new Promise((resolve) => {
const result = [];
const store = getObjectStore(DB_STORE_NAME, 'readonly');
const req = store.openCursor();
req.onsuccess = (evt) => {
const cursor = evt.target.result;
if (cursor) {
const retrive = store.get(cursor.key);
retrive.onsuccess = function (evt) {
const value = evt.target.result;
result.append(value);
};
cursor.continue();
} else {
console.log('No more entries');
resolve(result);
}
};
});
const setAnEntry = (value) => {
const store = getObjectStore(DB_STORE_NAME, 'readwrite');
store.put(value);
};
const removeAnEntry = (value) => {
const store = getObjectStore(DB_STORE_NAME, 'readwrite');
store.remove(value);
};
return {
all: getAllEntries,
add: setAnEntry,
remove: removeAnEntry,
init: init
};
}
alert("Your browser doesn't support a stable version of IndexedDB. Dairy related features will not be available.");
return {};
})();
Dairy.init()
.then(
result => {
console.log('adding something in the diary');
console.log('... ', Dairy.add({ subject: 'hello', description: 'bluh bluh', date: Date() }));
console.log('show all: ', Dairy.all());
},
error => console.log(error)
);