Source: dg_sdk.js

import AIServerZoo from './aiserverzoo.js';
import AIServerCloudZoo from './aiservercloudzoo.js';
import CloudServerCloudZoo from './cloudservercloudzoo.js';
import DGError from './dgerror.js';

const defaultLocalhostIP = 'localhost:8779';

/**
 * @class
 * @classdesc This class is the entrypoint for DeGirumJS, responsible for creating AIServerZoo / AIServerCloudZoo / CloudServerCloudZoo instances.
 * 
 * @example <caption>Usage:</caption>
 * let dg = new dg_sdk();
 * let zoo = dg.connect('ws://localhost:8779', 'https://cs.degirum.com/degirum/public', secretToken);
 */
class dg_sdk {
    constructor() {
        // console.log('Entered dg_sdk constructor');
    }

    /**
     * Connects to an AI Server or Cloud model zoo based on the provided parameters.
     *
     * This method supports three modes of inference:<br>
     * 1. AIServer inference with AIServer model zoo.<br>
     * 2. AIServer inference with Cloud model zoo.<br>
     * 3. Cloud inference with Cloud model zoo.<br>
     * Dummy flag is used for creating AIServerCloudZoo instances that only need to have model listing functionality.
     * This is useful for page elements or testing that need to display the model / zoo lists without connecting to any AIServer.
     * 
     * @example <caption>AIServer inference with AIServer model zoo:</caption>
     * let zoo = dg.connect('ws://localhost:8779');
     * @example <caption>AIServer inference with Cloud model zoo:</caption>
     * let zoo = dg.connect('ws://localhost:8779', 'https://cs.degirum.com/degirum/public', secretToken);
     * @example <caption>Cloud inference with Cloud model zoo:</caption>
     * let zoo = dg.connect('cloud', 'https://cs.degirum.com/degirum/public', secretToken);

     * @param {string} dest - The destination address or alias ('cloud' for cloud inference).
     * @param {string} [zooUrl=null] - The URL of the cloud model zoo (required for cloud model access in AIServerCloudZoo / CloudServerCloudZoo inference).
     * @param {string} [token=null] - The authentication token for the model zoo (required for cloud model access).
     * @param {boolean} [isDummy=false] - Flag to indicate if a dummy zoo instance should be created.
     * @returns {AIServerZoo|AIServerCloudZoo|CloudServerCloudZoo} - An instance of the appropriate zoo class based on the parameters.
     */
    connect(dest, zooUrl = null, token = null, isDummy = false) {
        // Input validation for destination
        if (typeof dest !== 'string') {
            throw new DGError('Invalid destination format. Expected string.', 'INVALID_DESTINATION_FORMAT', { dest: dest }, 'Invalid destination format.', 'Please provide a destination in string format.');
        }

        let cloudInferenceRequested = ['cloud', 'CLOUD', 'Cloud'].includes(dest);
        let zooTokenExist = zooUrl !== null && zooUrl !== '' && token !== null && token !== '';

        // If both zooUrl and token exist, and dest is not 'cloud'
        if (zooTokenExist && !cloudInferenceRequested) {
            // AIServer inference, Cloud model zoo
            console.log('dg_sdk: Preparing AI Server inference on Cloud model zoo');

            // Validate zooUrl
            if (!this.isValidZooUrl(zooUrl)) {
                throw new DGError('Invalid zooUrl format. Example formats: "https://foo.bar.com/path" or "https://foobar.com".', 'INVALID_ZOOURL_FORMAT', { zooUrl: zooUrl }, 'Invalid zooUrl format.', 'Please provide a zooUrl in the correct format.');
            }

            // 1. Prepare the websocket string
            let wsAddress = this.prepareWebSocketAddress(dest);

            // 2. Prepare the website string
            let websiteAddress = this.prepareWebsiteAddress(zooUrl);

            // 3. Return the AIServerCloudZoo instance
            return new AIServerCloudZoo(wsAddress, websiteAddress, token, isDummy);
        } else if (zooTokenExist && cloudInferenceRequested) {
            // Cloud inference, Cloud model zoo
            console.log('dg_sdk: Preparing Cloud inference on Cloud model zoo');

            // Validate zooUrl
            if (!this.isValidZooUrl(zooUrl)) {
                throw new DGError('Invalid zooUrl format. Example formats: "https://foo.bar.com/path" or "https://foobar.com".', 'INVALID_ZOOURL_FORMAT', { zooUrl: zooUrl }, 'Invalid zooUrl format.', 'Please provide a zooUrl in the correct format.');
            }

            // 1. Prepare the website string
            let websiteAddress = this.prepareWebsiteAddress(zooUrl);

            // 2. Return the CloudServerCloudZoo instance
            return new CloudServerCloudZoo(websiteAddress, token, isDummy);
        }

        if (cloudInferenceRequested) {
            throw new DGError('Cloud inference requested but zooUrl and token are missing.', 'MISSING_ZOOURL_OR_TOKEN', { dest: dest, zooUrl: zooUrl, token: token }, 'Missing zooUrl or token.', 'Please provide a zooUrl and token for cloud inference.');
        }

        // Otherwise, we use AIServerZoo
        console.log('dg_sdk: Preparing AI Server inference on AI Server model zoo');

        const wsAddress = this.prepareWebSocketAddress(dest);
        return new AIServerZoo(wsAddress);
    }

    /**
     * Gets the list of supported devices for inference as a list of "RUNTIME/DEVICE" strings.
     * @param {string} inferenceHostAddress - The address of the inference host, either an AIServer IP address or 'cloud'
     * @returns {Promise<string[]>} - A promise that resolves with the list of supported runtime/device strings.
     */
    async getSupportedDevices(inferenceHostAddress) {
        // Input validation for inferenceHostAddress
        if (typeof inferenceHostAddress !== 'string' || inferenceHostAddress === '') {
            throw new DGError('Invalid inferenceHostAddress format. Expected string.', 'INVALID_INFERENCEHOSTADDRESS_FORMAT', { inferenceHostAddress: inferenceHostAddress }, 'Invalid inferenceHostAddress format.', 'Please provide an inferenceHostAddress in string format.');
        }

        let cloudInferenceRequested = ['cloud', 'CLOUD', 'Cloud'].includes(inferenceHostAddress);

        if (cloudInferenceRequested) {
            // Create dummy instance of CloudServerCloudZoo and return supported devices.
            let cloudZoo = new CloudServerCloudZoo('https://cs.degirum.com/public', '', true);
            return cloudZoo.systemSupportedDeviceTypes();
        }

        // Otherwise, we use AIServerZoo
        const wsAddress = this.prepareWebSocketAddress(inferenceHostAddress);
        let aiZoo = new AIServerZoo(wsAddress);
        return aiZoo.systemSupportedDeviceTypes();
    }

    /** 
     * Prepares the WebSocket address based on the destination.
    * @private
    * @param {string} dest - The destination address.
    * @returns {string} - The prepared WebSocket address.
    */
    prepareWebSocketAddress(dest) {
        dest = dest.replace(/^(http:\/\/|https:\/\/|ws:\/\/|wss:\/\/)/, '');
        if (['localhost', 'local', 'LOCALHOST', 'LOCAL'].includes(dest)) {
            dest = defaultLocalhostIP; // Default for the AI Server
        }

        const ipPortPattern = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):\d+$/;
        const domainPortPattern = /^([a-zA-Z0-9\-\.]+):\d+$/;
        if (!ipPortPattern.test(dest) && !domainPortPattern.test(dest) && dest !== defaultLocalhostIP) {
            throw new DGError('Invalid address or missing port. Use the format "address:port", "ip:port", or use "localhost" or "local".', 'INVALID_ADDRESS_OR_PORT', { dest: dest }, 'Invalid address or missing port.', 'Please provide an address in the correct format.');
        }

        return `ws://${dest}`;
    }


    /** 
     * Prepares the website address based on the zoo URL.
     * @private
     *
     * @param {string} zooUrl - The URL of the model zoo.
     * @returns {string} - The prepared website address.
     */
    prepareWebsiteAddress(zooUrl) {
        zooUrl = zooUrl.endsWith('/') ? zooUrl.slice(0, -1) : zooUrl; // Remove trailing slash if present
        if (!zooUrl.startsWith('http://') && !zooUrl.startsWith('https://')) {
            zooUrl = 'https://' + zooUrl;
        }
        return zooUrl;
    }

    /** 
     * Prepares the website address based on the zoo URL.
     * @private
     *
     * @param {string} zooUrl - The URL of the model zoo.
     * @returns {string} - The prepared website address.
     */
    isValidZooUrl(zooUrl) {
        try {
            // The URL constructor will throw an error if zooUrl is not a valid URL
            new URL(zooUrl);
            return true;
        } catch (error) {
            return false;
        }
    }

}

export default dg_sdk;