@osimatic/helpers-js 1.5.34 → 1.5.35

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osimatic/helpers-js",
3
- "version": "1.5.34",
3
+ "version": "1.5.35",
4
4
  "main": "main.js",
5
5
  "scripts": {
6
6
  "test": "jest",
@@ -309,5 +309,13 @@ describe('Password', () => {
309
309
  const bar = document.querySelector('.password_strength_bar');
310
310
  expect(bar.classList.contains('bg-warning')).toBe(true);
311
311
  });
312
+
313
+ test('should not throw when input is null', () => {
314
+ expect(() => Password.displayPasswordStrength(null)).not.toThrow();
315
+ });
316
+
317
+ test('should not throw when input is undefined', () => {
318
+ expect(() => Password.displayPasswordStrength(undefined)).not.toThrow();
319
+ });
312
320
  });
313
321
  });
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Generic class for managing web push notifications.
3
+ * Handles service worker registration, push subscription lifecycle, and server-side subscription persistence.
4
+ *
5
+ * Usage:
6
+ * WebPushNotification.init({
7
+ * vapidPublicKey: '<base64url VAPID public key>',
8
+ * subscriberUrl: '<full URL to save subscription on server>',
9
+ * serviceWorkerPath: '<path to WebPushServiceWorker.js>',
10
+ * httpHeaders: { Authorization: '...' },
11
+ * });
12
+ * WebPushNotification.subscribe();
13
+ *
14
+ * The service worker must handle the 'pushsubscriptionchange' event and post a message of type 'RESUBSCRIBE' with the new subscription to the page so that this class can re-send it to the server.
15
+ *
16
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Push_API
17
+ * @see https://web.dev/articles/push-notifications-overview
18
+ */
19
+ class WebPushNotification {
20
+
21
+ // =========================================================================
22
+ // Configuration
23
+ // =========================================================================
24
+
25
+ /**
26
+ * Initialize the class with project-specific configuration.
27
+ * Must be called before any other method.
28
+ * @param {object} config
29
+ * @param {string} config.vapidPublicKey - base64url VAPID public key
30
+ * @param {string} config.subscriberUrl - full URL to save subscription on server
31
+ * @param {string} [config.unsubscribeUrl] - full URL to delete subscription on server (optional)
32
+ * @param {string} config.serviceWorkerPath - path to the service worker file
33
+ * @param {object} [config.httpHeaders={}] - HTTP headers for authenticated requests
34
+ */
35
+ static init(config) {
36
+ this._vapidPublicKey = config.vapidPublicKey;
37
+ this._subscriberUrl = config.subscriberUrl;
38
+ this._unsubscribeUrl = config.unsubscribeUrl || null;
39
+ this._serviceWorkerPath = config.serviceWorkerPath;
40
+ this._httpHeaders = config.httpHeaders || {};
41
+ this._messageListenerRegistered = false;
42
+ }
43
+
44
+ // =========================================================================
45
+ // Public API
46
+ // =========================================================================
47
+
48
+ /**
49
+ * Check whether all requirements for push notifications are met:
50
+ * 1. must run in secure context (window.isSecureContext = true)
51
+ * 2. browser must implement navigator.serviceWorker, window.PushManager, window.Notification
52
+ * @returns {boolean}
53
+ */
54
+ static isAvailable() {
55
+ if (!window.isSecureContext) {
56
+ console.log('WebPushNotification: site must run in secure context!');
57
+ return false;
58
+ }
59
+ return (('serviceWorker' in navigator) &&
60
+ ('PushManager' in window) &&
61
+ ('Notification' in window));
62
+ }
63
+
64
+ /**
65
+ * Full subscription flow:
66
+ * - requests user permission if not already granted
67
+ * - registers the service worker
68
+ * - subscribes via PushManager (reuses existing subscription if any)
69
+ * - saves the subscription on the server
70
+ * - sets up the message listener for pushsubscriptionchange
71
+ * @returns {Promise<object>} server response
72
+ */
73
+ static async subscribe() {
74
+ if (!this.isAvailable()) return;
75
+
76
+ if ('Notification' in window && window.Notification.permission === 'default') {
77
+ await window.Notification.requestPermission();
78
+ }
79
+ if ('Notification' in window && window.Notification.permission !== 'granted') return;
80
+
81
+ const swReg = await this._registerServiceWorker();
82
+ if (!swReg) return;
83
+
84
+ this._initMessageListener();
85
+
86
+ // Reuse existing subscription or create a new one
87
+ var sub = await swReg.pushManager.getSubscription();
88
+ if (!sub) {
89
+ sub = await swReg.pushManager.subscribe({
90
+ applicationServerKey: this._encodeToUint8Array(this._vapidPublicKey),
91
+ userVisibleOnly: true,
92
+ });
93
+ }
94
+
95
+ return this.saveSubscription(sub);
96
+ }
97
+
98
+ /**
99
+ * Save a push subscription on the server using the Fetch API.
100
+ * The subscription is enriched with the userAgent for server-side identification.
101
+ * Called on initial subscription and on pushsubscriptionchange.
102
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription
103
+ * @param {PushSubscription} sub
104
+ * @returns {Promise<object>} server response
105
+ */
106
+ static async saveSubscription(sub) {
107
+ console.log('WebPushNotification: saveSubscription');
108
+
109
+ var headers = {...this._httpHeaders};
110
+ headers['Content-Type'] = 'application/json';
111
+
112
+ // Stringify and parse to add custom property — directly stringifying a
113
+ // PushSubscription ignores added properties
114
+ var body = JSON.parse(JSON.stringify(sub));
115
+ body.userAgent = navigator.userAgent;
116
+
117
+ var response = await fetch(this._subscriberUrl, {
118
+ method: 'post',
119
+ headers: headers,
120
+ body: JSON.stringify(body),
121
+ });
122
+ return response.json();
123
+ }
124
+
125
+ /**
126
+ * Unsubscribe from push notifications, notify the server if unsubscribeUrl is configured, then unregister the service worker.
127
+ * @returns {Promise<void>}
128
+ */
129
+ static async unsubscribe() {
130
+ if (!this.isAvailable()) return;
131
+
132
+ var reg = await navigator.serviceWorker.getRegistration();
133
+ if (reg) {
134
+ var sub = await reg.pushManager.getSubscription();
135
+ if (sub) {
136
+ if (this._unsubscribeUrl) {
137
+ await this.deleteSubscription(sub);
138
+ }
139
+ await sub.unsubscribe();
140
+ }
141
+ }
142
+
143
+ await this._unregisterServiceWorker();
144
+ }
145
+
146
+ /**
147
+ * Delete a push subscription on the server using the Fetch API.
148
+ * @param {PushSubscription} sub
149
+ * @returns {Promise<object>} server response
150
+ */
151
+ static async deleteSubscription(sub) {
152
+ console.log('WebPushNotification: deleteSubscription');
153
+
154
+ var headers = {...this._httpHeaders};
155
+ headers['Content-Type'] = 'application/json';
156
+
157
+ var body = JSON.parse(JSON.stringify(sub));
158
+ body.userAgent = navigator.userAgent;
159
+
160
+ var response = await fetch(this._unsubscribeUrl, {
161
+ method: 'delete',
162
+ headers: headers,
163
+ body: JSON.stringify(body),
164
+ });
165
+ return response.json();
166
+ }
167
+
168
+ /**
169
+ * Check whether push notifications are already subscribed.
170
+ * @returns {Promise<boolean>}
171
+ */
172
+ static async isSubscribed() {
173
+ if (!this.isAvailable()) return false;
174
+ var swReg = await navigator.serviceWorker.getRegistration();
175
+ if (!swReg) return false;
176
+ var sub = await swReg.pushManager.getSubscription();
177
+ return sub !== null;
178
+ }
179
+
180
+ // =========================================================================
181
+ // Private
182
+ // =========================================================================
183
+
184
+ /**
185
+ * Register the service worker.
186
+ * The browser handles duplicate registration — no check needed.
187
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register
188
+ * @returns {Promise<ServiceWorkerRegistration|null>}
189
+ */
190
+ static async _registerServiceWorker() {
191
+ try {
192
+ var swReg = await navigator.serviceWorker.register(this._serviceWorkerPath, {scope: '/'});
193
+ console.log('WebPushNotification: registration succeeded. Scope is ' + swReg.scope);
194
+ return swReg;
195
+ } catch (e) {
196
+ console.log('WebPushNotification: registration failed with ' + e);
197
+ return null;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Unregister the service worker.
203
+ * @returns {Promise<void>}
204
+ */
205
+ static async _unregisterServiceWorker() {
206
+ var reg = await navigator.serviceWorker.getRegistration();
207
+ if (!reg) return;
208
+ var bOK = await reg.unregister();
209
+ console.log(bOK
210
+ ? 'WebPushNotification: unregister service worker succeeded.'
211
+ : 'WebPushNotification: unregister service worker failed.'
212
+ );
213
+ }
214
+
215
+ /**
216
+ * Set up the message listener for RESUBSCRIBE messages from the service worker.
217
+ * Called once from subscribe() — guard against duplicate registration.
218
+ */
219
+ static _initMessageListener() {
220
+ if (this._messageListenerRegistered) return;
221
+ this._messageListenerRegistered = true;
222
+ navigator.serviceWorker.addEventListener('message', async (event) => {
223
+ if (event.data && event.data.type === 'RESUBSCRIBE') {
224
+ console.log('WebPushNotification: received RESUBSCRIBE message from service worker');
225
+ await WebPushNotification.saveSubscription(event.data.subscription);
226
+ }
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Encode a base64url string to a Uint8Array.
232
+ * Used to convert the VAPID public key for pushManager.subscribe().
233
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe
234
+ * @param {string} strBase64 - base64url encoded string
235
+ * @returns {Uint8Array}
236
+ */
237
+ static _encodeToUint8Array(strBase64) {
238
+ var strPadding = '='.repeat((4 - (strBase64.length % 4)) % 4);
239
+ strBase64 = (strBase64 + strPadding).replace(/-/g, '+').replace(/_/g, '/');
240
+ var rawData = atob(strBase64);
241
+ var aOutput = new Uint8Array(rawData.length);
242
+ for (let i = 0; i < rawData.length; ++i) {
243
+ aOutput[i] = rawData.charCodeAt(i);
244
+ }
245
+ return aOutput;
246
+ }
247
+ }
248
+
249
+ module.exports = { WebPushNotification };