@cocreate/pwa 1.6.27 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/CoCreate.config.js +2 -2
- package/LICENSE +683 -21
- package/demo/index.html +4 -3
- package/demo/{manifest.json → manifest.webmanifest} +87 -91
- package/demo/offline.html +1 -1
- package/demo/pages/test3.html +0 -1
- package/docs/index.html +9 -9
- package/package.json +7 -5
- package/src/cache.js +5 -5
- package/src/index.js +48 -18
- package/src/service-worker.js +197 -0
- package/webpack.config.js +80 -73
- package/demo/sw.js +0 -112
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2023 CoCreate and Contributors.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
********************************************************************************/
|
|
17
|
+
|
|
18
|
+
// Commercial Licensing Information:
|
|
19
|
+
// For commercial use of this software without the copyleft provisions of the AGPLv3,
|
|
20
|
+
// you must obtain a commercial license from CoCreate LLC.
|
|
21
|
+
// For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
|
|
22
|
+
|
|
23
|
+
const cacheName = "dynamic-v2";
|
|
24
|
+
let organization_id = ""
|
|
25
|
+
let storage = true
|
|
26
|
+
|
|
27
|
+
const queryString = self.location.search;
|
|
28
|
+
const queryParams = new URLSearchParams(queryString);
|
|
29
|
+
let cacheType = queryParams.get('cache');
|
|
30
|
+
|
|
31
|
+
self.addEventListener("install", (e) => {
|
|
32
|
+
console.log('Service Worker Installing')
|
|
33
|
+
self.skipWaiting();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
self.addEventListener("activate", async (e) => {
|
|
37
|
+
e.waitUntil(clients.claim());
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
self.addEventListener("fetch", async (e) => {
|
|
41
|
+
e.respondWith(
|
|
42
|
+
caches
|
|
43
|
+
.match(e.request)
|
|
44
|
+
.then(async (cacheResponse) => {
|
|
45
|
+
if (!(e.request.url.indexOf('http') === 0) || e.request.method === 'POST') return;
|
|
46
|
+
|
|
47
|
+
if (!storage) {
|
|
48
|
+
const url = new URL(e.request.url);
|
|
49
|
+
const hostname = url.host;
|
|
50
|
+
const pathname = url.pathname;
|
|
51
|
+
|
|
52
|
+
let file = await readFile({
|
|
53
|
+
database: '5ff747727005da1c272740ab',
|
|
54
|
+
array: 'files',
|
|
55
|
+
filter: {
|
|
56
|
+
path: hostname,
|
|
57
|
+
host: '*'
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (file) {
|
|
62
|
+
// console.log('Host:', hostname);
|
|
63
|
+
console.log('Pathname:', pathname);
|
|
64
|
+
console.log('file', file)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (file && file.object && file.object[0]) {
|
|
68
|
+
file = file.object[0]
|
|
69
|
+
|
|
70
|
+
const modifiedRequest = new Request(e.request, {
|
|
71
|
+
headers: new Headers({
|
|
72
|
+
'File-Content': file.src,
|
|
73
|
+
'Content-Type': file['content-type']
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
e.request = modifiedRequest
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!navigator.onLine && !!cacheResponse && cacheType !== 'false')
|
|
81
|
+
return cacheResponse;
|
|
82
|
+
else {
|
|
83
|
+
fetch(e.request).then((networkResponse) => {
|
|
84
|
+
if (!organization_id)
|
|
85
|
+
organization_id = networkResponse.headers.get('organization')
|
|
86
|
+
|
|
87
|
+
storage = networkResponse.headers.get('storage')
|
|
88
|
+
if (cacheType && cacheType !== 'false') {
|
|
89
|
+
caches.open(cacheName).then((cache) => {
|
|
90
|
+
if (networkResponse.status !== 206 && networkResponse.status !== 502) {
|
|
91
|
+
cache.put(e.request, networkResponse);
|
|
92
|
+
if (cacheType === 'reload' || cacheType === 'prompt') {
|
|
93
|
+
const networkModified = networkResponse.headers.get('last-modified');
|
|
94
|
+
const cacheModified = cacheResponse.headers.get('last-modified');
|
|
95
|
+
if (networkModified !== cacheModified) {
|
|
96
|
+
self.clients.matchAll().then((clients) => {
|
|
97
|
+
clients.forEach((client) => {
|
|
98
|
+
client.postMessage({ action: 'cacheType', cacheType }); // Send a custom message
|
|
99
|
+
console.log(`file ${cacheType} has been triggered`)
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}).catch(() => {
|
|
106
|
+
|
|
107
|
+
});
|
|
108
|
+
if (!cacheResponse || cacheType === 'false' || cacheType === 'offline')
|
|
109
|
+
return networkResponse.clone();
|
|
110
|
+
}
|
|
111
|
+
}).catch(() => {
|
|
112
|
+
return caches.match('./offline.html');
|
|
113
|
+
})
|
|
114
|
+
if (!!cacheResponse && cacheType !== 'false' && cacheType !== 'offline')
|
|
115
|
+
return cacheResponse;
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
.catch(function () {
|
|
119
|
+
console.log('Fetch failed retuned offline page! ')
|
|
120
|
+
return caches.match('./offline.html');
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
function readFile(data) {
|
|
127
|
+
return new Promise((resolve) => {
|
|
128
|
+
const request = indexedDB.open(data.database);
|
|
129
|
+
|
|
130
|
+
request.onsuccess = function () {
|
|
131
|
+
const db = request.result
|
|
132
|
+
|
|
133
|
+
const transaction = db.transaction(data.array, "readonly");
|
|
134
|
+
const objectStore = transaction.objectStore(data.array);
|
|
135
|
+
const cursorRequest = objectStore.openCursor();
|
|
136
|
+
|
|
137
|
+
cursorRequest.onsuccess = function () {
|
|
138
|
+
const cursor = cursorRequest.result;
|
|
139
|
+
if (cursor) {
|
|
140
|
+
const file = cursor.value;
|
|
141
|
+
if (!file || !file.src || !file.path || !file.host)
|
|
142
|
+
cursor.continue();
|
|
143
|
+
else if (file.path !== data.filter.path || !file.host.includes(data.filter.host))
|
|
144
|
+
cursor.continue();
|
|
145
|
+
else {
|
|
146
|
+
db.close()
|
|
147
|
+
return resolve(file);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
// Resolve the Promise when cursor is finished
|
|
151
|
+
db.close()
|
|
152
|
+
resolve();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
cursorRequest.onerror = function () {
|
|
157
|
+
console.error("Cursor error:", cursorRequest.error);
|
|
158
|
+
db.close()
|
|
159
|
+
resolve();
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
request.onerror = function (event) {
|
|
165
|
+
reject(event.target.error);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
self.addEventListener('message', function (event) {
|
|
172
|
+
if (event.data === 'getOrganization')
|
|
173
|
+
event.source.postMessage(organization_id);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// self.addEventListener('backgroundfetchsuccess', (event) => {
|
|
177
|
+
// const bgFetch = event.registration;
|
|
178
|
+
|
|
179
|
+
// event.waitUntil(async function() {
|
|
180
|
+
// // Create/open a cache.
|
|
181
|
+
// const cache = await caches.open(cacheName);
|
|
182
|
+
// // Get all the records.
|
|
183
|
+
// const records = await bgFetch.matchAll();
|
|
184
|
+
// // Copy each request/response across.
|
|
185
|
+
// const promises = records.map(async (record) => {
|
|
186
|
+
// const response = await record.responseReady;
|
|
187
|
+
// console.log('putting ')
|
|
188
|
+
// await cache.put(record.request, response);
|
|
189
|
+
// });
|
|
190
|
+
|
|
191
|
+
// // Wait for the copying to complete
|
|
192
|
+
// await Promise.all(promises);
|
|
193
|
+
|
|
194
|
+
// // Update the progress notification.
|
|
195
|
+
// // event.updateUI({ title: 'Episode 5 ready to listen!' });
|
|
196
|
+
// }());
|
|
197
|
+
// });
|
package/webpack.config.js
CHANGED
|
@@ -1,84 +1,91 @@
|
|
|
1
1
|
const path = require("path")
|
|
2
2
|
const TerserPlugin = require("terser-webpack-plugin")
|
|
3
3
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
|
|
4
|
-
let isProduction = process.env.NODE_ENV === "production"
|
|
5
4
|
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
|
|
6
5
|
|
|
7
|
-
module.exports = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
output: {
|
|
12
|
-
path: path.resolve(__dirname, "dist"),
|
|
13
|
-
filename: isProduction ? "[name].min.js" : "[name].js",
|
|
14
|
-
libraryTarget: "umd",
|
|
15
|
-
libraryExport: "default",
|
|
16
|
-
library: ["CoCreate", "pwa"],
|
|
17
|
-
globalObject: "this",
|
|
18
|
-
},
|
|
6
|
+
module.exports = (env, argv) => {
|
|
7
|
+
let isProduction = false
|
|
8
|
+
if (argv.mode === 'production')
|
|
9
|
+
isProduction = true
|
|
19
10
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}),
|
|
25
|
-
],
|
|
26
|
-
// Default mode for Webpack is production.
|
|
27
|
-
// Depending on mode Webpack will apply different things
|
|
28
|
-
// on final bundle. For now we don't need production's JavaScript
|
|
29
|
-
// minifying and other thing so let's set mode to development
|
|
30
|
-
mode: isProduction ? "production" : "development",
|
|
31
|
-
module: {
|
|
32
|
-
rules: [
|
|
33
|
-
{
|
|
34
|
-
test: /.js$/,
|
|
35
|
-
exclude: /(node_modules)/,
|
|
36
|
-
use: {
|
|
37
|
-
loader: "babel-loader",
|
|
38
|
-
options: {
|
|
39
|
-
plugins: ["@babel/plugin-transform-modules-commonjs"],
|
|
40
|
-
},
|
|
11
|
+
const config = {
|
|
12
|
+
entry: {
|
|
13
|
+
"CoCreate-pwa": "./src/index.js",
|
|
14
|
+
"service-worker": "./src/service-worker.js",
|
|
41
15
|
},
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
16
|
+
output: {
|
|
17
|
+
path: path.resolve(__dirname, "dist"),
|
|
18
|
+
filename: isProduction ? "[name].min.js" : "[name].js",
|
|
19
|
+
libraryTarget: "umd",
|
|
20
|
+
libraryExport: "default",
|
|
21
|
+
library: ["CoCreate", "pwa"],
|
|
22
|
+
globalObject: "this",
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
plugins: [
|
|
26
|
+
new CleanWebpackPlugin(),
|
|
27
|
+
new MiniCssExtractPlugin({
|
|
28
|
+
filename: "[name].css",
|
|
29
|
+
}),
|
|
48
30
|
],
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
31
|
+
// Default mode for Webpack is production.
|
|
32
|
+
// Depending on mode Webpack will apply different things
|
|
33
|
+
// on final bundle. For now we don't need production's JavaScript
|
|
34
|
+
// minifying and other thing so let's set mode to development
|
|
35
|
+
mode: isProduction ? "production" : "development",
|
|
36
|
+
module: {
|
|
37
|
+
rules: [
|
|
38
|
+
{
|
|
39
|
+
test: /.js$/,
|
|
40
|
+
exclude: /(node_modules)/,
|
|
41
|
+
use: {
|
|
42
|
+
loader: "babel-loader",
|
|
43
|
+
options: {
|
|
44
|
+
plugins: ["@babel/plugin-transform-modules-commonjs"],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
test: /.css$/i,
|
|
50
|
+
use: [
|
|
51
|
+
{ loader: "style-loader", options: { injectType: "linkTag" } },
|
|
52
|
+
"file-loader",
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
// add source map
|
|
59
|
+
...(isProduction ? {} : { devtool: "eval-source-map" }),
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
61
|
+
optimization: {
|
|
62
|
+
minimize: true,
|
|
63
|
+
minimizer: [
|
|
64
|
+
new TerserPlugin({
|
|
65
|
+
extractComments: true,
|
|
66
|
+
// cache: true,
|
|
67
|
+
parallel: true,
|
|
68
|
+
// sourceMap: true, // Must be set to true if using source-maps in production
|
|
69
|
+
terserOptions: {
|
|
70
|
+
// https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
|
|
71
|
+
// extractComments: 'all',
|
|
72
|
+
compress: {
|
|
73
|
+
drop_console: true,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
78
|
+
splitChunks: {
|
|
79
|
+
chunks: "all",
|
|
80
|
+
minSize: 200,
|
|
81
|
+
// maxSize: 99999,
|
|
82
|
+
//minChunks: 1,
|
|
78
83
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
84
|
+
cacheGroups: {
|
|
85
|
+
defaultVendors: false,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
return config
|
|
91
|
+
}
|
package/demo/sw.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
const CACHE_STATIC_NAME = "static-v1";
|
|
2
|
-
const CACHE_DYNAMIC_NAME = "dynamic-v2";
|
|
3
|
-
const CACHE_INMUTABLE_NAME = "inmutable-v1";
|
|
4
|
-
const CACHE_TEMP = 'temp-cache';
|
|
5
|
-
const CACHE_DYNAMIC_LIMIT = 50;
|
|
6
|
-
const fetchedMap = []
|
|
7
|
-
const cacheType = 'cache'
|
|
8
|
-
const requestMap = new Map()
|
|
9
|
-
|
|
10
|
-
function deleteCache(key) {
|
|
11
|
-
return caches.delete(key);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
self.addEventListener("install", (e) => {
|
|
15
|
-
console.log('Service Worker Installing')
|
|
16
|
-
|
|
17
|
-
self.skipWaiting();
|
|
18
|
-
// caches.open(CACHE_DYNAMIC_NAME).then((cache) => {
|
|
19
|
-
// cache.keys().then((keys) => {
|
|
20
|
-
// keys.forEach(key => {
|
|
21
|
-
// fetchedMap.push(key.url)
|
|
22
|
-
// })
|
|
23
|
-
// deleteCache()
|
|
24
|
-
// })
|
|
25
|
-
// })
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
self.addEventListener("activate", async (e) => {
|
|
29
|
-
// console.log('sw activate Event!')
|
|
30
|
-
// const bgFetch = await self.registration.backgroundFetch.fetch(new Date().toISOString(), fetchedMap);
|
|
31
|
-
// console.log('backgfetch', bgFetch)
|
|
32
|
-
|
|
33
|
-
// e.waitUntil(updateCache())
|
|
34
|
-
// updateCache()
|
|
35
|
-
e.waitUntil(clients.claim());
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
self.addEventListener("fetch", (e) => {
|
|
39
|
-
if (!(e.request.url.indexOf('http') === 0)) return;
|
|
40
|
-
e.respondWith(
|
|
41
|
-
caches
|
|
42
|
-
.match(e.request)
|
|
43
|
-
.then((cachesObj) => {
|
|
44
|
-
if (!navigator.onLine && !!cachesObj)
|
|
45
|
-
return cachesObj;
|
|
46
|
-
|
|
47
|
-
if (cacheType == 'cache' && !!cachesObj) {
|
|
48
|
-
// console.log('returned from cache')
|
|
49
|
-
requestMap.set(cachesObj, '')
|
|
50
|
-
fetch(e.request).then((newResp) => {
|
|
51
|
-
caches.open(CACHE_DYNAMIC_NAME).then((cache) => {
|
|
52
|
-
cache.put(e.request, newResp);
|
|
53
|
-
// cleanCache(CACHE_DYNAMIC_NAME, CACHE_DYNAMIC_LIMIT);
|
|
54
|
-
}).catch(() => {
|
|
55
|
-
|
|
56
|
-
});
|
|
57
|
-
}).catch(() => {
|
|
58
|
-
return caches.match('./offline.html');
|
|
59
|
-
})
|
|
60
|
-
return cachesObj;
|
|
61
|
-
} else {
|
|
62
|
-
return fetch(e.request).then((newResp) => {
|
|
63
|
-
caches.open(CACHE_DYNAMIC_NAME).then((cache) => {
|
|
64
|
-
cache.put(e.request, newResp);
|
|
65
|
-
// cleanCache(CACHE_DYNAMIC_NAME, CACHE_DYNAMIC_LIMIT);
|
|
66
|
-
}).catch(() => {});
|
|
67
|
-
return newResp.clone();
|
|
68
|
-
})
|
|
69
|
-
.catch(() => {
|
|
70
|
-
return caches.match('./offline.html');
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
.catch(function() {
|
|
75
|
-
console.log('Fetch failed retuned offline page! ')
|
|
76
|
-
return caches.match('./offline.html');
|
|
77
|
-
})
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
async function updateCache(){
|
|
82
|
-
console.log('updateCache!')
|
|
83
|
-
caches.open(CACHE_DYNAMIC_NAME)
|
|
84
|
-
.then((cache) => {
|
|
85
|
-
console.log('added', fetchedMap)
|
|
86
|
-
cache.addAll(fetchedMap)
|
|
87
|
-
}
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
self.addEventListener('backgroundfetchsuccess', (event) => {
|
|
92
|
-
const bgFetch = event.registration;
|
|
93
|
-
|
|
94
|
-
event.waitUntil(async function() {
|
|
95
|
-
// Create/open a cache.
|
|
96
|
-
const cache = await caches.open(CACHE_DYNAMIC_NAME);
|
|
97
|
-
// Get all the records.
|
|
98
|
-
const records = await bgFetch.matchAll();
|
|
99
|
-
// Copy each request/response across.
|
|
100
|
-
const promises = records.map(async (record) => {
|
|
101
|
-
const response = await record.responseReady;
|
|
102
|
-
console.log('putting ')
|
|
103
|
-
await cache.put(record.request, response);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Wait for the copying to complete
|
|
107
|
-
await Promise.all(promises);
|
|
108
|
-
|
|
109
|
-
// Update the progress notification.
|
|
110
|
-
// event.updateUI({ title: 'Episode 5 ready to listen!' });
|
|
111
|
-
}());
|
|
112
|
-
});
|