Explorar o código

Merge pull request #4162 from overleaf/jpa-service-worker-error-reporting

[misc] serviceWorker: add error reporting using postMessage API

GitOrigin-RevId: db021d4f5cd5b7b8a0026b92d412112f60499c0b
Jessica Lawshe %!s(int64=5) %!d(string=hai) anos
pai
achega
4c6933b5b8

+ 24 - 1
services/web/frontend/js/ide/pdfng/directives/serviceWorkerManager.js

@@ -1,3 +1,6 @@
+import { captureException } from '../../../infrastructure/error-reporter'
+const OError = require('@overleaf/o-error')
+
 let pendingWorkerSetup = Promise.resolve()
 let pendingWorkerSetup = Promise.resolve()
 
 
 function supportsServiceWorker() {
 function supportsServiceWorker() {
@@ -14,6 +17,26 @@ export function loadServiceWorker() {
       .register('/serviceWorker.js', {
       .register('/serviceWorker.js', {
         scope: '/project/',
         scope: '/project/',
       })
       })
-      .catch(error => console.warn('Cannot register serviceWorker', error))
+      .then(() => {
+        navigator.serviceWorker.addEventListener('message', event => {
+          let ctx
+          try {
+            ctx = JSON.parse(event.data)
+          } catch (e) {
+            return
+          }
+          if (!ctx || !ctx.error || !ctx.extra) return
+
+          const err = OError.tag(ctx.error, 'Error in serviceWorker')
+          const fullError = new Error()
+          fullError.name = err.name
+          fullError.message = err.message
+          fullError.stack = OError.getFullStack(err)
+          captureException(fullError, { extra: ctx.extra })
+        })
+      })
+      .catch(error =>
+        captureException(OError.tag(error, 'Cannot register serviceWorker'))
+      )
   }
   }
 }
 }

+ 2 - 6
services/web/frontend/js/infrastructure/error-reporter.js

@@ -59,12 +59,8 @@ function sentryReporter() {
 
 
 function nullReporter() {
 function nullReporter() {
   return Promise.resolve({
   return Promise.resolve({
-    captureException: error => {
-      console.error(error)
-    },
-    captureMessage: error => {
-      console.error(error)
-    },
+    captureException: console.error,
+    captureMessage: console.error,
   })
   })
 }
 }
 
 

+ 47 - 6
services/web/frontend/js/serviceWorker.js

@@ -1,4 +1,6 @@
 import { v4 as uuid } from 'uuid'
 import { v4 as uuid } from 'uuid'
+const OError = require('@overleaf/o-error')
+
 const COMPILE_REQUEST_MATCHER = /^\/project\/[0-9a-f]{24}\/compile$/
 const COMPILE_REQUEST_MATCHER = /^\/project\/[0-9a-f]{24}\/compile$/
 const PDF_REQUEST_MATCHER = /^\/project\/[0-9a-f]{24}\/.*\/output.pdf$/
 const PDF_REQUEST_MATCHER = /^\/project\/[0-9a-f]{24}\/.*\/output.pdf$/
 const PDF_JS_CHUNK_SIZE = 128 * 1024
 const PDF_JS_CHUNK_SIZE = 128 * 1024
@@ -253,10 +255,8 @@ function processPdfRequest(
         fetch(url, init)
         fetch(url, init)
           .then(response => {
           .then(response => {
             if (!(response.status === 206 || response.status === 200)) {
             if (!(response.status === 206 || response.status === 200)) {
-              throw new Error(
-                `could not fetch ${url} ${JSON.stringify(init)}: ${
-                  response.status
-                }`
+              throw new OError(
+                'non successful response status: ' + response.status
               )
               )
             }
             }
             const blobFetchDate = getServerTime(response)
             const blobFetchDate = getServerTime(response)
@@ -283,6 +283,9 @@ function processPdfRequest(
               data: backFillObjectContext(chunk, arrayBuffer),
               data: backFillObjectContext(chunk, arrayBuffer),
             }
             }
           })
           })
+          .catch(error => {
+            throw OError.tag(error, 'cannot fetch chunk', { url })
+          })
       )
       )
     )
     )
       .then(responses => {
       .then(responses => {
@@ -343,7 +346,7 @@ function processPdfRequest(
         })
         })
       })
       })
       .catch(error => {
       .catch(error => {
-        console.error('Could not fetch partial pdf chunks', error)
+        reportError(event, OError.tag(error, 'failed to compose pdf response'))
         return fetch(event.request)
         return fetch(event.request)
       })
       })
   )
   )
@@ -469,8 +472,19 @@ function getInterleavingDynamicChunks(chunks, start, end) {
   return dynamicChunks
   return dynamicChunks
 }
 }
 
 
+/**
+ * @param {FetchEvent} event
+ */
+function onFetchWithErrorHandling(event) {
+  try {
+    onFetch(event)
+  } catch (error) {
+    reportError(event, OError.tag(error, 'low level error in onFetch'))
+  }
+}
+
 // listen to all network requests
 // listen to all network requests
-self.addEventListener('fetch', onFetch)
+self.addEventListener('fetch', onFetchWithErrorHandling)
 
 
 // complete setup ASAP
 // complete setup ASAP
 self.addEventListener('install', event => {
 self.addEventListener('install', event => {
@@ -479,3 +493,30 @@ self.addEventListener('install', event => {
 self.addEventListener('activate', event => {
 self.addEventListener('activate', event => {
   event.waitUntil(self.clients.claim())
   event.waitUntil(self.clients.claim())
 })
 })
+
+/**
+ *
+ * @param {FetchEvent} event
+ * @param {Error} error
+ */
+function reportError(event, error) {
+  self.clients
+    .get(event.clientId)
+    .then(client => {
+      if (!client) {
+        // The client disconnected.
+        return
+      }
+      client.postMessage(
+        JSON.stringify({
+          extra: { url: event.request.url, info: OError.getFullInfo(error) },
+          error: {
+            name: error.name,
+            message: error.message,
+            stack: OError.getFullStack(error),
+          },
+        })
+      )
+    })
+    .catch(() => {})
+}