@cocreate/notification 1.0.0 → 1.1.0
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/CHANGELOG.md +19 -0
- package/package.json +3 -2
- package/src/client.js +19 -31
- package/src/server.js +116 -34
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# [1.1.0](https://github.com/CoCreate-app/CoCreate-notification/compare/v1.0.1...v1.1.0) (2023-10-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* generates Vapid keys per client and returns publicKey to client, the public key is used to create a subscription object and return to server ([0267c19](https://github.com/CoCreate-app/CoCreate-notification/commit/0267c1904a6e6d03c73e4c767cb39f26287776d9))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Use subscription.endpoint to get the apikey ([5fdb66d](https://github.com/CoCreate-app/CoCreate-notification/commit/5fdb66d52ca996ca096f3c935a75bd98c812a9cc))
|
|
12
|
+
|
|
13
|
+
## [1.0.1](https://github.com/CoCreate-app/CoCreate-notification/compare/v1.0.0...v1.0.1) (2023-09-24)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* add web-push in order to generate Vapid keys ([cffd320](https://github.com/CoCreate-app/CoCreate-notification/commit/cffd3202b28722cb930b2be18370b1c835c6e2b2))
|
|
19
|
+
|
|
1
20
|
# 1.0.0 (2023-09-24)
|
|
2
21
|
|
|
3
22
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/notification",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Simple HTML5 & JavaScript component add, update & remove values in element's notification from input, select or js api. Easily configured using HTML5 notification and/or JavaScript API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cocreate",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@cocreate/actions": "^1.11.2",
|
|
61
|
-
"@cocreate/socket-client": "^1.28.0"
|
|
61
|
+
"@cocreate/socket-client": "^1.28.0",
|
|
62
|
+
"web-push": "^3.6.6"
|
|
62
63
|
}
|
|
63
64
|
}
|
package/src/client.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import socket from '@cocreate/socket-client'
|
|
2
2
|
// import actions from '@cocreate/actions'
|
|
3
3
|
|
|
4
|
-
// Request push notification permission
|
|
5
|
-
const permission = await Notification.requestPermission();
|
|
6
|
-
if (permission === 'granted') {
|
|
7
|
-
// User granted push notification permission, handle it here
|
|
8
|
-
subscribeToPushNotifications();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
4
|
async function handlePushPermission() {
|
|
13
5
|
// Request push notification permission
|
|
14
6
|
const permission = await Notification.requestPermission();
|
|
@@ -18,14 +10,9 @@ async function handlePushPermission() {
|
|
|
18
10
|
const registration = await navigator.serviceWorker.ready;
|
|
19
11
|
try {
|
|
20
12
|
// Create a new subscription or retrieve the existing one
|
|
21
|
-
|
|
13
|
+
let subscription = await registration.pushManager.getSubscription();
|
|
22
14
|
|
|
23
|
-
if (
|
|
24
|
-
// Update the existing subscription if needed
|
|
25
|
-
// This might involve generating a new public key or other changes
|
|
26
|
-
// For this example, we'll assume no updates are needed
|
|
27
|
-
console.log('Subscription already exists:', existingSubscription);
|
|
28
|
-
} else {
|
|
15
|
+
if (!subscription) {
|
|
29
16
|
//TODO: getPublicKey
|
|
30
17
|
let response = await socket.send({
|
|
31
18
|
method: 'notification.publicKey',
|
|
@@ -35,31 +22,31 @@ async function handlePushPermission() {
|
|
|
35
22
|
return
|
|
36
23
|
|
|
37
24
|
// Create a new subscription
|
|
38
|
-
|
|
25
|
+
subscription = await registration.pushManager.subscribe({
|
|
39
26
|
userVisibleOnly: true,
|
|
40
27
|
applicationServerKey: response.publicKey,
|
|
41
28
|
});
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
method: 'notification.subscription',
|
|
45
|
-
subscription: newSubscription
|
|
46
|
-
})
|
|
30
|
+
}
|
|
47
31
|
|
|
48
|
-
|
|
32
|
+
await socket.send({
|
|
33
|
+
method: 'notification.subscription',
|
|
34
|
+
subscription: subscription
|
|
35
|
+
})
|
|
49
36
|
|
|
50
|
-
|
|
51
|
-
// Handle the subscription change here
|
|
52
|
-
const newSubscription = event.newSubscription;
|
|
53
|
-
const oldSubscription = event.oldSubscription;
|
|
37
|
+
console.log('New subscription created:', subscription);
|
|
54
38
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
39
|
+
navigator.serviceWorker.addEventListener('pushsubscriptionchange', (event) => {
|
|
40
|
+
// Handle the subscription change here
|
|
41
|
+
const newSubscription = event.newSubscription;
|
|
42
|
+
const oldSubscription = event.oldSubscription;
|
|
59
43
|
|
|
60
|
-
|
|
44
|
+
socket.send({
|
|
45
|
+
method: 'notification.subscription',
|
|
46
|
+
subscription: subscription
|
|
47
|
+
})
|
|
61
48
|
|
|
62
|
-
}
|
|
49
|
+
});
|
|
63
50
|
} catch (error) {
|
|
64
51
|
console.error('Error handling push subscription:', error);
|
|
65
52
|
}
|
|
@@ -77,3 +64,4 @@ document.querySelector('[actions*="notification.subscribe"]').addEventListener('
|
|
|
77
64
|
// endEvent: "notification",
|
|
78
65
|
// callback: (action) => handlePushPermission
|
|
79
66
|
// });
|
|
67
|
+
export default {}
|
package/src/server.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
2
|
const https = require('https');
|
|
3
|
+
const webpush = require('web-push');
|
|
3
4
|
|
|
4
5
|
class CoCreateNotification {
|
|
5
6
|
constructor(crud) {
|
|
6
|
-
this.
|
|
7
|
+
this.socket = crud.wsManager
|
|
7
8
|
this.crud = crud
|
|
8
9
|
this.newKeyMap = new Map()
|
|
9
10
|
this.subscriptions = new Map()
|
|
@@ -11,10 +12,10 @@ class CoCreateNotification {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
init() {
|
|
14
|
-
if (this.
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
15
|
+
if (this.socket) {
|
|
16
|
+
this.socket.on('notification.publicKey', (data) => this.publicKey(data));
|
|
17
|
+
this.socket.on('notification.subscription', (data) => this.subscription(data));
|
|
18
|
+
this.socket.on('notification.send', (data) => this.send(data));
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -22,52 +23,100 @@ class CoCreateNotification {
|
|
|
22
23
|
publicKey(data) {
|
|
23
24
|
let subscription = this.subscriptions.has(data.clientId)
|
|
24
25
|
if (!subscription) {
|
|
25
|
-
let newKeys = this.generateVapidKeys()
|
|
26
|
-
|
|
27
|
-
data.
|
|
28
|
-
} else {
|
|
29
|
-
data.publicKey = subscription.publicKey
|
|
26
|
+
// let newKeys = this.generateVapidKeys()
|
|
27
|
+
subscription = webpush.generateVAPIDKeys()
|
|
28
|
+
this.newKeyMap.set(data.clientId, subscription)
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
console.log('subscription: ', subscription)
|
|
33
32
|
|
|
33
|
+
data.publicKey = subscription.publicKey
|
|
34
|
+
if (data.socket)
|
|
35
|
+
this.socket.send(data)
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
async subscription(data) {
|
|
37
39
|
let newKeys = this.newKeyMap.get(data.clientId)
|
|
38
40
|
if (newKeys) {
|
|
39
41
|
this.newKeyMap.delete(data.clientId)
|
|
42
|
+
|
|
40
43
|
this.subscriptions.set(data.clientId, { ...newKeys, ...data.subscription })
|
|
41
44
|
} else {
|
|
42
45
|
newKeys = this.subscriptions.get(data.clientId)
|
|
43
46
|
if (newKeys) {
|
|
44
47
|
newKeys = { ...newKeys, ...data.subscription }
|
|
45
48
|
} else {
|
|
46
|
-
newKeys = {
|
|
49
|
+
newKeys = await this.crud.send({
|
|
50
|
+
method: 'read.object',
|
|
51
|
+
array: 'clients',
|
|
52
|
+
object: {
|
|
53
|
+
_id: data.clientId
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
if (newKeys && newKeys.object && newKeys.object[0]) {
|
|
57
|
+
newKeys = newKeys.object[0]
|
|
58
|
+
newKeys = { ...newKeys, ...data.subscription }
|
|
59
|
+
this.subscriptions.set(data.clientId, { ...newKeys, ...data.subscription })
|
|
60
|
+
} else {
|
|
61
|
+
this.publicKey(data)
|
|
62
|
+
this.subscription(data)
|
|
63
|
+
}
|
|
47
64
|
}
|
|
48
|
-
}
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
method: 'update.object',
|
|
52
|
-
array: 'keys',
|
|
53
|
-
object: {
|
|
54
|
-
...data.subscription,
|
|
55
|
-
...newKeys
|
|
56
|
-
}
|
|
57
|
-
});
|
|
66
|
+
}
|
|
58
67
|
|
|
68
|
+
if (newKeys) {
|
|
69
|
+
let tokenOptions = {
|
|
70
|
+
vapidDetails: {
|
|
71
|
+
subject: 'mailto:contact@cocreate.app',
|
|
72
|
+
publicKey: newKeys.publicKey,
|
|
73
|
+
privateKey: newKeys.privateKey
|
|
74
|
+
},
|
|
75
|
+
// Other optional options can be included here
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const jwt = webpush.generateRequestDetails(data.subscription, null, tokenOptions);
|
|
79
|
+
|
|
80
|
+
this.crud.send({
|
|
81
|
+
method: 'update.object',
|
|
82
|
+
array: 'clients',
|
|
83
|
+
object: {
|
|
84
|
+
_id: data.clientId,
|
|
85
|
+
...data.subscription,
|
|
86
|
+
...newKeys,
|
|
87
|
+
jwt
|
|
88
|
+
},
|
|
89
|
+
upsert: true,
|
|
90
|
+
organization_id: data.organization_id
|
|
91
|
+
});
|
|
92
|
+
}
|
|
59
93
|
};
|
|
60
94
|
|
|
61
95
|
// Load the client's subscription object from storage
|
|
62
|
-
send(data) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
96
|
+
async send(data) {
|
|
97
|
+
let subscription = this.subscriptions.get(data.clientId)
|
|
98
|
+
if (!subscription) {
|
|
99
|
+
try {
|
|
100
|
+
subscription = await this.crud.send({
|
|
101
|
+
method: 'read.object',
|
|
102
|
+
array: 'clients',
|
|
103
|
+
object: {
|
|
104
|
+
_id: data.clientId
|
|
105
|
+
},
|
|
106
|
+
organization_id: data.organization_id
|
|
107
|
+
|
|
108
|
+
})
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.log(error)
|
|
111
|
+
}
|
|
112
|
+
if (subscription && subscription.object && subscription.object[0])
|
|
113
|
+
subscription = subscription.object[0]
|
|
114
|
+
}
|
|
66
115
|
if (!subscription || !subscription.privateKey)
|
|
67
116
|
return
|
|
68
117
|
|
|
69
118
|
const cutoffDate = new Date();
|
|
70
|
-
cutoffDate.setDate(cutoffDate.getDate() -
|
|
119
|
+
cutoffDate.setDate(cutoffDate.getDate() - 90);
|
|
71
120
|
|
|
72
121
|
// Compare the modification time with the cutoff date
|
|
73
122
|
if (!this.newKeyMap.has(data.clientId) && subscription.privateKeyCreatedOn < cutoffDate) {
|
|
@@ -78,15 +127,27 @@ class CoCreateNotification {
|
|
|
78
127
|
if (data.payload && !data.payload.timestamp)
|
|
79
128
|
data.payload.timestamp = Date.now()
|
|
80
129
|
|
|
81
|
-
let payload =
|
|
82
|
-
delete payload.privateKey
|
|
83
|
-
delete payload.publicKey
|
|
84
|
-
|
|
130
|
+
// let payload = data.payload
|
|
131
|
+
// delete payload.privateKey
|
|
132
|
+
// delete payload.publicKey
|
|
133
|
+
|
|
134
|
+
let tokenOptions = {
|
|
135
|
+
vapidDetails: {
|
|
136
|
+
subject: 'mailto:contact@cocreate.app',
|
|
137
|
+
publicKey: subscription.publicKey,
|
|
138
|
+
privateKey: subscription.privateKey
|
|
139
|
+
},
|
|
140
|
+
// Other optional options can be included here
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const jwt = webpush.generateRequestDetails(subscription.endpoint, payload, tokenOptions);
|
|
144
|
+
const apiKey = getAPIKeyForEndpoint(subscrption.endpoint);
|
|
145
|
+
|
|
85
146
|
|
|
86
147
|
const options = {
|
|
87
148
|
method: 'POST',
|
|
88
149
|
headers: {
|
|
89
|
-
'Authorization': `Bearer ${
|
|
150
|
+
'Authorization': `Bearer ${apiKey || jwt.headers.Authorization}`,
|
|
90
151
|
'Content-Type': 'application/json',
|
|
91
152
|
},
|
|
92
153
|
};
|
|
@@ -103,6 +164,26 @@ class CoCreateNotification {
|
|
|
103
164
|
pushRequest.end();
|
|
104
165
|
}
|
|
105
166
|
|
|
167
|
+
getAPIKeyForEndpoint(endpoint) {
|
|
168
|
+
// Define the mapping of browser endpoints to API keys
|
|
169
|
+
const browserEndpointsAndAPIKeys = {
|
|
170
|
+
'https://fcm.googleapis.com/': 'yourFCMApiKey',
|
|
171
|
+
'https://updates.push.services.mozilla.com/wpush/v2/': 'yourMozillaPushServiceApiKey',
|
|
172
|
+
'https://api.push.apple.com/': 'yourAPNsApiKey', // For Safari on Mac
|
|
173
|
+
'https://api.sandbox.push.apple.com/': 'yourAPNsDevApiKey', // For Safari on iOS (Development)
|
|
174
|
+
'https://api.push.apple.com/': 'yourAPNsProdApiKey', // For Safari on iOS (Production)
|
|
175
|
+
// Add more browser endpoints and their API keys as needed
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Iterate through the keys in the mapping
|
|
179
|
+
for (const browserEndpoint of Object.keys(browserEndpointsAndAPIKeys)) {
|
|
180
|
+
if (endpoint.startsWith(browserEndpoint)) {
|
|
181
|
+
// Return the associated API key if the endpoint starts with a known browser endpoint
|
|
182
|
+
return browserEndpointsAndAPIKeys[browserEndpoint];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
106
187
|
generateVapidKeys() {
|
|
107
188
|
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
|
|
108
189
|
modulusLength: 4096, // Choose an appropriate key length
|
|
@@ -138,10 +219,11 @@ class CoCreateNotification {
|
|
|
138
219
|
|
|
139
220
|
// Function to base64 URL encode
|
|
140
221
|
base64URLEncode(value) {
|
|
141
|
-
|
|
222
|
+
const base64 = Buffer.from(value).toString('base64');
|
|
223
|
+
return base64
|
|
142
224
|
.replace(/\+/g, '-')
|
|
143
225
|
.replace(/\//g, '_')
|
|
144
|
-
.replace(
|
|
226
|
+
.replace(/=+$/, ''); // Remove trailing equal signs
|
|
145
227
|
}
|
|
146
228
|
|
|
147
229
|
|
|
@@ -157,7 +239,7 @@ const payloadExample = {
|
|
|
157
239
|
{ action: 'action-2', title: 'Action 2' },
|
|
158
240
|
],
|
|
159
241
|
tag: 'notification-tag', // A unique identifier for the notification
|
|
160
|
-
data,
|
|
242
|
+
data: {},
|
|
161
243
|
vibrate: [100, 50, 100], // Vibration pattern (milliseconds)
|
|
162
244
|
image: '/path/to/notification-image.jpg', // An image to display within the notification
|
|
163
245
|
requireInteraction: true, // Requires user interaction to close the notification
|