@degreesign/webapp 1.6.8

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 DegreeSign
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # DegreeSign webpack setup for WebApps
2
+ The progressive web application template built with Webpack and TypeScript. Leverage `@degreesign/webapp` package for streamlined configuration and deployment. In addition to server bundling.
3
+
4
+ ## Features
5
+ - **Webpack-powered**: Optimised bundling for production-ready applications.
6
+ - **TypeScript Support**: Ensures type safety and modern JavaScript features.
7
+ - **Modular Structure**: Organised folder layout for scalability.
8
+ - **SEO & PWA Ready**: Configurable metadata and manifest for progressive web apps.
9
+ - **Customisable**: Flexible Webpack configuration via `@degreesign/webapp`.
10
+
11
+ ## Recommended Setup
12
+ 1. **Node.js**: Version 18.x or higher.
13
+ 2. **Package Manager**: npm or Yarn.
14
+ 3. **Dependencies**:
15
+ - Install the `@degreesign/webapp` package: `npm install @degreesign/webapp`.
16
+ - Install Webpack and TypeScript: `npm install webpack webpack-cli typescript ts-loader`.
17
+ 4. **Environment**:
18
+ - Create a `.env` file in the root directory with for any keys used in ts code.
19
+ - Ensure a TypeScript configuration (`tsconfig.json`) is set up.
20
+ 5. **IDE**: Use VS Code or any TypeScript-compatible editor for best experience.
21
+
22
+ ## Package File
23
+ Add file `package.json` as following
24
+ ```json
25
+ {
26
+ "license": "MIT",
27
+ "scripts": {
28
+ "build": "webpack --config webpack.web.ts",
29
+ "start": "webpack serve --config webpack.web.ts",
30
+ "build_server": "webpack --config webpack.server.ts",
31
+ "start_server": "webpack serve --config webpack.server.ts"
32
+ },
33
+ "devDependencies": {
34
+ "@degreesign/webapp": "latest"
35
+ }
36
+ }
37
+ ```
38
+
39
+ ## WebApp FrontEnd
40
+ WebApp front-end website configuration
41
+
42
+ ### Folder Structure
43
+ ```
44
+ app_folder/
45
+ ├── public_html/ # Output directory for production build
46
+ ├── src/ # Source files
47
+ │ ├── assets/ # Static assets
48
+ │ │ ├── images/ # Image assets
49
+ │ │ │ ├── favicon.ico # Favicon
50
+ │ │ │ ├── app_icon.png # App icon for PWA
51
+ │ │ │ └── app_cover_image.webp # Cover image for SEO
52
+ │ ├── code/ # Utility scripts
53
+ │ │ └── utils.ts # Shared TypeScript utilities (optional)
54
+ │ ├── pages/ # Page-specific files
55
+ │ │ ├── home/ # Home page
56
+ │ │ │ ├── home.ts # Home page logic
57
+ │ │ │ └── home.html # Home page template
58
+ │ └── styles.css # Global styles
59
+ ├── webpack.web.ts # Webpack configuration
60
+ ├── .env # Environment variables
61
+ ├── tsconfig.json # TypeScript configuration
62
+ └── package.json # Project metadata and scripts
63
+ ```
64
+
65
+ ### Configuration
66
+ Add `webpack.web.ts` file as the core of the webapp build process as following
67
+ ```typescript
68
+ import { build } from "@degreesign/webapp";
69
+
70
+ module.exports = build({
71
+ type: "webapp",
72
+ websiteDomain: "example.com",
73
+ websiteName: "Your App Name",
74
+ appShortName: "AppName",
75
+ twitterUserName: "YourApp",
76
+ publishedTime: "2025-01-01T00:00:00+00:00",
77
+ author: "Your Name",
78
+ websiteTitle: "Your App Slogan",
79
+ websiteDescription: "A brief description of your app.",
80
+ coverImage: "app_cover_image.webp",
81
+ coverImageDescription: "A descriptive alt text for the cover image.",
82
+ background_color: "#ffffff",
83
+ theme_color: "#000000",
84
+ appIcon: "app_icon.png",
85
+ appIconMaskable: "app_icon_maskable.png",
86
+ fav_icon: "favicon.ico",
87
+ orientation: "portrait",
88
+ pagesList: [{
89
+ uri: `home`,
90
+ name: `HomePage`,
91
+ description: `Progressive Web App (PWA) HomePage`,
92
+ }],
93
+ htmlCommonElements: [],
94
+ obfuscateON: false,
95
+ srcDir: "src",
96
+ assetsDir: "assets",
97
+ commonDir: "code",
98
+ imagesDir: "images",
99
+ pagesDir: "pages",
100
+ pageHome: "home",
101
+ productionDir: "public_html",
102
+ htaccessCustom: "",
103
+ startURI: "/",
104
+ language: "en_GB",
105
+ port: 3210,
106
+ });
107
+ ```
108
+
109
+ ## WebApp BackEnd
110
+ WebApp back-end server configuration
111
+
112
+ ### Folder Structure
113
+ ```
114
+ app_folder/
115
+ ├── server_build/ # Output directory for production build
116
+ ├── server/ # Source files
117
+ │ ├── code/ # Utility scripts
118
+ │ │ └── utils.ts # Shared TypeScript utilities (optional)
119
+ │ └── main.ts # Main server file
120
+ ├── webpack.server.ts # Webpack configuration
121
+ ├── .env # Environment variables
122
+ ├── tsconfig.json # TypeScript configuration
123
+ └── package.json # Project metadata and scripts
124
+ ```
125
+
126
+ ### Configuration
127
+ Add `webpack.server.ts` file as the core of the server build process as following
128
+ ```typescript
129
+ import { build } from "@degreesign/webapp";
130
+
131
+ module.exports = build({
132
+ type: `server`,
133
+ obfuscateON: true,
134
+ srcDir: `server`,
135
+ productionDir: `server_build`,
136
+ filesList: [`main`],
137
+ port: 3211,
138
+ });
139
+ ```
140
+
141
+ ## Key Configuration Notes
142
+ - **Environment Variables**: Use `.env` to securely store sensitive data.
143
+ - **Development Server**: Runs on `port: 3210` by default.
144
+ - **PWA Support**: Customise `app_icon`, `fav_icon`, and `orientation` for a native-like experience.
145
+ - **SEO Optimisation**: Set `websiteTitle`, `websiteDescription`, and `coverImage` for better search visibility.
146
+ - **Pages**: Add all pages to `pagesList`.
147
+
148
+ ## Getting Started
149
+ 1. **Clone the Repository**:
150
+ ```bash
151
+ git clone https://github.com/DegreeSign/ds_webapp.git
152
+ cd ds_webapp
153
+ ```
154
+ 2. **Install Dependencies**:
155
+ ```bash
156
+ yarn install
157
+ ```
158
+ 3. **Create Folders Structure**:
159
+ Create folder structure for webapp, server, or both and add necessary files as explained above.
160
+ 4. **Run Development Server**:
161
+ *For WebApp*
162
+ ```bash
163
+ yarn start
164
+ ```
165
+ *For Server*
166
+ ```bash
167
+ yarn start_server
168
+ ```
169
+ 5. **Build for Production**:
170
+ *For WebApp*
171
+ ```bash
172
+ yarn build
173
+ ```
174
+ Output will be in the `public_html/` directory.
175
+ *For Server*
176
+ ```bash
177
+ yarn build_server
178
+ ```
179
+ Output will be in the `server_build/` directory.
180
+
181
+ ## Contributing
182
+ 1. Fork the repository.
183
+ 2. Create a feature branch: `git checkout -b feature-name`.
184
+ 3. Commit changes: `git commit -m "Add feature"`.
185
+ 4. Push to the branch: `git push origin feature-name`.
186
+ 5. Submit a pull request.
187
+
188
+ ## License
189
+ This project is licensed under the MIT License. See the `LICENSE` file for details.
@@ -0,0 +1,4 @@
1
+ import { build } from "./src/main";
2
+ import { Page } from "./src/types";
3
+ import { readData, readJSON, writeData, writeJSON } from "./src/utils";
4
+ export { build, writeData, writeJSON, readData, readJSON, Page, };
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readJSON = exports.readData = exports.writeJSON = exports.writeData = exports.build = void 0;
4
+ const main_1 = require("./src/main");
5
+ Object.defineProperty(exports, "build", { enumerable: true, get: function () { return main_1.build; } });
6
+ const utils_1 = require("./src/utils");
7
+ Object.defineProperty(exports, "readData", { enumerable: true, get: function () { return utils_1.readData; } });
8
+ Object.defineProperty(exports, "readJSON", { enumerable: true, get: function () { return utils_1.readJSON; } });
9
+ Object.defineProperty(exports, "writeData", { enumerable: true, get: function () { return utils_1.writeData; } });
10
+ Object.defineProperty(exports, "writeJSON", { enumerable: true, get: function () { return utils_1.writeJSON; } });
@@ -0,0 +1,4 @@
1
+ import { Configuration } from "webpack";
2
+ import { ConfigBuild } from "./types";
3
+ declare const build: (params: ConfigBuild) => Configuration;
4
+ export { build };
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.build = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const webpack_1 = __importDefault(require("webpack"));
9
+ const clean_webpack_plugin_1 = require("clean-webpack-plugin");
10
+ const terser_webpack_plugin_1 = __importDefault(require("terser-webpack-plugin"));
11
+ const webpack_node_externals_1 = __importDefault(require("webpack-node-externals"));
12
+ const webpack_bundle_analyzer_1 = require("webpack-bundle-analyzer");
13
+ const dotenv_1 = __importDefault(require("dotenv"));
14
+ const web_1 = require("./web");
15
+ const obfuscate_1 = require("./obfuscate");
16
+ dotenv_1.default.config();
17
+ const build = (params) => {
18
+ const isWebApp = params.type != `server`, { mode = `production`, obfuscateON = false, minimiseON = true, srcDir = `src`, productionDir = `public_html`, port = 3210, maxFileSizeMB = 2, resolveOptions = {}, licenseText = ``, } = params, { filesList = [], } = isWebApp ? {} : params, { entryPoints = {}, customWebRules = [], configWebPlugins = [], cssMinimise = [] } = isWebApp ? (0, web_1.webConfig)(params) : {}, fileSize = maxFileSizeMB * 1024 ** 2, envKeys = {};
19
+ // Environment keys
20
+ for (const key in process.env)
21
+ envKeys[`process.env.${key}`] = JSON.stringify(process.env[key]);
22
+ // entry points
23
+ if (!isWebApp)
24
+ for (let i = 0; i < filesList.length; i++) {
25
+ const fileName = filesList[i];
26
+ entryPoints[fileName] =
27
+ `./${srcDir}/${fileName}.ts`;
28
+ }
29
+ ;
30
+ return {
31
+ entry: entryPoints,
32
+ performance: {
33
+ maxAssetSize: fileSize,
34
+ maxEntrypointSize: fileSize,
35
+ },
36
+ output: {
37
+ path: path_1.default.resolve(process.cwd(), productionDir),
38
+ ...isWebApp ? {
39
+ filename: `code/[name].[contenthash].js`,
40
+ publicPath: '/', // Ensures assets are referenced with absolute paths starting from the root
41
+ } : {
42
+ filename: `[name].js`,
43
+ libraryTarget: `commonjs`, // CommonJS for Node.js
44
+ globalObject: `typeof globalThis !== 'undefined' ? globalThis : (typeof self !== 'undefined' ? self : this)`,
45
+ },
46
+ },
47
+ ...isWebApp ? {} : {
48
+ target: 'node18', // Target Node.js environment
49
+ node: {
50
+ __dirname: false, // Prevent Webpack from mocking __dirname
51
+ __filename: false, // Prevent Webpack from mocking __filename
52
+ },
53
+ externals: [(0, webpack_node_externals_1.default)()], // Externalize all node_modules
54
+ },
55
+ resolve: {
56
+ extensions: [`.tsx`, `.ts`, `.js`, `.json`], // Resolve files
57
+ ...isWebApp ? {} : {
58
+ mainFields: [`module`, `main`],
59
+ conditionNames: [`import`, `default`],
60
+ aliasFields: [],
61
+ preferRelative: true,
62
+ },
63
+ ...resolveOptions,
64
+ },
65
+ module: {
66
+ rules: [
67
+ {
68
+ test: /\.ts$/,
69
+ exclude: /node_modules/,
70
+ use: `ts-loader`,
71
+ },
72
+ ...isWebApp ? [] : [{
73
+ test: /\.node$/,
74
+ loader: 'node-loader',
75
+ options: {
76
+ name: '[name].[ext]',
77
+ },
78
+ }],
79
+ ...customWebRules
80
+ ],
81
+ },
82
+ plugins: [
83
+ new webpack_bundle_analyzer_1.BundleAnalyzerPlugin({
84
+ analyzerMode: params.openAnalyzer == undefined ? `disabled` : `server`,
85
+ openAnalyzer: params.openAnalyzer || false, // Prevents auto-opening the browser
86
+ }),
87
+ new webpack_1.default.DefinePlugin(envKeys),
88
+ ...licenseText ? [new webpack_1.default.BannerPlugin({
89
+ banner: licenseText,
90
+ raw: true,
91
+ })] : [],
92
+ new clean_webpack_plugin_1.CleanWebpackPlugin({
93
+ cleanOnceBeforeBuildPatterns: ['code/*.js', 'code/*.js.LICENSE.txt'],
94
+ }),
95
+ ...obfuscateON ? [obfuscate_1.obfuscationSettings] : [],
96
+ ...configWebPlugins,
97
+ ],
98
+ optimization: {
99
+ minimize: minimiseON, // Enable minimization
100
+ minimizer: [
101
+ new terser_webpack_plugin_1.default({
102
+ terserOptions: {
103
+ compress: {
104
+ drop_console: false, // Do not remove console logs
105
+ },
106
+ },
107
+ ...isWebApp ? {} : {
108
+ extractComments: false, // Prevent duplicate comments for external modules
109
+ },
110
+ }),
111
+ ...cssMinimise,
112
+ ],
113
+ ...isWebApp ? {} : {
114
+ usedExports: true, // Enable tree-shaking
115
+ sideEffects: false, // Mark the project as free of side effects
116
+ },
117
+ }, // @ts-ignore
118
+ devServer: {
119
+ static: {
120
+ directory: path_1.default.join(process.cwd(), productionDir),
121
+ },
122
+ port, // Specify your desired port
123
+ open: true, // Automatically open the browser
124
+ compress: true, // Enable gzip compression for files served
125
+ },
126
+ mode
127
+ };
128
+ };
129
+ exports.build = build;
@@ -0,0 +1,2 @@
1
+ import WebpackObfuscator from "webpack-obfuscator";
2
+ export declare const obfuscationSettings: WebpackObfuscator;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.obfuscationSettings = void 0;
7
+ const webpack_obfuscator_1 = __importDefault(require("webpack-obfuscator"));
8
+ exports.obfuscationSettings = new webpack_obfuscator_1.default({
9
+ compact: true, // Minify the output
10
+ controlFlowFlattening: false, // Disable control flow flattening to avoid errors
11
+ deadCodeInjection: false, // Disable dead code injection
12
+ debugProtection: false, // Disable debug protection
13
+ debugProtectionInterval: 0,
14
+ disableConsoleOutput: false, // Allow console output
15
+ identifierNamesGenerator: 'hexadecimal', // Use hexadecimal names for identifiers
16
+ log: false,
17
+ numbersToExpressions: false, // Disable numbers to expressions to reduce bloat
18
+ renameGlobals: false, // Avoid renaming global variables
19
+ selfDefending: false, // Disable self-defending code
20
+ simplify: true, // Simplify code structure
21
+ splitStrings: true, // Split strings into chunks
22
+ splitStringsChunkLength: 8, // chunk length
23
+ stringArray: true, // Enable string array transformation
24
+ stringArrayCallsTransform: false,
25
+ stringArrayEncoding: [], // No encoding for string array
26
+ stringArrayIndexShift: false, // Disable index shifting
27
+ stringArrayRotate: false, // Disable rotation to avoid potential issues
28
+ stringArrayShuffle: false, // Disable shuffling for stability
29
+ stringArrayWrappersCount: 1, // Match wrapper count
30
+ stringArrayWrappersChainedCalls: false, // Disable chained calls
31
+ stringArrayWrappersParametersMaxCount: 2, // Match max parameters
32
+ stringArrayWrappersType: `variable`, // Use variable wrappers
33
+ stringArrayThreshold: 1, // Obfuscate all strings
34
+ unicodeEscapeSequence: false // Disable unicode escape sequences
35
+ });
@@ -0,0 +1,7 @@
1
+ declare const DomainName: string, CACHE_NAME: string, ROOT_URI = "APP_URL", NOTIFICATION_ICON = "APP_ICON", NOTIFICATION_BADGE = "APP_BADGE", DEFAULT_NOTIFICATION_DATA: {
2
+ title: string;
3
+ body: string;
4
+ url: string;
5
+ },
6
+ /** Check internet connectivity */
7
+ isOnline: () => Promise<boolean>;
package/dist/src/sw.js ADDED
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ const DomainName = self.location.origin?.split(`.`)?.[0], CACHE_NAME = `cache_${DomainName}`, ROOT_URI = `APP_URL`, NOTIFICATION_ICON = `APP_ICON`, NOTIFICATION_BADGE = `APP_BADGE`, DEFAULT_NOTIFICATION_DATA = {
3
+ title: `New Notification`,
4
+ body: `You have a new notification!`,
5
+ url: `NOTIFICATION_URI`
6
+ },
7
+ /** Check internet connectivity */
8
+ isOnline = async () => {
9
+ try {
10
+ // Attempt to fetch a small resource to check connectivity
11
+ await fetch(`REFERENCE_FILE`, {
12
+ method: `HEAD`,
13
+ mode: `no-cors`,
14
+ cache: `no-store`
15
+ });
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ ;
22
+ };
23
+ // // Install event: Activate immediately, no pre-caching
24
+ // (self as unknown as ServiceWorkerGlobalScope).addEventListener(`install`, (event: ExtendableEvent) => {
25
+ // event.waitUntil(
26
+ // (self as unknown as ServiceWorkerGlobalScope).skipWaiting()
27
+ // );
28
+ // });
29
+ // // Activate event: Clean up old caches
30
+ // (self as unknown as ServiceWorkerGlobalScope).addEventListener(`activate`, (event: ExtendableEvent) => {
31
+ // event.waitUntil(
32
+ // caches.keys().then(cacheNames =>
33
+ // Promise.all(
34
+ // cacheNames.map(name => {
35
+ // if (name !== CACHE_NAME) {
36
+ // return caches.delete(name);
37
+ // }
38
+ // return null;
39
+ // })
40
+ // )
41
+ // ).then(() =>
42
+ // (self as unknown as ServiceWorkerGlobalScope)
43
+ // ?.clients
44
+ // ?.claim()
45
+ // )
46
+ // );
47
+ // });
48
+ // Fetch event: Check isOnline only for .html files, others load from cache if available
49
+ self.addEventListener(`fetch`, (event) => {
50
+ // event.respondWith(
51
+ // (async () => {
52
+ // const isHtmlRequest = event.request.url.endsWith('.html') || event.request.url.endsWith('/');
53
+ // if (event.request.method !== `GET`) {
54
+ // // Non-GET requests are not cached
55
+ // if (isHtmlRequest) {
56
+ // const online = await isOnline();
57
+ // if (online) {
58
+ // return fetch(event.request).catch(() => {
59
+ // return new Response(`Network unavailable`, {
60
+ // status: 503,
61
+ // statusText: `Service Unavailable`
62
+ // });
63
+ // });
64
+ // }
65
+ // return new Response(`Offline and non-GET request`, {
66
+ // status: 503,
67
+ // statusText: `Service Unavailable`
68
+ // });
69
+ // }
70
+ // return new Response(`Non-GET request not supported`, {
71
+ // status: 503,
72
+ // statusText: `Service Unavailable`
73
+ // });
74
+ // }
75
+ // // For non-.html files, try cache first
76
+ // if (!isHtmlRequest) {
77
+ // const cachedResponse = await caches.match(event.request);
78
+ // if (cachedResponse) {
79
+ // return cachedResponse;
80
+ // }
81
+ // // If not in cache, fetch from network
82
+ // try {
83
+ // const networkResponse = await fetch(event.request);
84
+ // if (networkResponse.ok) {
85
+ // const cache = await caches.open(CACHE_NAME);
86
+ // cache.put(event.request, networkResponse.clone());
87
+ // }
88
+ // return networkResponse;
89
+ // } catch {
90
+ // return new Response(`Network unavailable and no cached response found`, {
91
+ // status: 503,
92
+ // statusText: `Service Unavailable`
93
+ // });
94
+ // }
95
+ // }
96
+ // // For .html files, check online status
97
+ // const online = await isOnline();
98
+ // if (online) {
99
+ // // When online, always fetch from network
100
+ // try {
101
+ // const networkResponse = await fetch(event.request);
102
+ // if (networkResponse.ok) {
103
+ // const cache = await caches.open(CACHE_NAME);
104
+ // cache.put(event.request, networkResponse.clone());
105
+ // }
106
+ // return networkResponse;
107
+ // } catch {
108
+ // // Network failed, try cache
109
+ // const cachedResponse = await caches.match(event.request);
110
+ // if (cachedResponse) {
111
+ // return cachedResponse;
112
+ // }
113
+ // return new Response(`Network unavailable and no cached response found`, {
114
+ // status: 503,
115
+ // statusText: `Service Unavailable`
116
+ // });
117
+ // }
118
+ // } else {
119
+ // // When offline, serve from cache
120
+ // const cachedResponse = await caches.match(event.request);
121
+ // if (cachedResponse) {
122
+ // return cachedResponse;
123
+ // }
124
+ // return new Response(`Offline and no cached response found`, {
125
+ // status: 503,
126
+ // statusText: `Service Unavailable`
127
+ // });
128
+ // }
129
+ // })()
130
+ // );
131
+ });
132
+ // Notification click: Open or focus window
133
+ self.addEventListener(`notificationclick`, (event) => {
134
+ event.notification.close();
135
+ const url = new URL(ROOT_URI, self.location.origin).href;
136
+ event.waitUntil(self
137
+ ?.clients
138
+ ?.matchAll({ type: `window`, includeUncontrolled: true })
139
+ ?.then(clients => {
140
+ for (const client of clients) {
141
+ if (client.url === url && `focus` in client)
142
+ return client.focus();
143
+ }
144
+ ; // @ts-ignore
145
+ if (self?.clients.openWindow)
146
+ return self.clients.openWindow(url);
147
+ }));
148
+ });
149
+ // Notification close: Log event
150
+ self.addEventListener(`notificationclose`, (event) => {
151
+ // console.log(`Notification closed:`, event.notification);
152
+ });
153
+ // Push event: Show notification
154
+ self.addEventListener(`push`, (event) => {
155
+ let data = DEFAULT_NOTIFICATION_DATA;
156
+ if (event.data) {
157
+ try {
158
+ data = event.data.json();
159
+ }
160
+ catch (e) {
161
+ console.warn(`Invalid push data, using default: ${e}`);
162
+ }
163
+ ;
164
+ }
165
+ ;
166
+ const options = {
167
+ body: data.body,
168
+ icon: NOTIFICATION_ICON,
169
+ badge: NOTIFICATION_BADGE,
170
+ data: { url: new URL(data.url || ROOT_URI, self.location.origin).href }
171
+ };
172
+ event.waitUntil(self.registration.showNotification(data.title, options));
173
+ });
File without changes
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ if ('serviceWorker' in navigator) {
3
+ const serviceWorker = navigator.serviceWorker;
4
+ serviceWorker?.getRegistrations?.()?.then((registrations) => {
5
+ for (let registration of registrations)
6
+ registration.unregister();
7
+ serviceWorker.register(`/sw.js?updated=TIME_UPDATED`);
8
+ });
9
+ }
10
+ ;