forked from kiwix/kiwix-js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
service-worker.js
185 lines (165 loc) · 7.86 KB
/
service-worker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/**
* service-worker.js : Service Worker implementation,
* in order to capture the HTTP requests made by an article, and respond with the
* corresponding content, coming from the archive
*
* Copyright 2015 Mossroy and contributors
* License GPL v3:
*
* This file is part of Kiwix.
*
* Kiwix is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Kiwix is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
*/
'use strict';
self.addEventListener('install', function(event) {
event.waitUntil(self.skipWaiting());
console.log("ServiceWorker installed");
});
self.addEventListener('activate', function(event) {
// "Claiming" the ServiceWorker is necessary to make it work right away,
// without the need to reload the page.
// See https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim
event.waitUntil(self.clients.claim());
console.log("ServiceWorker activated");
});
var regexpRemoveUrlParameters = new RegExp(/([^\?]+)\?.*$/);
// This function is duplicated from uiUtil.js
// because using requirejs would force to add the 'fetch' event listener
// after the initial evaluation of this script, which is not supported any more
// in recent versions of the browsers.
// Cf https://bugzilla.mozilla.org/show_bug.cgi?id=1181127
// TODO : find a way to avoid this duplication
function removeUrlParameters(url) {
if (regexpRemoveUrlParameters.test(url)) {
return regexpRemoveUrlParameters.exec(url)[1];
} else {
return url;
}
}
console.log("ServiceWorker startup");
var outgoingMessagePort = null;
var fetchCaptureEnabled = false;
self.addEventListener('fetch', fetchEventListener);
console.log('fetchEventListener set');
self.addEventListener('message', function (event) {
if (event.data.action === 'init') {
console.log('Init message received', event.data);
outgoingMessagePort = event.ports[0];
console.log('outgoingMessagePort initialized', outgoingMessagePort);
fetchCaptureEnabled = true;
console.log('fetchEventListener enabled');
}
if (event.data.action === 'disable') {
console.log('Disable message received');
outgoingMessagePort = null;
console.log('outgoingMessagePort deleted');
fetchCaptureEnabled = false;
console.log('fetchEventListener disabled');
}
});
// TODO : this way to recognize content types is temporary
// It must be replaced by reading the actual MIME-Type from the backend
var regexpJPEG = new RegExp(/\.jpe?g$/i);
var regexpPNG = new RegExp(/\.png$/i);
var regexpJS = new RegExp(/\.js/i);
var regexpCSS = new RegExp(/\.css$/i);
// Pattern for ZIM file namespace - see http://www.openzim.org/wiki/ZIM_file_format#Namespaces
var regexpZIMUrlWithNamespace = new RegExp(/(?:^|\/)([-ABIJMUVWX])\/(.+)/);
var regexpDummyArticle = new RegExp(/dummyArticle\.html$/);
function fetchEventListener(event) {
if (fetchCaptureEnabled) {
console.log('ServiceWorker handling fetch event for : ' + event.request.url);
// TODO handle the dummy article more properly
if (regexpZIMUrlWithNamespace.test(event.request.url)
&& !regexpDummyArticle.test(event.request.url)) {
console.log('Asking app.js for a content', event.request.url);
event.respondWith(new Promise(function(resolve, reject) {
var nameSpace;
var title;
var titleWithNameSpace;
var contentType;
var regexpResult = regexpZIMUrlWithNamespace.exec(event.request.url);
nameSpace = regexpResult[1];
title = regexpResult[2];
// The namespace defines the type of content. See http://www.openzim.org/wiki/ZIM_file_format#Namespaces
// TODO : read the contentType from the ZIM file instead of hard-coding it here
if (nameSpace === 'A') {
console.log("It's an article : " + title);
contentType = 'text/html';
}
else if (nameSpace === 'I' || nameSpace === 'J') {
console.log("It's an image : " + title);
if (regexpJPEG.test(title)) {
contentType = 'image/jpeg';
}
else if (regexpPNG.test(title)) {
contentType = 'image/png';
}
}
else if (nameSpace === '-') {
console.log("It's a layout dependency : " + title);
if (regexpJS.test(title)) {
contentType = 'text/javascript';
var responseInit = {
status: 200,
statusText: 'OK',
headers: {
'Content-Type': contentType
}
};
var httpResponse = new Response(';', responseInit);
// TODO : temporary before the backend actually sends a proper content
resolve(httpResponse);
return;
}
else if (regexpCSS.test(title)) {
contentType = 'text/css';
}
}
// We need to remove the potential parameters in the URL
title = removeUrlParameters(decodeURIComponent(title));
titleWithNameSpace = nameSpace + '/' + title;
// Let's instanciate a new messageChannel, to allow app.s to give us the content
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
if (event.data.action === 'giveContent') {
console.log('content message received for ' + titleWithNameSpace, event.data);
var responseInit = {
status: 200,
statusText: 'OK',
headers: {
'Content-Type': contentType
}
};
var httpResponse = new Response(event.data.content, responseInit);
console.log('ServiceWorker responding to the HTTP request for ' + titleWithNameSpace + ' (size=' + event.data.content.length + ' octets)' , httpResponse);
resolve(httpResponse);
}
else if (event.data.action === 'sendRedirect') {
resolve(Response.redirect(event.data.redirectUrl));
}
else {
console.log('Invalid message received from app.js for ' + titleWithNameSpace, event.data);
reject(event.data);
}
};
console.log('Eventlistener added to listen for an answer to ' + titleWithNameSpace);
outgoingMessagePort.postMessage({'action': 'askForContent', 'title': titleWithNameSpace}, [messageChannel.port2]);
console.log('Message sent to app.js through outgoingMessagePort');
}));
}
// If event.respondWith() isn't called because this wasn't a request that we want to handle,
// then the default request/response behavior will automatically be used.
}
}