Source

tools/RestServices.js

// $Id: RestServices.js 8623 2025-09-08 14:34:47Z jochen.hanff $
// // @ts-check

"use strict";

import { ApiResponse } from "./ApiResponse.js"

/**
 * @fileoverview Rest services for making HTTP API calls.
 * Types and services to manage HTTP API calls.
 */

/**
 *
 *  @category Tools
 *
 *  @classdesc Helper class for response of a REST call.
 *  @property {number} status           HTTP status code (getter)
 *  @property {*} data                  Parsed response body (getter)
 *  @property {string|null} contentType Content-Type header (getter)
 *  @property {string|null} message     Human readable status (getter)
 *
 */
export class RestResponse extends ApiResponse {
  
  #_contentType = null;
  

  /**
   *
   * @param {*} status        REST status code
   * @param {*} responsedata  Response as JSON object
   * @param {string|null} contentType
   * @param {string|null} message  Optional message
   */
  constructor(status, responsedata, contentType, message = null) {
    super(status, responsedata, message); 
    this.#_contentType = contentType;
  }
    
  /**
   * @returns {string|null}
   */
  get getContentType() {
    return this.#_contentType;
  }
  
}

/**
 * @category Tools
 *
 * @classdesc Helper class for Rest services. <br>
 *  Provides methods such as for making HTTP API calls.
 *
 */
export class RestServices {
  
  constructor() {}

  /**
   *    Supported HTTP methods. <br>
   *    GET / POST / PUT / DELETE / PATCH
   *
   *    @example
   *    // call REST service
   *    const url = `${ctx.getServiceUrl(InfohubContext.SERVICE.USR)}/${cntx.getScope()}/usergroups?filter=$userid ~eq~ '${userid}'&members=info`;
   *    let resp = RestServices.makeApiCall( ctx.getToken(), RestServices.METHODS.GET , url);
   *    @readonly
   *
   */
  static METHODS = {
    GET: "GET",
    POST: "POST",
    PUT: "PUT",
    DELETE: "DELETE",
    PATCH: "PATCH",
  };

  /**
   *
   * Make HTTP API call.
   * Uses 'fetch' to execute the call.
   *
   * @example
   *  //
   *  const url = `${ctx.getServiceUrl(InfohubContext.SERVICE.USR)}/${ctx.getScope()}/usergroups?filter=$userid ~eq~ '${userid}'&members=info`;
   *  let resp = RestServices.makeApiCall( ctx.getToken(), RestServices.METHODS.GET , url);
   *
   * @param {string} token        The access token to use for the API call
   * @param {string} method       HTTP method
   * @param {string} fullpath     URL of endpoint
   * @param {Object} body         = null, Request Body, Could be null for GET
   * @param {string} acceptType   = "application/json", 
   *    Accept: acceptType      // Accept Header
   * @param {string} contentType  = "application/json"
   *    Content-Type header
   * @param {string} responseType "auto" // "auto" | "json" | "text" | "blob" | "arrayBuffer"
   *    Response type
   *
   * @returns {Promise<RestResponse>}
   *
   */

  static async makeApiCall(
    token,
    method,
    fullpath,
    body          = null,
    acceptType    = "application/json",
    contentType   = "application/json",
    responseType  = "auto" // "auto" | "json" | "text" | "blob" | "arrayBuffer"
  ) {
    const url = fullpath;

    const headers = {
      Authorization: `Bearer ${token}`,
      Accept: acceptType,
    };

    // Only set Content-Type if sending a body
    if (body != null) headers["Content-Type"] = contentType;

    const options = { method, headers };

    if (
      body &&
      (method === RestServices.METHODS.POST ||
        method === RestServices.METHODS.PATCH ||
        method === RestServices.METHODS.PUT)
    ) {
      options.body =
        contentType && contentType.includes("application/json")
          ? JSON.stringify(body)
          : body;
    }

    try {
      console.log("fetch ...");

      const response = await fetch(url, options);

      const ct = response.headers.get("content-type") || "";

      let data;
      if (
        responseType === "blob" ||
        ct.startsWith("image/") ||
        ct.includes("octet-stream")
      ) {
        data = await response.blob();
      } else if (responseType === "arrayBuffer") {
        data = await response.arrayBuffer();
      } else if (responseType === "text" || ct.startsWith("text/")) {
        data = await response.text();
      } else {
        // default json, but tolerate non-json
        try {
          data = await response.json();
        } catch {
          data = await response.text();
        }
      }

      // return RestResponse(
      //     status: response.status,
      //     ok: response.ok,
      //     data,
      //     contentType: ct,
      //     message: response.ok
      //         ? "Success"
      //         : `HTTP ${response.status}: ${response.statusText}`,
      // );

      let msg = null;

      if (response.ok) {
        msg = "Success";
      } else {
        msg = `HTTP ${response.status}: ${response.statusText}`;
      }

      return new RestResponse(response.status, data, ct, msg);
    } catch (error) {
      console.error(`API call failed: ${method} ${url}`, error);
      return new RestResponse(500, null, null, error.message);
    }
  }
}