PageSourceSearch

https://www.clm-community.eu/serviceworker.js

js clm-community.eu collected 2026-09-25 18:38:31 UTC 16,555 bytes, 572 lines download raw bytes

1
2
3
4
5importScripts(
6  'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js'
7);
8
9self.addEventListener("install", event => {
10  console.log("installing service worker")
11});
12
13self.addEventListener('activate', event => {
14  console.log("activating service worker")
15});
16
17
18function buildNotification(comment, ...comments) {
19  let allComments = [comment];
20  for (let i = 0; i < comments.length; i++) {
21    let subComments = comments[i];
22    for (let j = 0; j < subComments.length; j++) {
23      let subComment = subComments[j];
24      if (subComment.data.data.id == comment.data.data.id) {
25        allComments.push(comment);
26        allComments = allComments.slice(1);
27      } else {
28        allComments.push(subComment);
29      }
30    }
31  }
32  if (allComments.length == 1) {
33    return allComments[0]
34  }
35  let currentUser = "";
36  let response_urls = [];
37  let groupedComments = [];
38  for (let i = allComments.length - 1; i >= 0; i--) {
39    let currentComment = allComments[i];
40    if (currentUser != currentComment.data.data.user) {
41      currentUser = currentComment.data.data.user;
42      groupedComments.push({user: currentUser, comments: []})
43      let dateCreated = new Date(currentComment.data.data.date_created);
44    }
45    groupedComments[groupedComments.length-1].comments.push(currentComment)
46    if (currentComment.data.response_url) {
47      response_urls.push(currentComment.data.response_url);
48    }
49  }
50
51  body = 'Latest comments:'
52  for (let i = groupedComments.length - 1; i >= 0; i--) {
53    let userComments = groupedComments[i].comments
54    let dateCreated = new Date(userComments[userComments.length-1].data.data.date_created);
55    body += `\n${groupedComments[i].user} at ${dateCreated.toLocaleTimeString()}`;
56    for (let j = 0; j < userComments.length; j++) {
57      body += `\n    ${userComments[j].body}`;
58    }
59  }
60
61  let notificationData = {
62    body: body,
63    icon: comment.icon ? comment.icon : "https://www.clm-community.eu/media/static/images/logos/logo_160x160.png",
64    tag: comment.tag,
65    requireInteraction: true,
66    renotify: true,
67    actions: comment.actions,
68    data: {
69      url: comment.data.url,
70      comments: allComments,
71      response_urls: response_urls,
72      subject: `${allComments.length} new comments in #${comment.data.data.channel.channel_id}: ${comment.data.data.channel.display_name}`
73    }
74  }
75  return notificationData;
76}
77
78function runningInAndroidChrome() {
79  // check if we are running in android chrome
80  return navigator.userAgent.match(/(chrome)/ig) && navigator.userAgent.match(/(android)/ig)
81}
82
83function runningInChrome() {
84  // check if we are running in android chrome
85  return navigator.userAgent.match(/(chrome)/ig)
86}
87
88self.addEventListener('push', function(event) {
89
90  var payload = event.data.text();
91
92  let data = JSON.parse(payload);
93
94  let tag = data.channel ? "channel-" + data.channel.channel_id : data.id;
95  let icon = data.icon ? data.icon : "https://www.clm-community.eu/media/static/images/logos/logo_160x160.png";
96
97  if ((!data.unread) || (data.action == "delete")) {
98
99    event.waitUntil(
100      self.registration.getNotifications().then(
101        function(allNotifications){
102          return self.registration.getNotifications({tag: tag}).then(
103            function(notifications){
104              if (allNotifications.length > notifications.length) {
105                notifications.forEach(n => n.close());
106                let notification = allNotifications.filter(n => n.tag != tag)[0]
107                notification.data.notificationData.renotify = false;
108                return self.registration.showNotification(
109                  notification.data.subject, notification.data.notificationData
110                )
111              } else if (notifications.length > 0) {
112                if (runningInAndroidChrome()) {
113                  // keep the notification to avoid unwanted popups
114                  let notification = notifications[0];
115                  notifications.forEach(n => n.close());
116                  notification.data.notificationData.tag = "dismiss";
117                  notification.data.notificationData.renotify = false;
118                  notification.data.notificationData.silent = true;
119                  return self.registration.showNotification(
120                    notification.data.subject, notification.data.notificationData
121                  )
122                } else {
123                  notifications.forEach(n => n.close());
124                }
125              } else {
126                if (runningInAndroidChrome()) {
127                  return self.registration.showNotification(
128                    "", {tag: "dismiss", icon: icon, silent: true}
129                  )
130                }
131              }
132            }
133          )
134        }
135      )
136    )
137  } else if (data.unread) {
138    // Retrieve a list of the clients of this service worker.
139    event.waitUntil(
140      self.clients.matchAll().then(function(clientList) {
141        // Check if there's at least one focused client.
142
143        var notificationData = {
144          body: data.body,
145          icon: icon,
146          tag: tag,
147          requireInteraction: true,
148          actions: [],
149          renotify: true,
150          data: {
151            url: data.url ? data.url: self.location.origin,
152            subject: data.subject,
153            data: data
154          }
155        }
156        notificationData.data.notificationData = notificationData;
157        if (data.response_url) {
158          notificationData.actions.push({
159            action: "markAsRead",
160            title: "Mark as read"
161          });
162          notificationData.actions.push({
163            action: "delete",
164            title: "Delete notification"
165          });
166          notificationData.data.response_url = data.response_url;
167        }
168
169        var focused = clientList.some(function(client) {
170          return client.focused;
171        });
172
173        if (!focused) {
174          if ((data.action == "create") || (data.action == "update")) {
175            return self.registration.getNotifications({tag: notificationData.tag}).then(
176              (notifications) => {
177                notificationData = buildNotification(
178                  notificationData, ...notifications.map(n => n.data.comments ? n.data.comments.map(c => c.data.notificationData) : [n.data.notificationData])
179                )
180                if ((!runningInAndroidChrome()) && (runningInChrome())) {
181                  // close the notifications because the actions in desktop
182                  // chrome will not appear again otherwise
183                  notifications.forEach(n => n.close());
184                }
185                return self.registration.showNotification(
186                  notificationData.data.subject, notificationData
187                ).then(
188                  () => self.registration.getNotifications({tag: "dismiss"})
189                ).then(
190                  notifications => notifications.forEach(n => n.close())
191                )
192              }
193            )
194          }
195        }
196      })
197    )
198  }
199});
200
201// Register event listener for the 'notificationclick' event.
202self.addEventListener('notificationclick', function(event) {
203  if (event.action) {
204    var body = {};
205    var method = "PATCH";
206    if (event.action == "markAsRead") {
207      body.unread = false;
208    } else if (event.action == "delete") {
209      method = "DELETE";
210    }
211    if (event.notification.data.response_urls) {
212      event.waitUntil(
213        Promise.all(
214          event.notification.data.response_urls.map(
215            uri => fetch(uri, {
216              method: method,
217              headers: {
218                'Content-Type': 'application/json'
219              },
220              body: JSON.stringify(body)
221            })
222          )
223        ).then((response) => {
224          event.notification.close();
225        }).catch(error => {
226          console.error(error);
227        })
228      )
229    } else {
230      event.waitUntil(
231        fetch(event.notification.data.response_url, {
232          method: method,
233          headers: {
234            'Content-Type': 'application/json'
235          },
236          body: JSON.stringify(body)
237        }).then((response) => {
238          event.notification.close();
239        }).catch(error => {
240          console.error(error);
241        })
242      )
243    }
244  } else {
245    event.waitUntil(
246      // Retrieve a list of the clients of this service worker.
247      self.clients.matchAll({type: "window"}).then(function(clientList) {
248        // If there is at least one client, focus it.
249        if (clientList.length > 0) {
250          event.notification.close();
251          clientList[0].navigate(event.notification.data.url);
252          return clientList[0].focus();
253        }
254
255        // Otherwise, open a new page.
256        return self.clients.openWindow(event.notification.data.url);
257      })
258    );
259  }
260});
261
262
263
264importScripts('/static/js/e2ee/utils.js');
265importScripts('https://cdn.jsdelivr.net/npm/idb-keyval@6/dist/umd.js');
266
267
268async function handleMasterKeyPost({ url, request, event, params }) {
269
270  let body = await request.json();
271
272  let salt = crypto.getRandomValues(new Uint8Array(16))
273
274  return generateKeyFromPassword(body.password, salt).then(
275    async (wrappingKey) => {
276      let masterKey = new MasterKey(
277        request.headers,
278        "/e2ee/rest/"
279      )
280      return masterKey.uploadPublicKeys().then(
281        async (response) => {
282          if (response.status == 201) {
283            return masterKey.uploadPrivateKeys(
284              "/e2ee/rest/master_keysecrets/",
285              wrappingKey,
286              "POST",
287              {
288                salt: arrayBufferToBase64(salt),
289                identifier: body.identifier
290              }
291            )
292          } else {
293            return response;
294          }
295        }
296      ).then(
297        async (response) => {
298          if (response.status == 201) {
299            let sessionKey = await generateAESKey(["wrapKey", "unwrapKey"]);
300
301            return idbKeyval.set("sessionKey", sessionKey).then(
302              async () => masterKey.uploadPrivateKeys(
303                '/e2ee/rest/sessionkeys/',
304                sessionKey,
305                "POST"
306              )
307            )
308          } else {
309            return response
310          }
311        }
312      )
313    }
314  )
315
316}
317
318
319async function handleMasterKeySecretPost({ url, request, event, params }) {
320
321  let body = await request.json();
322
323  let salt = crypto.getRandomValues(new Uint8Array(16))
324
325  return generateKeyFromPassword(body.password, salt).then(
326    async (wrappingKey) => {
327      let masterKey = new MasterKeyFromSession(
328        request.headers,
329        "/e2ee/rest/"
330      );
331      return masterKey.uploadPrivateKeys(
332        "/e2ee/rest/master_keysecrets/",
333        wrappingKey,
334        "POST",
335        {
336          salt: arrayBufferToBase64(salt),
337          identifier: body.identifier
338        }
339      )
340    }
341  )
342}
343
344
345async function handleSessionKeyPost({ url, request, event, params }) {
346
347  var masterKey;
348
349  // get the public key and encrypt the sessionKey
350  let body = await request.json();
351  let sessionKey = await generateAESKey(["wrapKey", "unwrapKey"]);
352
353  if (body.ignore) {
354    return fetch(
355      url, {
356        method: "POST",
357        headers: request.headers,
358        body: JSON.stringify({ignore: true})
359      }
360    )
361  }
362
363  return idbKeyval.set("sessionKey", sessionKey).then(
364    async function () {
365      if (body.password) {
366        masterKey = new MasterKeyFromPassword(
367          request.headers,
368          "/e2ee/rest/",
369          body.password,
370          body.uuid
371        )
372
373        return masterKey.uploadPrivateKeys(
374          url, sessionKey, "POST"
375        )
376      } else {
377        // publish encrypted session secret to the server
378        masterKey = new MasterKeyFromSession(
379          request.headers,
380          "/e2ee/rest/"
381        );
382        return masterKey.wrapAESKey(sessionKey).then(
383          async (sessionSecret) => {
384
385            return fetch(
386              url,
387              {
388                method: 'POST',
389                headers: request.headers,
390                body: JSON.stringify({
391                  session_secret: arrayBufferToBase64(sessionSecret)
392                })
393              }
394            ).then(async function (response) {
395              let randomNumber = twoRandomDigits();
396              return fetch("/static/js/e2ee/passwords.json").then(
397                async passwords => new Response(
398                  JSON.stringify({
399                    verificationNumber: await getVerificationNumber(
400                      randomNumber, passwords[randomNumber], sessionSecret
401                    ),
402                    session_secret: arrayBufferToBase64(sessionSecret)
403                  }),
404                  {
405                    headers: response.headers,
406                    status: response.status,
407                    statusText: response.statusText
408                  }
409
410                )
411              )
412            })
413          }
414        )
415      }
416    }
417  )
418}
419
420async function handleForeignSessionKeyPatch({ url, request, event, params }) {
421
422  let body = await request.json();
423
424  // first validate the verificationNumber and then create the secret
425
426  return fetch("/static/js/e2ee/passwords.json").then(
427    async function (passwords) {
428
429      let requestValid = await verifyRequest(
430        body.verificationNumber, passwords, base64ToArrayBuffer(body.session_secret)
431      )
432      if (!requestValid) {
433        return new Response(
434          '{"error": "Invalid verification number"}',
435          {statusCode: 400, statusText: "Bad Request"}
436        )
437      }
438
439      var masterKey;
440
441      if (body.password) {
442        masterKey = new MasterKeyFromPassword(
443          request.headers,
444          "/e2ee/rest/",
445          body.password,
446          body.uuid
447        );
448      } else {
449        masterKey = new MasterKeyFromSession(
450          request.headers,
451          "/e2ee/rest/"
452        );
453      }
454
455      return masterKey.unwrapSessionKey(
456        body.session_secret
457      ).then(
458        async (sessionKey) => {
459          return masterKey.uploadPrivateKeys(
460            url, sessionKey, "PATCH", { session_secret: null }
461          )
462        }
463      )
464    }
465  )
466}
467
468async function handleEncryptionPost({ url, request, event, params }) {
469  let body = await request.json();
470
471  if (typeof (body.message) === "undefined") {
472    return new Response(
473      '{"error": "No message to encrypt."}',
474      {statusCode: 400, statusText: "Bad Request"}
475    )
476  }
477
478  let encryptionKey;
479  if (body.encryption_key) {
480    encryptionKey = new ExistingEncryptionKey(
481      request.headers, "/e2ee/rest/", body.encryption_key
482    );
483
484  } else {
485    encryptionKey = new EncryptionKey(
486      request.headers, "/e2ee/rest/"
487    );
488    await encryptionKey.createKey();
489  }
490
491  body = await encryptionKey.encryptAndSignString(
492    body.message
493  );
494  body.encryption_key = encryptionKey.uuid;
495
496  return new Response(
497    JSON.stringify(body),
498    {statusCode: 200, statusText: "OK"}
499  )
500}
501
502async function handleDecryptionPost({ url, request, event, params }) {
503  let body = await request.json();
504
505  if (typeof (body.message) === "undefined") {
506    return new Response(
507      '{"error": "No message to decrypt."}',
508      { statusCode: 400, statusText: "Bad Request" }
509    )
510  } else if (typeof (body.encryption_key) === "undefined") {
511    return new Response(
512      '{"error": "No encryption key specified."}',
513      { statusCode: 400, statusText: "Bad Request" }
514    )
515  }
516
517  let encryptionKey = new ExistingEncryptionKey(
518    request.headers, "/e2ee/rest/", body.encryption_key
519  );
520
521  return encryptionKey.decryptAndVerfiyString(
522    body.message, body.signature, body.signed_by
523  ).then(
524    message => new Response(
525      JSON.stringify({ message: message }),
526      {statusCode: 200, statusText: "OK"}
527    )
528  ).catch((err) => new Response(
529    JSON.stringify(err),
530    {statusCode: 400, statusText: "Bad Request"}
531  ))
532}
533
534
535workbox.routing.registerRoute(
536  new RegExp('/e2ee/rest/master_keys/$'),
537  handleMasterKeyPost,
538  "POST"
539);
540
541
542workbox.routing.registerRoute(
543  new RegExp('/e2ee/rest/master_keysecrets/$'),
544  handleMasterKeySecretPost,
545  "POST"
546);
547
548
549workbox.routing.registerRoute(
550  new RegExp('/e2ee/rest/sessionkeys/$'),
551  handleSessionKeyPost,
552  "POST"
553);
554
555workbox.routing.registerRoute(
556  new RegExp('/e2ee/rest/sessionkeys/.+/$'),
557  handleForeignSessionKeyPatch,
558  "PATCH"
559);
560
561workbox.routing.registerRoute(
562  new RegExp('/e2ee/rest/encrypt/'),
563  handleEncryptionPost,
564  "POST"
565);
566
567workbox.routing.registerRoute(
568  new RegExp('/e2ee/rest/decrypt/'),
569  handleDecryptionPost,
570  "POST"
571);
572

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.