Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(service-worker): add service worker utilities and logging support #18

Merged
merged 23 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9a7a104
feat(service-worker): add service worker utilities and logging support
arashagp Dec 14, 2024
d5281f8
feat(service-worker): refactor imports and add version-checker utility
arashagp Dec 14, 2024
b60c47b
refactor(service-worker): enhance PWA installation handling with type…
arashagp Dec 16, 2024
21d82da
feat(service-worker): add beforeinstallprompt option and integrate sn…
arashagp Dec 16, 2024
af07308
refactor(snackbar): optimize logger and signal instantiation with pur…
arashagp Dec 16, 2024
3585038
feat(snackbar): allow 'infinite' duration option for snackbar display
arashagp Dec 16, 2024
e90f9d7
doc(snackbar): update documentation to clarify 'infinite' duration op…
arashagp Dec 16, 2024
fa505b5
refactor(service-worker): update snackbar duration to 'infinite' for …
arashagp Dec 16, 2024
245dfe1
refactor(service-worker): improve AlwatrSignal instantiation with pur…
arashagp Dec 16, 2024
da949f2
refactor(service-worker): update version and repository directory in …
arashagp Dec 16, 2024
c80b516
feat(service-worker): add an empty test file for initial setup
arashagp Dec 16, 2024
30f85ab
refactor(snackbar): simplify default duration assignment in showSnack…
arashagp Dec 16, 2024
54d0d8d
doc(service-worker): remove unnecessary comment in main.ts
arashagp Dec 16, 2024
45e266b
refactor(service-worker): remove install-pwa.ts file and related func…
arashagp Dec 16, 2024
59c0359
refactor(logger): simplify logger initialization by removing package …
arashagp Dec 16, 2024
bac2251
refactor(service-worker): remove serviceWorkerNotifyHandler and relat…
arashagp Dec 16, 2024
e63fd7e
refactor(version-checker): remove isVersionLarger function and relate…
arashagp Dec 16, 2024
fa24ba7
refactor(service-worker): remove unused exports from main.ts
arashagp Dec 16, 2024
944c6f7
refactor(service-worker): clean up package.json and yarn.lock by remo…
arashagp Dec 16, 2024
217786e
doc(service-worker): write installation
arashagp Dec 17, 2024
fa1aad9
chore: set correct owner homepage
njfamirm Dec 17, 2024
466ca5b
refactor(service-worker): review and enhance
njfamirm Dec 17, 2024
9a9e4dc
lint: make happy
njfamirm Dec 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"argb",
"backorder",
"backordering",
"beforeinstallprompt",
"colspan",
"cssnano",
"endmacro",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Necessary library for all ECMAScript (JavaScript/TypeScript) projects.",
"repository": "https://github.com/the-nexim/nanolib",
"license": "AGPL-3.0-only",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (www.njfamirm.ir)",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (https://www.njfamirm.ir)",
"contributors": [
"Arash Ghardashpoor <arash.qardashpoor@gmail.com> (https://www.agpagp.ir)"
],
Expand Down
2 changes: 1 addition & 1 deletion packages/alpine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"directory": "packages/alpine"
},
"license": "AGPL-3.0-only",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (www.njfamirm.ir)",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (https://www.njfamirm.ir)",
"contributors": [
"Arash Ghardashpoor <arash.qardashpoor@gmail.com> (https://www.agpagp.ir)"
],
Expand Down
2 changes: 1 addition & 1 deletion packages/element/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"directory": "packages/element"
},
"license": "AGPL-3.0-only",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (www.njfamirm.ir)",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (https://www.njfamirm.ir)",
"contributors": [
"Arash Ghardashpoor <arash.qardashpoor@gmail.com> (https://www.agpagp.ir)"
],
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"directory": "packages/eslint-config"
},
"license": "AGPL-3.0-only",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (www.njfamirm.ir)",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (https://www.njfamirm.ir)",
"contributors": [
"Arash Ghardashpoor <arash.qardashpoor@gmail.com> (https://www.agpagp.ir)"
],
Expand Down
2 changes: 1 addition & 1 deletion packages/prettier-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"directory": "packages/prettier-config"
},
"license": "AGPL-3.0-only",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (www.njfamirm.ir)",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (https://www.njfamirm.ir)",
"contributors": [
"Arash Ghardashpoor <arash.qardashpoor@gmail.com> (https://www.agpagp.ir)"
],
Expand Down
661 changes: 661 additions & 0 deletions packages/service-worker/LICENSE

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions packages/service-worker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# @nexim/service-worker

![NPM Version](https://img.shields.io/npm/v/%40nexim%2Fservice-worker)
![npm bundle size](https://img.shields.io/bundlephobia/min/%40nexim%2Fservice-worker)
![Build & Lint & Test](https://github.com/the-nexim/nanolib/actions/workflows/build-lint-test.yaml/badge.svg)
![NPM Downloads](https://img.shields.io/npm/dm/%40nexim%2Fservice-worker)
![NPM License](https://img.shields.io/npm/l/%40nexim%2Fservice-worker)

## Overview

Utilities to simplify the usage of service workers in your web applications.

## Installation

Install the package using npm or yarn:

```sh
npm install @nexim/service-worker

# Or using yarn
yarn add @nexim/service-worker
```

## Api

### registerServiceWorker

Register the service worker and handle updates.

```ts
import {registerServiceWorker} from '@nexim/service-worker';

const serviceWorkerPath = '/service-worker.js';
registerServiceWorker(serviceWorkerPath);
```

### serviceWorkerSignal

Signal for service worker events.

```ts
import {serviceWorkerSignal} from '@nexim/service-worker';

serviceWorkerSignal.subscribe(({event}) => {
console.log('Service worker event:', event);
});
```

### Type ServiceWorkerEvent

The events that can be emitted by the service worker.
73 changes: 73 additions & 0 deletions packages/service-worker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"name": "@nexim/service-worker",
"version": "0.0.0",
"description": "Utilities to simplify the usage of service workers in your web applications.",
"keywords": [
"worker",
"service-worker",
"typescript",
"nexim"
],
"homepage": "https://github.com/the-nexim/nanolib/tree/next/packages/service-worker#readme",
"bugs": {
"url": "https://github.com/the-nexim/nanolib/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/the-nexim/nanolib",
"directory": "packages/service-worker"
},
"license": "AGPL-3.0-only",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (https://www.njfamirm.ir)",
"contributors": [
"Arash Ghardashpoor <arash.qardashpoor@gmail.com> (https://www.agpagp.ir)"
],
"type": "module",
"exports": {
".": {
"types": "./dist/main.d.ts",
"import": "./dist/main.mjs",
"require": "./dist/main.cjs"
}
},
"main": "./dist/main.cjs",
"module": "./dist/main.mjs",
"types": "./dist/main.d.ts",
"files": [
"**/*.{js,mjs,cjs,map,d.ts,html,md,LEGAL.txt}",
"LICENSE",
"!**/*.test.js",
"!demo/**/*"
],
"scripts": {
"b": "yarn run build",
"build": "yarn run build:ts && yarn run build:es",
"build:es": "nano-build --preset=module",
"build:ts": "tsc --build",
"c": "yarn run clean",
"cb": "yarn run clean && yarn run build",
"clean": "rm -rfv dist *.tsbuildinfo",
"d": "yarn run build:es && yarn node --enable-source-maps --trace-warnings",
"t": "yarn run test",
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --enable-source-maps --experimental-vm-modules\" ava",
"w": "yarn run watch",
"watch": "yarn run watch:ts & yarn run watch:es",
"watch:es": "yarn run build:es --watch",
"watch:ts": "yarn run build:ts --watch --preserveWatchOutput"
},
"dependencies": {
"@alwatr/flux": "^4.0.2",
"@alwatr/logger": "^5.0.0",
"@alwatr/package-tracer": "^5.0.0"
},
"devDependencies": {
"@alwatr/nano-build": "^5.0.0",
"@alwatr/type-helper": "^5.0.0",
"@nexim/typescript-config": "workspace:^",
"ava": "^6.2.0",
"typescript": "^5.6.3"
},
"publishConfig": {
"access": "public"
}
}
6 changes: 6 additions & 0 deletions packages/service-worker/src/main.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import test from 'ava';

// empty test
test('empty test', (test) => {
test.pass();
});
106 changes: 106 additions & 0 deletions packages/service-worker/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {AlwatrSignal} from '@alwatr/flux';
import {createLogger} from '@alwatr/logger';
import {packageTracer} from '@alwatr/package-tracer';

/**
* The events that can be emitted by the service worker.
*/
export type ServiceWorkerEvent =
| 'service_worker_registered'
| 'service_worker_register_failed'
| 'service_worker_first_install'
| 'service_worker_updated'
| 'service_worker_installed'
| 'service_worker_update_found';

__dev_mode__: packageTracer.add(__package_name__, __package_version__);

const logger = /* @__PURE__ */ createLogger(__package_name__);

/**
* Signal for service worker events.
*
* @example
* import {serviceWorkerSignal} from '@nexim/service-worker';
*
* serviceWorkerSignal.subscribe(({event}) => {
* console.log('Service worker event:', event);
* });
*/
export const serviceWorkerSignal = /* @__PURE__ */ new AlwatrSignal<{event: ServiceWorkerEvent}>({
name: 'serviceWorker',
});

/**
* Register the service worker and handle updates.
*
* @param serviceWorkerPath The path to the service worker.
*
* @example
* import {registerServiceWorker} from '@nexim/service-worker';
*
* const serviceWorkerPath = '/service-worker.js';
* registerServiceWorker(serviceWorkerPath);
*/
export async function registerServiceWorker(serviceWorkerPath: string): Promise<void> {
logger.logMethodArgs?.('registerServiceWorker', {serviceWorkerPath});

try {
const swRegistration = await navigator.serviceWorker.register(serviceWorkerPath);
serviceWorkerSignal.notify({event: 'service_worker_registered'});
swRegistration.addEventListener('updatefound', () => serviceWorkerUpdateFoundHandler(swRegistration.installing));
logger.logOther?.('Service worker registered.');
}
catch (error) {
logger.error('registerServiceWorker', 'registration_failed ', {error});
serviceWorkerSignal.notify({event: 'service_worker_register_failed'});
}
}

/**
* Handle the 'updatefound' event
*
* @param serviceWorker The service worker
*/
function serviceWorkerUpdateFoundHandler(serviceWorker: ServiceWorker | null): void {
if (serviceWorker == null) return;
logger.logMethod?.('swUpdateFound');

// Only notify update found if there's an existing controller
if (navigator.serviceWorker.controller) {
serviceWorkerSignal.notify({event: 'service_worker_update_found'});
}
else {
serviceWorkerSignal.notify({event: 'service_worker_first_install'});
}

serviceWorker.addEventListener('statechange', () => serviceWorkerStateChangeHandler(serviceWorker));
}

/**
* Handle the 'statechange' event.
*
* If the service worker state is 'installed' then it is either a new install or an update.
* If the service worker state is 'redundant' then the service worker is redundant.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/state
*
* @param serviceWorker The service worker.
*/
function serviceWorkerStateChangeHandler(serviceWorker: ServiceWorker): void {
logger.logMethodArgs?.('serviceWorkerStateChangeHandler', {state: serviceWorker.state});

if (serviceWorker.state === 'installed') {
// if old controller available then its update else its new install
if (navigator.serviceWorker.controller) {
serviceWorkerSignal.notify({event: 'service_worker_updated'});
}
else {
serviceWorkerSignal.notify({event: 'service_worker_installed'});
}
}
else if (serviceWorker.state === 'redundant') {
logger.accident('serviceWorkerStateChangeHandler', 'sw_redundant', 'Service worker redundant');
serviceWorkerSignal.notify({event: 'service_worker_installed'});
}
}
12 changes: 12 additions & 0 deletions packages/service-worker/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "@nexim/typescript-config/tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"emitDeclarationOnly": true,
"composite": true,
"types": ["@alwatr/nano-build", "@alwatr/type-helper"]
},
"include": ["src/**/*.ts"],
"references": []
}
2 changes: 1 addition & 1 deletion packages/snackbar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"directory": "packages/snackbar"
},
"license": "AGPL-3.0-only",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (www.njfamirm.ir)",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (https://www.njfamirm.ir)",
"contributors": [
"Arash Ghardashpoor <arash.qardashpoor@gmail.com> (https://www.agpagp.ir)"
],
Expand Down
15 changes: 7 additions & 8 deletions packages/snackbar/src/lib/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import {waitForTimeout} from '@alwatr/wait';

import type {SnackbarComponent} from './element.js';

const logger = createLogger(`${__package_name__}/handler`);
const logger = /* @__PURE__ */ createLogger(`${__package_name__}/handler`);

/**
* @property content - Content to be displayed in the snackbar.
* @property {action} - The action button configuration.
* @property action.label - The label for the action button.
* @property action.handler - The handler function for the action button.
* @property duration - Duration for which the snackbar is displayed. `-1` for infinite duration.
* @property duration - Duration for which the snackbar is displayed. `infinite` for infinite duration.
* Duration for which the snackbar is displayed.
* `-1` for infinite duration.
* `infinite` for infinite duration.
* @property addCloseButton - Whether to add a close button to the snackbar.
*/
export type SnackbarOptions = {
Expand All @@ -23,7 +23,7 @@ export type SnackbarOptions = {
label: string;
handler: () => void;
};
duration?: Duration;
duration?: Duration | 'infinite';
addCloseButton?: boolean;
};

Expand Down Expand Up @@ -53,7 +53,7 @@ export const snackbarActionButtonClickedSignal = new AlwatrTrigger({
* addCloseButton: true,
* });
*/
export const snackbarSignal = new AlwatrSignal<SnackbarOptions>({name: 'snackbar'});
export const snackbarSignal = /* @__PURE__ */ new AlwatrSignal<SnackbarOptions>({name: 'snackbar'});

// Subscribe to the snackbar signal to show the snackbar when the signal is emitted.
snackbarSignal.subscribe((options) => {
Expand All @@ -71,10 +71,9 @@ async function showSnackbar(options: SnackbarOptions): Promise<void> {
logger.logMethodArgs?.('showSnackbar', {options});

// Parse the duration
if (options.duration != null) options.duration = parseDuration(options.duration);

// Set default duration if not provided
options.duration = parseDuration('4s');
options.duration ??= '4s';

const element = document.createElement('snack-bar') as SnackbarComponent;

Expand Down Expand Up @@ -111,7 +110,7 @@ async function showSnackbar(options: SnackbarOptions): Promise<void> {
document.body.appendChild(element);

// Set a timeout to close the snackbar if duration is not infinite
if (options.duration !== -1) {
if (options.duration !== 'infinite') {
waitForTimeout(parseDuration(options.duration)).then(closeSnackbar_);
}
}
2 changes: 1 addition & 1 deletion packages/typescript-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"directory": "packages/typescript-config"
},
"license": "AGPL-3.0-only",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (www.njfamirm.ir)",
"author": "S. Amir Mohammad Najafi <njfamirm@gmail.com> (https://www.njfamirm.ir)",
"contributors": [
"Arash Ghardashpoor <arash.qardashpoor@gmail.com> (https://www.agpagp.ir)"
],
Expand Down
6 changes: 6 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
},
{
"path": "./packages/alpine"
},
{
"path": "./packages/service-worker"
},
{
"path": "./packages/snackbar"
}
]
}
Loading
Loading