@osimatic/helpers-js 1.5.34 → 1.5.36
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 +1 -1
- package/tests/user.test.js +8 -0
- package/web_push_notification.js +249 -0
package/package.json
CHANGED
package/tests/user.test.js
CHANGED
|
@@ -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 };
|