first commit

This commit is contained in:
s.golasch
2023-08-01 13:49:46 +02:00
commit 1fc239fd54
20238 changed files with 3112246 additions and 0 deletions

21
build/node_modules/generate-service-worker/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Pinterest
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

95
build/node_modules/generate-service-worker/README.md generated vendored Normal file
View File

@@ -0,0 +1,95 @@
Generate Service Worker
=========================
A node module for generating service worker files based on provided configuration options.
## Why?
There are several other popular service worker generators out there ([sw-precache](https://github.com/GoogleChrome/sw-precache), [offline-plugin](https://github.com/NekR/offline-plugin/), etc), but they focus only on caching, and are not testable or easy to experiment with. Service workers also include support for other tools like notifications and homescreen installs. This generator attempts to account for a wider variety of configurable options.
GenerateServiceWorker supports generating a service worker with a root configuration, and any number of other experimental service workers. **This is perfect for experimenting with different caching strategies, or rolling out service worker changes.** The runtime file generated by [service-worker-plugin](https://github.com/pinterest/service-workers/tree/master/packages/service-worker-plugin) makes it particularly easy to utilize your own experiment framework alongside the generated experimental service worker files if you can statically host the service workers.
Caching strategies inspired by [sw-toolkit](https://github.com/GoogleChrome/sw-toolbox).
## Use
```js
const generateServiceWorkers = require('generate-service-worker');
const serviceWorkers = generateServiceWorkers({
cache: {
offline: true,
precache: ['/static/js/bundle-81hj9isadf973adfsh10.js'],
strategy: [{
type: 'prefer-cache',
matches: ['\\.js']
}],
}
}, {
'roll_out_notifications': {
notifications: {
default: {
title: 'Pinterest',
body: 'You\'ve got new Pins!'
}
},
}
});
```
## Configurations
GenerateServiceWorker currently supports caching and notifications. The following are the configuration options for each.
### Caching
The `cache` key is used for defining caching strategies. The strings in `precache` will be used to prefetch assets and insert them into the cache. The regexes in `strategy.matches` are used at runtime to determine which strategy to use for a given GET request. All cached items will be removed at installation of a new service worker version. Additionally, you can use your own custom cache template by including the full path in the `template` property. We suggest forking our `templates/cache.js` file to get started and to be familiar with how variable injection works in the codebase. If the `offline` option is set to `true`, the service worker will assume that an html response is an "App Shell". It will cache the html response and return it only in the case of a static route change while offline.
```js
const CacheType = {
offline?: boolean,
precache?: Array<string>,
strategy?: Array<StrategyType>,
template?: string,
};
const StrategyType = {
type: 'offline-only' | 'fallback-only' | 'prefer-cache' | 'race',
matches: Array<regex>,
};
```
### Strategy Types
strategy | description
--------------- | -----------
`offline-only` | Only serve from cache if browser is offline.
`fallback-only` | Only serve from cache if fetch returns an error status (>= 400)
`prefer-cache` | Always pull from cache if data is available
`race` | Pull from cache and make fetch request. Whichever returns first should be used. (Good for some low-end phones)
### Notifications
The `notifications` key is used for including browser notification events in your service worker. To enable the notifications in your app, you can call `runtime.requestNotificationsPermission()` from the generated runtime file. The backend work is not included. You will still need to push notifications to your provider and handle registration. Additionally, you can use your own custom notifications template by including the full path in the `template` property. We suggest forking our `templates/notifications.js` file to get started and to be familiar with how variable injection works in the codebase.
```js
const NotificationsType = {
default: {
title: string,
body?: string,
icon?: string,
tag?: string,
data?: {
url: string
}
},
duration?: number,
template?: string,
});
```
### Event Logging
The `log` key is used for defining which service worker events your API wants to know about. Each `string` should be a valid url path that will receive a 'GET' request for the corresponding event.
```js
const LogType = {
notificationClicked?: string,
notificationReceived?: string
};
```
## License
MIT

66
build/node_modules/generate-service-worker/index.js generated vendored Normal file
View File

@@ -0,0 +1,66 @@
'use strict';
const fs = require('fs');
const path = require('path');
const hash = require('./utils/hash');
const validate = require('./utils/validators').validate;
const TEMPLATE_PATH = path.join(__dirname, 'templates');
function buildMainTemplate(config) {
const template = config.template || path.join(TEMPLATE_PATH, 'main.js');
return fs.readFileSync(template, 'utf-8');
}
function buildCacheTemplate(config) {
if (!config.cache) {
return '';
}
const template = config.cache.template || path.join(TEMPLATE_PATH, 'cache.js');
return fs.readFileSync(template, 'utf-8');
}
function buildNotificationsTemplate(config) {
if (!config.notifications) {
return '';
}
const template = config.notifications.template || path.join(TEMPLATE_PATH, 'notifications.js');
return fs.readFileSync(template, 'utf-8');
}
function buildServiceWorker(config) {
const Cache = config.cache ? JSON.stringify(config.cache, null, 2) : 'undefined';
const Notifications = config.notifications ? JSON.stringify(config.notifications, null, 2) : 'undefined';
const Log = config.log ? JSON.stringify(config.log, null, 2) : '{}';
return [
'/*\n * AUTOGENERATED FROM GENERATE-SERVICE-WORKER\n */\n',
`const $VERSION = '${hash(config)}';`,
`const $DEBUG = ${config.debug || false};`,
`const $Cache = ${Cache};`,
`const $Notifications = ${Notifications};`,
`const $Log = ${Log};\n`,
buildMainTemplate(config),
buildCacheTemplate(config),
buildNotificationsTemplate(config)
].join('\n');
}
/*
* Public API. This method will generate a root service worker and any number of
* extended configuration service workers (used for testing/experimentation).
* @returns Object { [key]: service-worker }
*/
module.exports = function generateServiceWorkers(baseConfig, experimentConfigs) {
validate(baseConfig);
const serviceWorkers = {
main: buildServiceWorker(baseConfig)
};
Object.keys(experimentConfigs || {}).forEach(key => {
validate(experimentConfigs[key]);
serviceWorkers[key] = buildServiceWorker(experimentConfigs[key]);
});
return serviceWorkers;
};

View File

@@ -0,0 +1,54 @@
{
"_from": "generate-service-worker",
"_id": "generate-service-worker@1.7.2",
"_inBundle": false,
"_integrity": "sha1-tFWMUBUOrOTREj5HjQ3gAXgamUc=",
"_location": "/generate-service-worker",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "generate-service-worker",
"name": "generate-service-worker",
"escapedName": "generate-service-worker",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/generate-service-worker/-/generate-service-worker-1.7.2.tgz",
"_shasum": "b4558c50150eace4d1123e478d0de001781a9947",
"_spec": "generate-service-worker",
"_where": "/Users/asciidisco/Desktop/asciidisco.com/build",
"author": {
"name": "zackargyle"
},
"bugs": {
"url": "https://github.com/pinterest/service-workers/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "Generate Service Worker ========================= A node module for generating service worker files based on provided configuration options.",
"files": [
"index.js",
"templates/*.js",
"utils/*.js"
],
"homepage": "https://github.com/pinterest/service-workers/tree/master/packages/generate-service-worker",
"keywords": [
"service-workers",
"service",
"workers"
],
"license": "MIT",
"main": "index.js",
"name": "generate-service-worker",
"repository": {
"type": "git",
"url": "git+https://github.com/pinterest/service-workers.git"
},
"version": "1.7.2"
}

View File

@@ -0,0 +1,198 @@
/* -------- CACHE --------- */
const CURRENT_CACHE = `SW_CACHE:${$VERSION}`;
const APP_SHELL_CACHE = 'SW_APP_SHELL';
const isValidResponse = res => (res.ok || (res.status === 0 && res.type === 'opaque'));
const isNavigation = req => req.mode === 'navigate' || (req.method === 'GET' && req.headers.get('accept').includes('text/html'));
/* -------- CACHE LISTENERS --------- */
self.addEventListener('install', handleInstall);
self.addEventListener('activate', handleActivate);
if ($Cache.precache || $Cache.offline || $Cache.strategy) {
self.addEventListener('fetch', handleFetch);
}
/* -------- CACHE HANDLERS --------- */
function handleInstall(event) {
logger.log('Entering install handler.');
self.skipWaiting();
if ($Cache.precache) {
event.waitUntil(precache());
}
}
function handleActivate(event) {
logger.log('Entering activate handler.');
const cachesCleared = caches.keys().then(cacheNames => {
logger.group('cleanup');
return Promise.all(cacheNames.map(cacheName => {
if (CURRENT_CACHE !== cacheName) {
logger.log(`Deleting cache key: ${cacheName}`, 'cleanup');
return caches.delete(cacheName);
}
return Promise.resolve();
})).then(() => logger.groupEnd('cleanup'));
});
event.waitUntil(cachesCleared);
}
function handleFetch(event) {
if (isNavigation(event.request)) {
if ($Cache.offline) {
event.respondWith(
fetchAndCacheAppShell(event.request)
.catch(() => caches.match(APP_SHELL_CACHE))
.catch(() => undefined)
);
}
} else if (event.request.method === 'GET') {
const strategy = getStrategyForUrl(event.request.url);
if (strategy) {
logger.group(event.request.url);
logger.log(`Using strategy ${strategy.type}.`, event.request.url);
event.respondWith(
applyEventStrategy(strategy, event).then(response => {
logger.groupEnd(event.request.url);
return response;
}).catch(() => undefined)
);
}
}
}
/* -------- CACHE HELPERS --------- */
function applyEventStrategy(strategy, event) {
const request = event.request;
switch (strategy.type) {
case 'offline-only':
return fetchAndCache(request, strategy)().catch(getFromCache(request));
case 'fallback-only':
return fetchAndCache(request, strategy)().then(fallbackToCache(request));
case 'prefer-cache':
return getFromCache(request)().catch(fetchAndCache(request, strategy));
case 'race':
return getFromFastest(request, strategy)();
default:
return Promise.reject(`Strategy not supported: ${strategy.type}`);
}
}
function insertInCache(request, response) {
logger.log('Inserting in cache.', request.url);
return caches.open(CURRENT_CACHE)
.then(cache => cache.put(request, response));
}
function getFromCache(request) {
return () => {
return caches.match(request).then(response => {
if (response) {
logger.log('Found entry in cache.', request.url);
return response;
}
logger.log('No entry found in cache.', request.url);
throw new Error(`No cache entry found for ${request.url}`);
});
};
}
function getStrategyForUrl(url) {
if ($Cache.strategy) {
return $Cache.strategy.find(strategy => {
return strategy.matches.some(match => {
const regex = new RegExp(match);
return regex.test(url);
});
});
}
return null;
}
function fetchAndCache(request) {
return () => {
logger.log('Fetching remote data.', request.url);
return fetch(request).then(response => {
if (isValidResponse(response)) {
logger.log('Caching remote response.', request.url);
insertInCache(request, response.clone());
} else {
logger.log('Fetch error.', request.url);
}
return response;
});
};
}
function fetchAndCacheAppShell(request) {
return fetch(request).then(response => {
if (isValidResponse(response)) {
logger.log('Caching app shell.', request.url);
insertInCache(APP_SHELL_CACHE, response.clone());
}
return response;
});
}
function fallbackToCache(request) {
return (response) => {
if (!isValidResponse(response)) {
return getFromCache(request)();
}
return response;
};
}
function getFromFastest(request, strategy) {
return () => new Promise((resolve, reject) => {
var errors = 0;
function raceReject() {
errors += 1;
if (errors === 2) {
reject(new Error('Network and cache both failed.'));
}
}
function raceResolve(response) {
if (response instanceof Response) {
resolve(response);
} else {
raceReject();
}
}
getFromCache(request)()
.then(raceResolve)
.catch(raceReject);
fetchAndCache(request, strategy)()
.then(raceResolve)
.catch(raceReject);
});
}
function precache() {
logger.group('precaching');
return caches.open(CURRENT_CACHE).then(cache => {
return Promise.all(
$Cache.precache.map(urlToPrefetch => {
logger.log(urlToPrefetch, 'precaching');
const cacheBustedUrl = new URL(urlToPrefetch, location.href);
cacheBustedUrl.search += (cacheBustedUrl.search ? '&' : '?') + `cache-bust=${Date.now()}`;
const request = new Request(cacheBustedUrl, { mode: 'no-cors' });
return fetch(request).then(response => {
if (!isValidResponse(response)) {
logger.error(`Failed for ${urlToPrefetch}.`, 'precaching');
return undefined;
}
return cache.put(urlToPrefetch, response);
});
})
);
}).then(() => logger.groupEnd('precaching'));
}

View File

@@ -0,0 +1,41 @@
if (!$Cache) {
self.addEventListener('install', (event) => {
event.waitUntil(self.skipWaiting());
});
}
function print(fn) {
return function (message, group) {
if ($DEBUG) {
if (group && logger.groups[group]) {
logger.groups[group].push({
fn: fn,
message: message
});
} else {
console[fn].call(console, message);
}
}
};
}
const logger = {
groups: {},
group: group => {
logger.groups[group] = [];
},
groupEnd: group => {
const groupLogs = logger.groups[group];
if (groupLogs && groupLogs.length > 0) {
console.groupCollapsed(group);
groupLogs.forEach(log => {
console[log.fn].call(console, log.message);
});
console.groupEnd();
}
delete logger.groups[group];
},
log: print('log'),
warn: print('warn'),
error: print('error')
};

View File

@@ -0,0 +1,137 @@
'use strict';
/* -------- NOTIFICATIONS --------- */
self.addEventListener('push', handleNotificationPush);
self.addEventListener('notificationclick', handleNotificationClick);
/* -------- NOTIFICATIONS HANDLERS --------- */
function handleNotificationPush(event) {
logger.log('Push notification received');
if ($Log.notificationReceived) {
event.waitUntil(logNotificationReceived(event));
}
// Show notification or fallback
if (event.data && event.data.title) {
event.waitUntil(showNotification(event.data));
} else if ($Notifications.fallbackURL) {
event.waitUntil(
self.registration.pushManager.getSubscription()
.then(fetchNotification)
.then(convertResponseToJson)
.then(showNotification)
.catch(showNotification)
);
} else {
logger.warn('No notification.data and no fallbackURL.');
event.waitUntil(showNotification());
}
}
function handleNotificationClick(event) {
logger.log('Push notification clicked.', event.notification.tag);
if ($Log.notificationClicked) {
event.waitUntil(logNotificationClick(event));
}
// Open the url if provided
if (event.notification.data && event.notification.data.url) {
const url = event.notification.data.url;
event.waitUntil(openWindow(url));
} else if (event.notification.tag.indexOf(':') !== -1) {
// TODO: Deprecate
const url = event.notification.tag.split(':')[2] || '/';
event.waitUntil(openWindow(url));
} else {
logger.warn('Cannot route click with no data.url property. Using "/".', event.notification.tag);
event.waitUntil(openWindow('/'));
}
event.notification.close();
logger.groupEnd(event.notification.tag);
}
/* -------- NOTIFICATIONS HELPERS --------- */
function showNotification(data) {
if (!data || !data.tag) {
// eslint-disable-next-line no-param-reassign
data = $Notifications.default;
}
logger.group(data.tag);
logger.log('Show notification.', data.tag);
return self.registration
.showNotification(data.title, data)
.then(delayDismissNotification);
}
function fetchNotification(subscription) {
if (!subscription) {
logger.warn('No subscription found.');
throw new Error('No subscription found.');
}
logger.log('Fetching remote notification data.');
const queries = {
endpoint: subscription.endpoint
};
const url = formatUrl($Notifications.fallbackURL, queries);
return fetch(url, { credentials: 'include' });
}
function convertResponseToJson(response) {
if (response.status !== 200) {
throw new Error('Notification data fetch failed.');
}
return response.json();
}
function delayDismissNotification() {
setTimeout(function serviceWorkerDismissNotification() {
self.registration.getNotifications()
.then(notifications => {
notifications.forEach(notification => {
notification.close();
logger.log('Dismissing notification.', notification.tag);
logger.groupEnd(notification.tag);
});
});
}, $Notifications.duration || 5000);
}
function openWindow(url) {
if (clients.openWindow) {
return clients.openWindow(url);
}
return Promise.resolve();
}
function logNotificationReceived(event) {
return logAction(event, $Log.notificationReceived);
}
function logNotificationClick(event) {
return logAction(event.notification, $Log.notificationClicked);
}
function logAction(notification, url) {
logger.log(`Send log event to ${url}.`, notification.tag);
return self.registration.pushManager.getSubscription().then((subscription) => {
const query = {
endpoint: subscription.endpoint,
tag: notification.tag
};
return fetch(formatUrl(url, query), { credentials: 'include' });
});
}
function formatUrl(url, queries) {
const prefix = url.includes('?') ? '&' : '?';
const query = Object.keys(queries).map(function (key) {
return `${key}=${queries[key]}`;
}).join('&');
return url + prefix + query;
}

View File

@@ -0,0 +1,10 @@
const crypto = require('crypto');
function buildHashFromConfig(config) {
return crypto
.createHash('md5')
.update(JSON.stringify(config))
.digest('hex');
}
module.exports = buildHashFromConfig;

View File

@@ -0,0 +1,102 @@
/* eslint-disable no-throw-literal */
function arrayOfTypeValidation(validator) {
return withRequired(function arrayOfType(value) {
if (!Array.isArray(value)) {
throw `Value ${value} must be an array.`;
}
value.every(validator);
});
}
function oneOfTypeValidation(types) {
return withRequired(function oneOf(value) {
const isValidType = types.some(function (Type) {
try {
Type(value);
return true;
} catch (e) {
return false;
}
});
if (!isValidType) {
throw `Value ${value} not a valid type.`;
}
});
}
function oneOfValidation(list) {
return withRequired(function oneOf(value) {
if (list.indexOf(value) === -1) {
throw `Value ${value} not a valid option from list: ${list.join(', ')}.`;
}
});
}
function shapeValidation(objShape) {
return withRequired(function shape(value) {
if (value && typeof value !== 'object') {
throw `Value <${value}> must be an object.`;
}
Object.keys(objShape).forEach(function shapeKeyValidation(key) {
try {
objShape[key](value[key]);
} catch (e) {
if (objShape[key].name === 'shape') {
throw e;
} else {
throw `Key: "${key}" failed with "${e}"`;
}
}
});
});
}
function booleanValidation(value) {
if (!value || typeof value !== 'boolean') {
throw `Value ${value} must be of type "boolean".`;
}
}
function objectValidation(value) {
if (!value || typeof value !== 'object') {
throw `Value ${value} must be non-null "object".`;
}
}
function stringValidation(value) {
if (typeof value !== 'string') {
throw `Value ${value} must be of type "string".`;
}
}
function numberValidation(value) {
if (typeof value !== 'number') {
throw `Value ${value} must be of type "number".`;
}
}
function withRequired(_validator) {
function validator(value) {
return value === undefined || _validator(value);
}
validator.required = function requiredValidator(value) {
if (value === undefined) {
throw 'Value cannot be undefined.';
}
_validator(value);
};
return validator;
}
module.exports = {
boolean: withRequired(booleanValidation),
object: withRequired(objectValidation),
number: withRequired(numberValidation),
string: withRequired(stringValidation),
arrayOfType: arrayOfTypeValidation,
oneOf: oneOfValidation,
oneOfType: oneOfTypeValidation,
shape: shapeValidation
};

View File

@@ -0,0 +1,54 @@
const V = require('./validate');
const StrategyShape = V.shape({
type: V.oneOf(['offline-only', 'fallback-only', 'prefer-cache', 'race']).required,
matches: V.arrayOfType(V.string).required
});
const CacheShape = V.shape({
offline: V.boolean,
precache: V.arrayOfType(V.string),
strategy: V.arrayOfType(StrategyShape)
});
const NotificationsShape = V.shape({
default: V.shape({
title: V.string.required,
body: V.string,
icon: V.string,
tag: V.string,
data: V.shape({
url: V.string
})
}).required,
duration: V.number,
fallbackURL: V.string
});
const LogShape = V.shape({
installed: V.string,
notificationClicked: V.string,
notificationReceived: V.string,
requestOptions: V.object
});
function validate(config) {
if (!config.template) {
if (config.cache && !config.cache.template) {
CacheShape(config.cache);
}
if (config.notifications && !config.notifications.template) {
NotificationsShape(config.notifications);
}
if (config.log) {
LogShape(config.log);
}
}
}
module.exports = {
CacheShape: CacheShape,
NotificationsShape: NotificationsShape,
LogShape: LogShape,
validate: validate
};