mirror of
https://github.com/futurepress/epub.js.git
synced 2025-10-05 15:32:55 +02:00
370 lines
7.9 KiB
JavaScript
370 lines
7.9 KiB
JavaScript
import {defer, isXml, parse} from "./utils/core";
|
|
import httpRequest from "./utils/request";
|
|
import mime from "../libs/mime/mime";
|
|
import Path from "./utils/path";
|
|
import EventEmitter from "event-emitter";
|
|
|
|
/**
|
|
* Handles saving and requesting files from local storage
|
|
* @class
|
|
*/
|
|
class Store {
|
|
|
|
constructor(name, requester, resolver) {
|
|
this.urlCache = {};
|
|
|
|
this.requester = requester || httpRequest;
|
|
this.resolver = resolver;
|
|
|
|
this.online = true;
|
|
|
|
this.checkRequirements();
|
|
|
|
// This should be the name of the application for modals
|
|
localforage.config({
|
|
name: name || 'epubjs'
|
|
});
|
|
|
|
this.addListeners();
|
|
}
|
|
|
|
/**
|
|
* Checks to see if localForage exists in global namspace,
|
|
* Requires localForage if it isn't there
|
|
* @private
|
|
*/
|
|
checkRequirements(){
|
|
try {
|
|
if (typeof localForage === "undefined") {
|
|
let localForage = require("localforage");
|
|
}
|
|
} catch (e) {
|
|
throw new Error("localForage lib not loaded");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add online and offline event listeners
|
|
* @private
|
|
*/
|
|
addListeners() {
|
|
window.addEventListener('online', this.status.bind(this));
|
|
window.addEventListener('offline', this.status.bind(this));
|
|
}
|
|
|
|
/**
|
|
* Remove online and offline event listeners
|
|
* @private
|
|
*/
|
|
removeListeners() {
|
|
window.removeEventListener('online', this.status.bind(this));
|
|
window.removeEventListener('offline', this.status.bind(this));
|
|
}
|
|
|
|
/**
|
|
* Update the online / offline status
|
|
* @private
|
|
*/
|
|
status(event) {
|
|
let online = navigator.onLine;
|
|
this.online = online;
|
|
if (online) {
|
|
this.emit("online", this);
|
|
} else {
|
|
this.emit("offline", this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add all of a book resources to the store
|
|
* @param {Resources} resources book resources
|
|
* @param {boolean} [force] force resaving resources
|
|
* @return {Promise<object>} store objects
|
|
*/
|
|
add(resources, force) {
|
|
let mapped = resources.resources.map((item) => {
|
|
let { href } = item;
|
|
let url = this.resolver(href);
|
|
let encodedUrl = window.encodeURIComponent(url);
|
|
|
|
return localforage.getItem(encodedUrl).then((item) => {
|
|
if (!item || force) {
|
|
return this.requester(url, "binary")
|
|
.then((data) => {
|
|
return localforage.setItem(encodedUrl, data);
|
|
});
|
|
} else {
|
|
return item;
|
|
}
|
|
});
|
|
|
|
});
|
|
return Promise.all(mapped);
|
|
}
|
|
|
|
/**
|
|
* Put binary data from a url to storage
|
|
* @param {string} url a url to request from storage
|
|
* @return {Promise<Blob>}
|
|
*/
|
|
put(url) {
|
|
let encodedUrl = window.encodeURIComponent(url);
|
|
|
|
return localforage.getItem(encodedUrl).then((result) => {
|
|
if (!result) {
|
|
return this.requester(url, "binary", withCredentials, headers).then((data) => {
|
|
return localforage.setItem(encodedUrl, data);
|
|
});
|
|
}
|
|
return result;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Request a url
|
|
* @param {string} url a url to request from storage
|
|
* @param {string} [type] specify the type of the returned result
|
|
* @param {boolean} [withCredentials]
|
|
* @param {object} [headers]
|
|
* @return {Promise<Blob | string | JSON | Document | XMLDocument>}
|
|
*/
|
|
request(url, type, withCredentials, headers){
|
|
if (this.online) {
|
|
// From network
|
|
return this.requester(url, type, withCredentials, headers).then((data) => {
|
|
// save to store if not present
|
|
this.put(url);
|
|
return data;
|
|
})
|
|
} else {
|
|
// From store
|
|
return this.retrieve(url, type);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Request a url from storage
|
|
* @param {string} url a url to request from storage
|
|
* @param {string} [type] specify the type of the returned result
|
|
* @return {Promise<Blob | string | JSON | Document | XMLDocument>}
|
|
*/
|
|
retrieve(url, type) {
|
|
var deferred = new defer();
|
|
var response;
|
|
var path = new Path(url);
|
|
|
|
// If type isn't set, determine it from the file extension
|
|
if(!type) {
|
|
type = path.extension;
|
|
}
|
|
|
|
if(type == "blob"){
|
|
response = this.getBlob(url);
|
|
} else {
|
|
response = this.getText(url);
|
|
}
|
|
|
|
|
|
return response.then((r) => {
|
|
var deferred = new defer();
|
|
var result;
|
|
if (r) {
|
|
result = this.handleResponse(r, type);
|
|
deferred.resolve(result);
|
|
} else {
|
|
deferred.reject({
|
|
message : "File not found in storage: " + url,
|
|
stack : new Error().stack
|
|
});
|
|
}
|
|
return deferred.promise;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle the response from request
|
|
* @private
|
|
* @param {any} response
|
|
* @param {string} [type]
|
|
* @return {any} the parsed result
|
|
*/
|
|
handleResponse(response, type){
|
|
var r;
|
|
|
|
if(type == "json") {
|
|
r = JSON.parse(response);
|
|
}
|
|
else
|
|
if(isXml(type)) {
|
|
r = parse(response, "text/xml");
|
|
}
|
|
else
|
|
if(type == "xhtml") {
|
|
r = parse(response, "application/xhtml+xml");
|
|
}
|
|
else
|
|
if(type == "html" || type == "htm") {
|
|
r = parse(response, "text/html");
|
|
} else {
|
|
r = response;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* Get a Blob from Storage by Url
|
|
* @param {string} url
|
|
* @param {string} [mimeType]
|
|
* @return {Blob}
|
|
*/
|
|
getBlob(url, mimeType){
|
|
let encodedUrl = window.encodeURIComponent(url);
|
|
|
|
return localforage.getItem(encodedUrl).then(function(uint8array) {
|
|
if(!uint8array) return;
|
|
|
|
mimeType = mimeType || mime.lookup(url);
|
|
|
|
return new Blob([uint8array], {type : mimeType});
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Get Text from Storage by Url
|
|
* @param {string} url
|
|
* @param {string} [mimeType]
|
|
* @return {string}
|
|
*/
|
|
getText(url, mimeType){
|
|
let encodedUrl = window.encodeURIComponent(url);
|
|
|
|
mimeType = mimeType || mime.lookup(url);
|
|
|
|
return localforage.getItem(encodedUrl).then(function(uint8array) {
|
|
var deferred = new defer();
|
|
var reader = new FileReader();
|
|
var blob = new Blob([uint8array], {type : mimeType});
|
|
|
|
if(!blob) return;
|
|
|
|
reader.addEventListener("loadend", () => {
|
|
deferred.resolve(reader.result);
|
|
});
|
|
|
|
reader.readAsText(blob, mimeType);
|
|
|
|
return deferred.promise;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get a base64 encoded result from Storage by Url
|
|
* @param {string} url
|
|
* @param {string} [mimeType]
|
|
* @return {string} base64 encoded
|
|
*/
|
|
getBase64(url, mimeType){
|
|
let encodedUrl = window.encodeURIComponent(url);
|
|
|
|
mimeType = mimeType || mime.lookup(url);
|
|
|
|
return localforage.getItem(encodedUrl).then((uint8array) => {
|
|
var deferred = new defer();
|
|
var reader = new FileReader();
|
|
var blob = new Blob([uint8array], {type : mimeType});
|
|
|
|
if(!blob) return;
|
|
|
|
reader.addEventListener("loadend", () => {
|
|
deferred.resolve(reader.result);
|
|
});
|
|
reader.readAsDataURL(blob, mimeType);
|
|
|
|
return deferred.promise;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a Url from a stored item
|
|
* @param {string} url
|
|
* @param {object} [options.base64] use base64 encoding or blob url
|
|
* @return {Promise} url promise with Url string
|
|
*/
|
|
createUrl(url, options){
|
|
var deferred = new defer();
|
|
var _URL = window.URL || window.webkitURL || window.mozURL;
|
|
var tempUrl;
|
|
var response;
|
|
var useBase64 = options && options.base64;
|
|
|
|
if(url in this.urlCache) {
|
|
deferred.resolve(this.urlCache[url]);
|
|
return deferred.promise;
|
|
}
|
|
|
|
if (useBase64) {
|
|
response = this.getBase64(url);
|
|
|
|
if (response) {
|
|
response.then(function(tempUrl) {
|
|
|
|
this.urlCache[url] = tempUrl;
|
|
deferred.resolve(tempUrl);
|
|
|
|
}.bind(this));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
response = this.getBlob(url);
|
|
|
|
if (response) {
|
|
response.then(function(blob) {
|
|
|
|
tempUrl = _URL.createObjectURL(blob);
|
|
this.urlCache[url] = tempUrl;
|
|
deferred.resolve(tempUrl);
|
|
|
|
}.bind(this));
|
|
|
|
}
|
|
}
|
|
|
|
|
|
if (!response) {
|
|
deferred.reject({
|
|
message : "File not found in storage: " + url,
|
|
stack : new Error().stack
|
|
});
|
|
}
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Revoke Temp Url for a achive item
|
|
* @param {string} url url of the item in the store
|
|
*/
|
|
revokeUrl(url){
|
|
var _URL = window.URL || window.webkitURL || window.mozURL;
|
|
var fromCache = this.urlCache[url];
|
|
if(fromCache) _URL.revokeObjectURL(fromCache);
|
|
}
|
|
|
|
destroy() {
|
|
var _URL = window.URL || window.webkitURL || window.mozURL;
|
|
for (let fromCache in this.urlCache) {
|
|
_URL.revokeObjectURL(fromCache);
|
|
}
|
|
this.urlCache = {};
|
|
this.removeListeners();
|
|
}
|
|
}
|
|
|
|
EventEmitter(Store.prototype);
|
|
|
|
export default Store;
|