Source

infohub/Context.js

//
// $Id: Context.js 9183 2025-11-01 14:14:54Z jochen.hanff $$Id: Context.js 9183 2025-11-01 14:14:54Z jochen.hanff $
// @ts-check

"use strict";

import { RestServices } from "../tools/RestServices.js";
import { ApiResponse } from "../tools/ApiResponse.js";
// import { ProjectServices } from "../infohub/Project.js";

// Note: Avoid static import of Project/ProjectServices to prevent circular deps

/**
  
  @category COLNEO infohub
   
  @classdesc Class to configure access to COLNEO infohub. <br>
    Keeps track of user identity, project/scope, and base URLs for services.
      
 */
export class Context extends EventTarget {
    
  #_projectSrvs = null;

  /**
   *  Supported web services.
   *
   *  HUB | CONFIG | USR | IDP | IAM | RENDER | RPC | TYPES
   *
   *  @example
   *  ...
   *  const url = `${ctx.getServiceUrl(cnContext.SERVICE.IDP)}/users/${userid}`;
   *  @readonly
   *
   */
  static SERVICE = {
    HUB: "hub",
    CONFIG: "config",
    ADMIN: "admin",
    USR: "usr",
    IDP: "idp",
    IAM: "iam",
    RENDER: "render",
    RPC: "rpc",
    TYPES: "types",
  };

  static #_servicesnames_ = [
    this.SERVICE.HUB,
    this.SERVICE.CONFIG,
    this.SERVICE.ADMIN,
    this.SERVICE.USR,
    this.SERVICE.IDP,
    this.SERVICE.IAM,
    this.SERVICE.RENDER,
    this.SERVICE.RPC,
    this.SERVICE.TYPES,
  ];

  /**
    
      Get list of names of supported web services.
       
      @example
      let names = Infohub.getServiceNames()   
      @returns {string[]} Array of names as string  
     
      @since 08.2025, jh
    
   */
  static getServiceNames() {
    return [...this.#_servicesnames_];
  }

  /**
   *
   *  Create an COLNEO infohub context with service endpoints set to null and empty user state.
   *  @example
   *
   *  // create context
   *  const ctx = new cnContext();
   *
   *  ctx.setServiceUrls({
   *    hub   : "https://app.colneo.services/hub",
   *    admin : "https://app.colneo.services/admin",
   *    usr   : "https://app.colneo.services/usr",
   *    idp   : "https://idp.colneo.services/service"
   *  });
   *
   *  ctx.setUserAndToken( "user@example.com" , "... jwt_token ...");
   *  ctx.setScopeAndProject( "cn_c00", "proj_123");
   *
   *  ctx.getScope(); // => "project"
   *  ctx.getProjectShortId(); // => "proj_123"
   *  ctx.getUser(); // => "user@example.com"
   *  ctx.getToken(); // => "jwt_token"
   *
   *  console.log(cntx.getUser()); // => "user@example.com"
   *
   * @since 08.2025, jh
   *
   */

  constructor() {
 
    super();

    this._userid = null;
    this._token = null;

    this._serviceurl = {};

    for (const srv of Context.getServiceNames()) {
      this._serviceurl[srv] = null;
    }

    this._scope = null;
    this._projectShortId = null;

    // project object
    this._project = null;
        
  }

  /**
   * Print current context to the console (for debugging).
   * @returns {void}
   */
  dump() {
    
    console.log("Context: {");
    console.log(`  USER:          ${this._userid}`);
    console.log(`  TOKEN:         ${this._token}`);
    console.log(`  SCOPE:         ${this._scope}`);
    console.log(`  PROJECT SID :  ${this._projectShortId}`);
    console.log(`  PROJECT:       ${this._project}`);

    for (const srv of Context.getServiceNames()) {
      console.log(`  ${srv} = ${this.getServiceUrl(srv)} `);
    }

    console.log("} Context");
    
  }

  /**
   * Reset scope and project to null.
   * @returns {void}
   */
  resetProjectAndScope() {     
    
    const changed = this._scope !== null  || this._projectShortId !== null ;
    
    this._scope = null;
    this._projectShortId = null;
    
    if (changed) {
      this.#emitChange({ projectShortId: null, scope: null });
    }
    
  }

  saveToLocalStorage( cb ) {
    return cb({
      'status' : 501
    })
  }

  restoreFromLocalStorage( cb ) {
    return cb({
      'status' : 501
    })
  }

  resetLocalStorage( cb ) {
    return cb({
      'status' : 501
    })
  }
  
  /**
   *
   *  Set current user id and authentication token.
   *
   *  @param {string|null} userid
   *  @param {string|null} token
   *
   *  @returns {void}
   *
   */
  setUserAndToken(userid, token) {
    const changed = this._userid !== userid || this._token !== token;
    this._userid = userid;
    this._token = token;
    if (changed) this.#emitChange({ userid: userid, token: token });
  }

  /**
   *
   * Set scope and project short id.
   *
   * @param {string} scope
   * @param {string} project_sid
   *
   * @returns {void}
   *
   */
  setScopeAndProjectShortId(scope, project_sid) {
    
    const changed = this._projectShortId !== project_sid || this._scope !== scope;
    
    this._projectShortId = project_sid;
    this._scope = scope;

    this._project = null; //aab

    if (changed) {
      this.#emitChange({ projectShortId: project_sid, scope: scope }); //aab
    }

  }

  /**
   *
   *  Get current user id.
   *
   *  @returns {string} User ID (Email)
   *
   */
  getUserId() {
    return this._userid ?? "";
  }

  /**
   *
   * Get current access token.
   *
   * @returns {string}
   *
   */
  getToken() {
    return this._token ?? "";
  }

  /**
   * Get current scope.
   * @returns {string}
   */
  getScope() {
    return this._scope;
  }

  /**
   * Set current scope.
   * @param {string} scope
   * @returns {void}
   */
  setScope(scope) {
    if (this._scope === scope) return;
    this._scope = scope;
    this.#emitChange({ scope: scope }); // no need to check if changed, setScope is only called if scope has changed
  }

  /**
   *
   *  Set multiple service URLs at once.
   *
   *  @example
   *
   *  const ctx = new cnContext();
   *
   *  ctx.setServiceUrls({
   *    hub   : "https://app.colneo.services/hub",
   *    admin : "https://app.colneo.services/admin",
   *    usr   : "https://app.colneo.services/usr"
   *  });
   *
   *  console.log(ctx.getServiceUrl("hub"));    // https://app.colneo.services/hub
   *  console.log(ctx.getServiceUrl("config")); // null (not set yet)
   *
   *  @param {Record<string, string>} cfg - Object mapping service names to URLs
   *
   *  @throws {TypeError} If cfg is not a non-null object
   */
  setServiceUrls(cfg) {

    if (typeof cfg !== "object" || cfg === null) {
      throw new TypeError("cfg must be a non-null object");
    }

    let changed = false;
    
    for (const [srv, url] of Object.entries(cfg)) {
      if (srv in this._serviceurl) {
        if (this._serviceurl[srv] !== url) {
          this._serviceurl[srv] = url;
          changed = true;
        }
      } else {
        console.warn(`Unknown service name: ${srv}`);
      }
    }
    if (changed) this.#emitChange({ serviceurl: cfg });
  }

  /**
   * Set default values for services urls
   */
  setServiceUrlsDefault() {
      
    /** @type {Record<string, string>} */
    const srv = {}

    srv[Context.SERVICE.HUB] = "https://app.colneo.services/hub"
    srv[Context.SERVICE.RENDER] = "https://app.colneo.services/render"
    srv[Context.SERVICE.ADMIN] = "https://app.colneo.services/admin"
    srv[Context.SERVICE.USR] = "https://app.colneo.services/usr"
    srv[Context.SERVICE.IDP] = "https://idp.colneo.services/service"
    srv[Context.SERVICE.IAM] = "https://app.colneo.services/iam"

    this.setServiceUrls(srv)

  }
  /**
   *
   *   Get URL of supported webservice.
   *
   *   @param {string} servicename
   *
   *   @returns {string | null} Url of service, null if not found
   *
   *   @since 08.2025, jh
   */
  getServiceUrl(servicename) {
    if (servicename in this._serviceurl) {
      return this._serviceurl[servicename];
    }
    return null;
  }

  /**
   * Set current project short id.
   * @param {string} sid
   * @returns {void}
   */
  setProjectShortId(sid) {
    if (this._projectShortId == sid) return;
    // console.log( `### /// Context.setProjectShortId( ${sid} )` )
    this._projectShortId = sid;
    this._project = null; //aab
    this.#emitChange({ projectShortId: sid });
  }

  /**
   *
   * @returns Project ShortId
   * @since 1.0, 09.2025, jh
   */
  getProjectShortId() {
    return this._projectShortId;
  }

  /**
   * api call to get project data from infohub by short id
   * @returns {Promise<object|null>} Returns a Project object or null
   *
   */
  async getProject() {
    
    const sid = this.getProjectShortId();
    
    if (sid != null) {

      if (this._project) {        
        
        return this._project

      } else {

        if( this.#_projectSrvs == null ) {
          const { ProjectServices } = await import("./Project.js");
          this.#_projectSrvs = new ProjectServices(this);
        }
        
        // MUST wrap callback in Promise so await actually waits!

        await new Promise((resolve) => {

          this.#_projectSrvs.getProjectByShortId(sid, (res) => {

            if (res.status < 300) {
              this._project = res.getData();
            } else {
              this._project = null
            }

            resolve(undefined); // Resolve Promise after callback finishes

          });
        });

        return this._project;

      }

    } else {
      return null;
    }    

  }

  /**
   * Internal: emit a change notification to contextChanged event. Details are optional.
   * @param {object} [details]
   * @param {string|null} [details.userid]
   * @param {string|null} [details.token]
   * @param {string|null} [details.scope]
   * @param {string|null} [details.projectShortId]
   * @param {Record<string, string>} [details.serviceurl]
   * @param {object|null} [details.project] - Project object or null
   * @fires Context.contextChanged
   * @returns {void}
   */
  #emitChange(details_) {
    // this.dispatchEvent((new CustomEvent('contextChanged', { detail: details }))); //aab

    let e = new CustomEvent("contextChanged", {
      detail /** @type {any} */: details_,
    });
    this.dispatchEvent(e); //aab
  }
}