IndexedDB — безлимитное хранение данных

от автора

Добрый день, уважаемое сообщество.
Для тех, кто не в курсе, что такое IndexedDB и с чем его едят можно, почитать здесь.

А мы идем далее.

Безлимит

В конторе в которой я работаю появилась необходимость использования индексированной локальной базы данных на стороне клиента и выбор сразу пал на IndexedDB.

Но как всегда есть одно «НО», это самое «НО» — ограничение размера БД на машине пользователя в размере 5 МБ, что отнюдь нас не устраивало. Так как данная технология планировалась использоваться в админке нашего проекта и все юзеры использовали в качестве дефолтного браузера Google Chrome, то было принято решение поиска обхода того самого ограничение через расширение-прокси. Перелопатив много инфы мы пришли к выводу, что ограничение на размер БД можно убрать использовав специальные флаги в манифесте нашего расширения:

"permissions": [         "unlimitedStorage",         "unlimited_storage"     ], 

Отправка сообщений сайт-расширение-сайт

Идем далее. С безлимитным хранением данных мы разобрались, но теперь возникла необходимость работать с той самой безлимитной БД непосредственно с самого сайта. Для этого использовалась отправка сообщений между сайтом и расширением (расширение выступило в роли прокси, между сайтом и безлимитной БД). Для этого в манифесте нашего расширения добавили следующие флаги:

"externally_connectable": {         "matches": [             "*://localhost/*", 	"ЗДЕСЬ_ДОБАВЛЯЕМ_РАЗРЕШЕННЫЕ_ШАБЛОНЫ_URL "         ]     } 

Выяснилось что валидными считаются URL вида: *://google.com/* and http://*.chromium.org/*, а, http:// * / *, * :/ / *. COM / не являются.
Больше информации о externally_connectable можете почитать здесь.

Идем далее.

Наступил этап написания того самого «моста» между сайтом и расширением для доступа к БД.
В качестве основной библиотеки для работы с IndexDB на стороне расширения была использована db.js, с которой вы можете ознакомиться тут.

Чтобы не изобретать велосипед, было принято решение использовать на стороне сайта синтаксис доступа который реализован в db.js.

Расширение

И так поехали, создаем background.js, который будем прослушивать входящие сообщения, и отвечать на них. Листинг кода привожу ниже:

var server;  chrome.runtime.onMessageExternal.addListener(     function (request, sender, sendResponse) {         var cmd = request.cmd,             params = request.params;         try {             switch (cmd) {                 case "open":                     db.open(params).done(function (s) {                         server = s;                         var exclude = "add close get query remove update".split(" ");                         var tables = new Array();                         for(var table in server){                             if(exclude.indexOf(table)==-1){                                 tables.push(table);                             }                         }                         sendResponse(tables);                     });                     break;                 case "close":                     server.close();                     sendResponse({});                     break;                 case "get":                     server[request.table].get(params).done(sendResponse)                     break;                 case "add":                     server[request.table].add(params).done(sendResponse);                     break;                 case "update":                     server[request.table].update(params).done(sendResponse);                     break;                 case "remove":                     server[request.table].remove(params).done(sendResponse);                     break;                 case "execute":                     var tmp_server = server[request.table];                     var query = tmp_server.query.apply(tmp_server, obj2arr(request.query));                     var flt;                     for (var i = 0; i < request.filters.length; i++) {                         flt = request.filters[i];                         if (flt.type == "filter") {                             flt.args = new Function("item", flt.args[0]);                         }                         query = query[flt.type].apply(query, obj2arr(flt.args));                     }                     query.execute().done(sendResponse);                     break;             }         } catch (error) {             if (error.name != "TypeError") {                 sendResponse({RUNTIME_ERROR: error});             }         }         return true;     }); 

Но тут нас ждал сюрприз, а именно на выполнении участка кода:

flt.args = new Function("item", flt.args[0]); 

получаем исключение:

Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' chrome-extension-resource:"..

Для разрешения данной проблемы добавим в манифест еще одну строку, которая разрешает выполнение пользовательского js на стороне расширения.

"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"

Также пришлось реализовать вспомогательную функцию перегона объекта в массив, для передачи его в качестве аргументов функции.

var obj2arr = function (obj) {     if (typeof obj == 'object') {         var tmp_args = new Array();         for (var k in obj) {             tmp_args.push(obj[k]);         }         return tmp_args;     } else {         return [obj];     } } 

Полный листинг manifest.json

{     "manifest_version": 2,      "name": "exDB",     "description": "This extension give proxy access to indexdb from page.",     "version": "1.0",      "background": {         "page": "background.html"     },     "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",     "externally_connectable": {         "matches": [             "*://localhost/*"         ]     },     "permissions": [         "unlimitedStorage",         "unlimited_storage"     ],     "icons": {         "16": "icons/icon_016.png",         "48": "icons/icon_048.png"     }  } 

Клиент

С расширением разобрались, теперь приступим к написанию клиент-библиотеки для работы с нашим прокси-расширением.
Первое, что необходимо, при отправке сообщения с клиента указать какому расширению мы хотим его послать, для этого, указываем его id:

chrome.runtime.sendMessage("ID_РАСШИРЕНИЯ", data, callback);

Полный листинг клиентской библиотеки:

(function (window, undefined) {     "use strict";     function exDB() {         var self = this;         this.extensionId = arguments[0] || "knpcnhfbafbjadcbeipdihdblfogiafm";         this.filterList = new Array();         this._table;         this._query;         self.sendMessage = function sendMessage(data, callback) {             chrome.runtime.sendMessage(self.extensionId, data, callback);         };          self.open = function (params, callback) {             self.sendMessage({"cmd": "open", "params": params}, function(r){                 var tn;                 for(var i=0;i< r.length;i++)                     tn = r[i];                     self.__defineGetter__(tn,function(){                         self._table = tn;                         return this;                     });                 callback();             });             return self;         };          self.close = function (callback) {             self.sendMessage({"cmd": "close", "params": {}}, callback);             return self;         }          self.table = function (name) {             self._table = name;             return self;         };          self.query = function () {             self._query = arguments;             return self;         };          self.execute = function (callback) {             self.sendMessage({"cmd": "execute", "table": self._table, "query": self._query, "filters": self.filterList}, function (result) {                 if (result && result.RUNTIME_ERROR) {                     console.error(result.RUNTIME_ERROR.message);                     result = null;                 }                 callback(result);             });             self._query = null;             self.filterList = [];         };          "add update remove get".split(" ").forEach(function (fn) {             self[fn] = function (item, callback) {                 self.sendMessage({"cmd": fn, "table": self._table, "params": item}, function (result) {                     if (result && result.RUNTIME_ERROR) {                         console.error(result.RUNTIME_ERROR.message);                         result = null;                     }                     callback(result);                 });                 return self;             }         });          "all only lowerBound upperBound bound filter desc distinct keys count".split(" ").forEach(function (fn) {             self[fn] = function () {                 self.filterList.push({type: fn, args: arguments});                 return self;             }         });      }      window.exDB = exDB; })(window, undefined); 

На данном этапе наш комплекс для работы с безлимитной indexDB готов. Ниже приведу примеры использования.

Подключение

    var db = new exDB();     db.open({         server: 'my-app',         version: 1,         schema: {             people: {                 key: { keyPath: 'id', autoIncrement: true },                 // Optionally add indexes                 indexes: {                     firstName: { },                     answer: { unique: true }                 }             }         }     }, function () {}); 
Добавление записи

db.table("people").add({  firstName: 'Aaron', lastName: 'Powell', answer: 142},function(r){ }); 
Обновление записи

 db.table("people").update({ id:1, firstName: 'Aaron', lastName: 'Powell', answer: 1242}, function (r) {});
Удаление записи по ID

db.table("people").remove(1,function(key){});
Получение записи по ID

db.table("people3").get(111,function(r){ 	console.log(r);  }); 
Выборки / Сортировки

db.people.query("firstName").only("Aaron2").execute(function(r){          console.log("GETTER",r); });  db.table("people").query("answer").all().desc().execute(function(r){          console.log("all",r);  }); db.table("people").query("answer").only(12642).count().execute(function(r){          console.log("only",r); }); db.table("people").query("answer").bound(20,45).execute(function(r){          console.log("bound",r); }); db.table("people").query("answer").lowerBound(50).keys().execute(function(r){          console.log("lowerBound",r); }); db.table("people").query("answer").upperBound(43).execute(function(r){          console.log("upperBound",r); });  db.table("people").query("answer").filter("return item.answer==42 && item.firstName=='Aaron'").execute(function(r){          console.log("filter",r); }); 

Вывод

На сегодняшний момент данное решение активно используется на одном из наших проектов, буду благодарен за конструктивную критику и предложения. Так как этом моя первая статья на хабре прошу сильно не судить.

С исходниками Вы можете ознакомится на github.

ссылка на оригинал статьи http://habrahabr.ru/post/198666/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *