github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/public/src/streamService.ts (about)

     1  import { QuickFeedService } from '../proto/qf/quickfeed_connectweb'
     2  import { Submission } from '../proto/qf/types_pb'
     3  import { Code, createConnectTransport, createPromiseClient, PromiseClient } from "@bufbuild/connect-web"
     4  import { ConnStatus } from './Helpers'
     5  
     6  
     7  export class StreamService {
     8      private service: PromiseClient<typeof QuickFeedService>
     9      private backoff = 1000
    10  
    11      constructor() {
    12          this.service = createPromiseClient(QuickFeedService, createConnectTransport({ baseUrl: "https://" + window.location.host }))
    13      }
    14  
    15      // timeout returns a promise that resolves after the current backoff has elapsed
    16      private async timeout() {
    17          return new Promise(resolve => setTimeout(resolve, this.backoff))
    18      }
    19  
    20      public async submissionStream(options: {
    21          onMessage: (payload?: Submission | undefined) => void,
    22          onError: (error: Error) => void
    23          onStatusChange: (status: ConnStatus) => void
    24      }) {
    25          const stream = this.service.submissionStream({})
    26          try {
    27              options.onStatusChange(ConnStatus.CONNECTED)
    28              for await (const msg of stream) {
    29                  options.onMessage(msg)
    30              }
    31          } catch (error) {
    32              if (error.code === Code.Canceled) {
    33                  // The stream was canceled, so we don't need to reconnect.
    34                  // This happens when the stream is closed by the server
    35                  // which happens only if the user opens a new stream, i.e., opens the frontend in a new tab.
    36                  options.onError(new Error("Stream was canceled by the server."))
    37                  return
    38              }
    39  
    40              // Attempt to reconnect up to log2(128) + 1 times, increasing delay between attempts by 2x each time
    41              // This is a total of 8 attempts with a maximum delay of 255 seconds
    42              if (this.backoff <= 128 * 1000) {
    43                  // Attempt to reconnect after a backoff
    44                  options.onStatusChange(ConnStatus.RECONNECTING)
    45                  await this.timeout()
    46                  this.submissionStream(options)
    47                  this.backoff *= 2
    48              } else {
    49                  this.backoff = 1000
    50                  options.onError(new Error("An error occurred while connecting to the server"))
    51              }
    52          }
    53      }
    54  }