zammad/zammad

View on GitHub
app/frontend/shared/server/apollo/handler/SubscriptionHandler.ts

Summary

Maintainability
A
2 hrs
Test Coverage
// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

import { watch } from 'vue'

import type {
  OperationSubscriptionOptionsReturn,
  OperationSubscriptionsResult,
  WatchResultCallback,
} from '#shared/types/server/apollo/handler.ts'

import BaseHandler from './BaseHandler.ts'

import type { FetchResult, OperationVariables } from '@apollo/client/core'
import type { UseSubscriptionReturn } from '@vue/apollo-composable'
import type { Ref, WatchStopHandle } from 'vue'

export default class SubscriptionHandler<
  TResult = OperationSubscriptionsResult,
  TVariables extends OperationVariables = OperationVariables,
> extends BaseHandler<
  TResult,
  TVariables,
  UseSubscriptionReturn<TResult, TVariables>
> {
  public subscribed = false

  public options(): OperationSubscriptionOptionsReturn<TResult, TVariables> {
    return this.operationResult.options
  }

  public start(): void {
    this.operationResult.start()
  }

  public stop(): void {
    this.operationResult.stop()
  }

  public result(): Ref<Maybe<TResult> | undefined> {
    return this.operationResult.result
  }

  public onResult(
    callback: (
      result: FetchResult<
        TResult,
        Record<string, unknown>,
        Record<string, unknown>
      >,
    ) => void,
  ) {
    this.operationResult.onResult(callback)
  }

  public async onSubscribed(): Promise<Maybe<TResult> | undefined> {
    return new Promise((resolve, reject) => {
      let errorUnsubscribe!: () => void
      let resultUnsubscribe!: () => void

      const onFirstResultLoaded = () => {
        resultUnsubscribe()
        errorUnsubscribe()
      }

      resultUnsubscribe = watch(this.result(), (result) => {
        this.subscribed = true

        // Remove the watchers again after the promise was resolved.
        onFirstResultLoaded()
        return resolve(result || null)
      })

      errorUnsubscribe = watch(this.operationError(), (error) => {
        onFirstResultLoaded()
        return reject(error)
      })
    })
  }

  public watchOnResult(
    callback: WatchResultCallback<TResult>,
  ): WatchStopHandle {
    return watch(
      this.result(),
      (result) => {
        if (!result) {
          return
        }
        callback(result)
      },
      {
        // Needed for when the component is mounted after the first mount, in this case
        // result will already contain the data and the watch will otherwise not be triggered.
        immediate: true,
      },
    )
  }
}