teamdigitale/italia-app

View on GitHub
ts/features/fci/saga/networking/handleCreateFilledDocument.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import { Millisecond } from "@pagopa/ts-commons/lib/units";
import { SagaIterator } from "redux-saga";
import {
  call,
  put,
  race,
  cancelled,
  take,
  delay,
  select
} from "typed-redux-saga/macro";
import { ActionType } from "typesafe-actions";
import * as E from "fp-ts/lib/Either";
import { readablePrivacyReport } from "../../../../utils/reporters";
import {
  fciLoadQtspFilledDocument,
  fciPollFilledDocument
} from "../../store/actions";
import { getNetworkError } from "../../../../utils/errors";
import { FilledDocumentDetailView } from "../../../../../definitions/fci/FilledDocumentDetailView";
import { fciPollFilledDocumentReadySelector } from "../../store/reducers/fciPollFilledDocument";
import { FciClient } from "../../api/backendFci";
import { SessionToken } from "../../../../types/SessionToken";
import { withRefreshApiCall } from "../../../fastLogin/saga/utils";
import { SagaCallReturnType } from "../../../../types/utils";

// Polling frequency timeout
const POLLING_FREQ_TIMEOUT = 2000 as Millisecond;

// Polling time threshold (10 seconds)
// If the polling time exceeds this threshold, the polling is stopped
const POLLING_TIME_THRESHOLD = (10 * 2000) as Millisecond;

/*
 * A saga to post filled Document.
 */
export function* handleCreateFilledDocument(
  postQtspFilledBody: FciClient["createFilledDocument"],
  bearerToken: SessionToken,
  action: ActionType<(typeof fciLoadQtspFilledDocument)["request"]>
): SagaIterator {
  try {
    const postQtspFilledBodyRequest = postQtspFilledBody({
      body: action.payload,
      Bearer: `Bearer ${bearerToken}`
    });
    const postQtspFilledBodyResponse = (yield* call(
      withRefreshApiCall,
      postQtspFilledBodyRequest,
      action
    )) as unknown as SagaCallReturnType<typeof postQtspFilledBody>;

    if (E.isLeft(postQtspFilledBodyResponse)) {
      throw Error(readablePrivacyReport(postQtspFilledBodyResponse.left));
    }

    if (postQtspFilledBodyResponse.right.status === 201) {
      yield* put(
        fciLoadQtspFilledDocument.success(
          postQtspFilledBodyResponse.right.value
        )
      );
      const qtspFilledDocumentUrl =
        postQtspFilledBodyResponse.right.value.filled_document_url;
      // if the url is present, we need to poll the document
      // to wait for the filled document ready
      if (qtspFilledDocumentUrl) {
        yield* call(filledDocumentPollWatcher, qtspFilledDocumentUrl);
      }
      return;
    }

    if (postQtspFilledBodyResponse.right.status === 401) {
      return;
    }

    throw Error(`response status ${postQtspFilledBodyResponse.right.status}`);
  } catch (e) {
    yield* put(fciLoadQtspFilledDocument.failure(getNetworkError(e)));
  }
}

/**
 * Handle the FCI polling saga
 * This saga is used to poll the QTSP filled_document to check if the
 * document is ready to be downloaded by the user. The polling is done
 * every POLLING_FREQ_TIMEOUT seconds and the polling is stopped when
 * the document is ready or when the polling is stopped if
 * the polling time exceeds POLLING_TIME_THRESHOLD.
 */
export function* watchFciPollSaga(
  qtspFilledDocumentUrl: FilledDocumentDetailView["filled_document_url"]
) {
  while (true) {
    try {
      yield* put(fciPollFilledDocument.request());
      const response = yield* call(fetch, qtspFilledDocumentUrl);
      const responseStatus = response.status;
      if (responseStatus === 200) {
        yield* put(
          fciPollFilledDocument.success({
            isReady: true
          })
        );
        yield* put(fciPollFilledDocument.cancel());
      }
      yield* delay(POLLING_FREQ_TIMEOUT);
    } catch (e) {
      yield* put(fciPollFilledDocument.failure(getNetworkError(e)));
      yield* put(fciPollFilledDocument.cancel());
    } finally {
      if (yield* cancelled()) {
        const isFilledDocumentReady: ReturnType<
          typeof fciPollFilledDocumentReadySelector
        > = yield* select(fciPollFilledDocumentReadySelector);
        if (!isFilledDocumentReady) {
          yield* put(
            fciPollFilledDocument.failure(
              getNetworkError(new Error("Polling cancelled"))
            )
          );
        }
      }
    }
  }
}

export function* filledDocumentPollWatcher(
  filledDocumentUrl: FilledDocumentDetailView["filled_document_url"]
) {
  yield* race({
    task: call(watchFciPollSaga, filledDocumentUrl),
    cancel: take(fciPollFilledDocument.cancel),
    delay: delay(POLLING_TIME_THRESHOLD)
  });
}