Skip to content

Commit

Permalink
Full offline functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
CMEONE committed Mar 6, 2021
1 parent 77c7cec commit 518750d
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 58 deletions.
10 changes: 6 additions & 4 deletions example/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ tApp.configure({
forbidden: "#/403"
},
caching: {
maxBytes: 5 * 1000 * 1000, // around 5MB in bytes (5 MB * 1000 KB/MB * 1000 bytes/KB)
updateCache: 5 * 60 * 1000, // updates the cache every 5 minutes in milliseconds (5 minutes * 60 seconds/minute * 1000 seconds/millisecond)
backgroundPages: ["./views/index.html", "./views/about.html"],
backgroundPages: ["./", "./config.js", "/tApp.js", "./views/index.html", "./views/about.html"],
persistent: true
}
});
Expand Down Expand Up @@ -51,4 +49,8 @@ tApp.route("#/403", function(request) {
`);
});

tApp.start();
tApp.start().then(() => {
tApp.install().then(() => {
tApp.update();
})
});
2 changes: 1 addition & 1 deletion example/views/about.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ <h1>About</h1>
<li>Caching allows your tApp to be blazingly fast. You can set custom cache size and time limits in your code, and tApp will save loaded pages in the cache so that they load instantly the next time the user navigates to that section of the tApp.</li>
<li>This tApp utilizes persistent caching, so after the very first page load, all requests are redirected to the cache until the cache time expires.</li>
<li>Routes can be specified to automatically load and cache asynchronously in the background when the user first loads your tApp, making the next page loads instantaneous.</li>
<li>tApps can work offline, just like normal apps. Since all server-like routing and computation can be handled in the client through tApp, users can browse the tApp offline. This offline functionality is not fully implemented but will be integrated in a later release of the library.</li>
<li>tApps can work offline, just like normal apps. Since all server-like routing and computation can be handled in the client through tApp, users can load browse the tApp offline at a later time after only loading the tApp once.</li>
</ul>
112 changes: 112 additions & 0 deletions tApp-service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
var version = 'v16::';

self.addEventListener("install", function(event) {
event.waitUntil(() => {
self.skipWaiting();
});
});

self.addEventListener("fetch", function(event) {
let fetchResponse = (async () => {
return new Promise((resolve, reject) => {
if (event.request.method !== 'GET') {
// Handle other requests such as POST here
return;
}
let url = event.request.url.split("#")[0];
let requestInit = indexedDB.open("tAppCache", 5);
let db;
function myFetch(page) {
return new Promise((resolve, reject) => {
fetch(page).then((response) => {
resolve(response);
}).catch(() => {
resolve(new Response("", {
status: 500,
statusText: 'Service Unavailable',
headers: new Headers({
'Content-Type': 'text/html'
})
}));
});
});
}
requestInit.onupgradeneeded = function() {
db = requestInit.result;
if(!db.objectStoreNames.contains("cachedPages")) {
db.createObjectStore("cachedPages");
}
if(!db.objectStoreNames.contains("offlineStorage")) {
db.createObjectStore("offlineStorage");
}
}
requestInit.onsuccess = async function() {
db = requestInit.result;
function setCachedPage(fullPath, value) {
return new Promise((resolve, reject) => {
let request = db.transaction(["cachedPages"], "readwrite").objectStore("cachedPages").put(value, fullPath);
request.onerror = () => {
reject("tAppError: Persistent caching is not available in this browser.");
};
request.onsuccess = () => {
resolve(true);
}
});
}
function getCachedPage(fullPath) {
return new Promise((resolve, reject) => {
let request = db.transaction(["cachedPages"], "readwrite").objectStore("cachedPages").get(fullPath);
request.onerror = (err) => {
myFetch(url).then((response) => {
resolve(response);
});
};
request.onsuccess = () => {
myFetch(url).then((response) => {
if(response.status === 200) {
response.text().then((text) => {

setCachedPage(url, {
data: text,
cachedAt: new Date().getTime()
});
}).catch(() => {

});
}
});
if(request.result != null) {
resolve(new Response(request.result.data, {
status: 200,
statusText: 'OK',
headers: new Headers({
'Content-Type': 'text/html'
})
}));
} else {
myFetch(url).then((response) => {
resolve(response);
});
}
};
});
}
getCachedPage(url).then((response) => {
resolve(response);
});
};
requestInit.onerror = (err) => {
myFetch(url).then((response) => {
resolve(response);
});
}
});
})();
event.respondWith(fetchResponse);
});

self.addEventListener("activate", function(event) {
event.waitUntil(() => {

});
});
106 changes: 53 additions & 53 deletions tApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class tApp {
static database;
static currentHash = "/";
static get version() {
return "v0.5.1";
return "v0.6.0";
}
static configure(params) {
if(params == null) {
Expand Down Expand Up @@ -61,12 +61,6 @@ class tApp {
error: "tAppError: Invalid configure parameter, caching.backgroundPages is not of type Array."
}
}
if(params.caching != null && params.caching.maxBytes == null) {
params.caching.maxBytes = Infinity;
}
if(params.caching != null && params.caching.updateCache == null) {
params.caching.updateCache = Infinity;
}
if(params.caching != null && params.caching.backgroundPages == null) {
params.caching.backgroundPages = [];
}
Expand Down Expand Up @@ -199,27 +193,6 @@ class tApp {
});

}
static updateCacheSize() {
return new Promise(async (resolve, reject) => {
let cachedData = await tApp.getCachedPages();
let keys = Object.keys(cachedData);
let size = 0;
for(let i = 0; i < keys.length; i++) {
size += new Blob([keys[i]]).size;
size += new Blob([cachedData[keys[i]].data]).size;
}
while(size > tApp.config.caching.maxBytes) {
let num = Math.floor(Math.random() * keys.length);
if(num < keys.length) {
let removedPage = await tApp.removeCachedPage(keys[num]);
size -= new Blob([keys[num]]).size;
size -= new Blob([removedPage.data]).size;
}
}
tApp.cacheSize = size;
resolve();
});
}
static getOfflineData(key) {
return new Promise((resolve, reject) => {
let request = tApp.database.transaction(["offlineStorage"], "readwrite").objectStore("offlineStorage").get(key);
Expand Down Expand Up @@ -291,28 +264,30 @@ class tApp {
return new Promise(async (resolve, reject) => {
let fullPath = new URL(path, window.location.href).href;
let cachedPage = await tApp.getCachedPage(fullPath);
if(cachedPage == null || cachedPage.cachedAt + tApp.config.caching.updateCache < new Date().getTime()) {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = async () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
tApp.setCachedPage(fullPath, {
data: xhr.responseText,
cachedAt: new Date().getTime()
});
tApp.updateCacheSize();
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = async () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
tApp.setCachedPage(fullPath, {
data: xhr.responseText,
cachedAt: new Date().getTime()
});
if(cachedPage == null) {
resolve(xhr.responseText);
} else {
}
} else {
if(cachedPage == null) {
reject({
error: "GET " + xhr.responseURL + " " + xhr.status + " (" + xhr.statusText + ")",
errorCode: xhr.status
});
}
}
}
xhr.open("GET", path, true);
xhr.send(null);
} else {
}
xhr.open("GET", path, true);
xhr.send(null);
if(cachedPage != null) {
resolve(cachedPage.data);
}
});
Expand Down Expand Up @@ -365,15 +340,6 @@ class tApp {
for(let i = 0; i < tApp.config.caching.backgroundPages.length; i++) {
tApp.get(tApp.config.caching.backgroundPages[i]);
}
if(tApp.config.caching.updateCache < 60000) {
setTimeout(() => {
tApp.loadBackgroundPages();
}, tApp.config.caching.updateCache);
} else {
setTimeout(() => {
tApp.loadBackgroundPages();
}, 60000);
}
}
static start() {
return new Promise((resolve, reject) => {
Expand All @@ -398,15 +364,16 @@ class tApp {
}, false);
tApp.updatePage(window.location.hash);
tApp.loadBackgroundPages();
resolve(true);
};
request.onsuccess = async (event) => {
tApp.database = request.result;
await tApp.updateCacheSize();
window.addEventListener("hashchange", () => {
tApp.updatePage(window.location.hash);
}, false);
tApp.updatePage(window.location.hash);
tApp.loadBackgroundPages();
resolve(true);

};
request.onupgradeneeded = (event) => {
Expand All @@ -424,11 +391,44 @@ class tApp {
}, false);
tApp.updatePage(window.location.hash);
tApp.loadBackgroundPages();
resolve(true);
}
resolve(true);
} else {
reject("tAppError: tApp has already started.");
}
});
}
static install(pathToServiceWorker = '/tApp-service-worker.js') {
return new Promise((resolve, reject) => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(pathToServiceWorker).then(function() {
resolve(true);
}, function() {
reject("tAppError: Unable to install full offline functionality, an error occurred.");
});
} else {
reject("tAppError: Full offline functionality is not supported for this website. This issue can occur in unsupported browsers (such as IE) or in insecure contexts (HTTPS/SSL is required for full offline functionality).");
}
});
}
static uninstall() {
return new Promise((resolve, reject) => {
navigator.serviceWorker.getRegistrations().then(function(registrations) {
for(let registration of registrations) {
registration.unregister()
}
resolve(true);
});
});
}
static update() {
return new Promise((resolve, reject) => {
navigator.serviceWorker.getRegistrations().then(function(registrations) {
for(let registration of registrations) {
registration.update()
}
resolve(true);
});
});
}
}

0 comments on commit 518750d

Please sign in to comment.