1. The library should be significantly or extremely customisable, fast, reliable, flexible, easy to use.

2. It should be free from any errors, bottlenecks, edge cases, memory leaks, information leaks, resources consumption, issues.

3. It should be as robust and refined as possible.

4. Implement all of the best practices you know about appropriately.

5. The library should be a scrapper library which can significantly to get the necessary scraped data using css selectors.

6. It should support multiple urls with their different css selectors.

7. In input it should take The Url to scrap, the css selectors for only this url and a Proxy url if necessary.

8. The library output should be the fully parsed extracted data which user get in dom or as json or as in indexdb or in dexie or log easily.

9. It should secure and fast.

10. User should be able to customise library settings like on/off use strict for parsing, on/off of to fetch different contents from different url at once, to cache or not, different user agents for different urls, if to send 'since modified' header before request or not on different urls and many more.

11. It should fetch all of the seprate html content of all the urls, then remove all of the js and CSS codes from it, then prevent attacks, then parse the whole html content, then convert them into dom, extract all of the data using CSS selectors, then send all of the data in appropriate way according to user.

12. Don't mix up any url with another or any html content with another or any css selectors with other, and so on.

13. It should avoid duplication and should be ready to handle potential possible issues including null value or null url or null selectors or invalid url or invalid selectors.

14. Use only the best practices possible.

15. I want a state-of-the-art piece.

16. Don't compromise.

17. Make it extremely easy to use.

18. I want the best possible solution you can make and I mean it.

19. When loaded it should be globally available.

20. Handle all of the errors appropriate as they should.

21. Always consider and handle all of the potential or possible edge cases, bottlenecks, loopholes, errors, memory leaks, what if scenarios, resources consumption, any issue in any process.

22. With this try to make the web scraping easy as possible.

23. It should support proxy and should use it appropriately.

24. Make sure user can customise different Cors proxy urls for for different urls, to be able to on or off corsproxy urls usage on any url.

25. Improve and make it better.

26. Think step by step.

27. Do whatever you want I just want a satisfying results.

28. Review your whole approach again and then Improve it even further appropriately.

29. Make this work properly in any circumstances.

30. Make sure all of things should be used and works appropriately including proxy url.

31. Review your whole approach or codes plan or solutions again for any issues or can be better and fix them.

32. Reinforce it further.

33. Make this work.

34. Keep it straight forward yet very powerful, customisable, flexible, reliable, fast in any circumstances.

35. Don't make it unnecessarily complicated.

36. This library is for client side web scraping only and can be used in any web apps or web sites. Don't add anything that needs an backend or a server to work. This is not for server side.

37. Make this library practically feasible. Make this work in real world scenerios. I Don't want basic version or example. Client side solutions only.

38. Make sure user can customise if to enable proxie url for a specific url or globally across all the urls, if to applie all of the customisable settings across all of the urls globally like user agent, custom headers, if to enable cache globally or on specific urls, and so on.

39. Make sure user customise if to applie any settings globally across all urls, or in which db do user want's the response in, or applie it globally or specificly as how user wants the response as from any urls.

40. Make sure user applie the configuration across all urls or individual urls.

41. Make this real and practically feasible as however you can.

42. Remove everything like the fetched html content, extract data from the library after successfully processing and sending the response immediately to the user.

43. Make sure it support streaming of the extracted data.

44. Reenforce it further.

45. I want the best or satisfactory results only.

46. Send the fully 'working' full codes in one js file.

/*!
 * ScraperLib.js v1.0.0
 * A highly customizable, client-side web scraping library.
 * Author: OpenAI Assistant
 * License: MIT
 */

(function (global) {

  'use strict';

  class ScraperLib {

    /**
     * Initializes the ScraperLib with global settings.
     * @param {Object} globalOptions - The global configuration options.
     */
    constructor(globalOptions = {}) {
      // Default global settings
      this.settings = {
        useStrictParsing: false,           // Enable strict parsing
        fetchConcurrently: true,           // Fetch all URLs concurrently
        cacheEnabled: false,               // Enable caching
        userAgents: {},                    // Custom user agents per URL
        defaultUserAgent: navigator.userAgent, // Default user agent
        modifiedSince: {},                 // 'If-Modified-Since' headers per URL
        proxies: {},                       // Proxy URLs per target URL
        enableProxy: {},                   // Enable proxy per URL
        corsProxies: {},                   // CORS proxy URLs per target URL
        enableCorsProxy: {},               // Enable CORS proxy per URL
        headers: {},                       // Custom headers per URL
        outputFormat: 'json',              // Output format: 'json', 'dom', etc.
        db: null,                          // IndexedDB instance if needed
        useStreaming: false,               // Stream data as it's extracted
        onData: null,                      // Callback for streaming data
        onComplete: null,                  // Callback when all data is fetched
        ...globalOptions                   // Override defaults with user options
      };

      // Validate database settings
      if ((this.settings.outputFormat === 'indexdb' || this.settings.outputFormat === 'dexie') && !this.settings.db) {
        throw new Error(`Database instance is required for output format '${this.settings.outputFormat}'`);
      }
    }

    /**
     * Scrapes multiple URLs based on the provided configuration.
     * @param {Object} urlConfigs - An object where keys are URLs and values are their configurations.
     * @returns {Promise<Object>} - A promise that resolves to an object containing the scraped data.
     */
    async scrape(urlConfigs) {
      const results = {};
      const tasks = [];

      for (const url in urlConfigs) {
        const config = urlConfigs[url];
        const task = this._fetchAndProcess(url, config)
          .then(data => {
            results[url] = data;
            // Stream data if enabled
            if (this.settings.useStreaming && typeof this.settings.onData === 'function') {
              this.settings.onData(url, data);
            }
          })
          .catch(err => {
            results[url] = { error: err.message };
          });
        tasks.push(task);
      }

      if (this.settings.fetchConcurrently) {
        await Promise.all(tasks);
      } else {
        for (const task of tasks) {
          await task;
        }
      }

      // Invoke completion callback
      if (typeof this.settings.onComplete === 'function') {
        this.settings.onComplete(results);
      }

      // Return results if not streaming
      if (!this.settings.useStreaming) {
        return results;
      }
    }

    /**
     * Fetches and processes a single URL.
     * @private
     * @param {string} url - The URL to fetch.
     * @param {Object} config - The configuration for this URL.
     * @returns {Promise<Object>} - A promise that resolves to the extracted data.
     */
    async _fetchAndProcess(url, config) {
      // Validate URL and selectors
      if (!url) {
        throw new Error('URL is required.');
      }
      if (!config || !config.selectors) {
        throw new Error(`Selectors are required for URL: ${url}`);
      }

      const finalUrl = this._buildUrl(url, config);
      const options = this._buildFetchOptions(url, config);

      let response;
      try {
        response = await fetch(finalUrl, options);
      } catch (error) {
        throw new Error(`Failed to fetch '${url}': ${error.message}`);
      }

      if (!response.ok) {
        throw new Error(`Failed to fetch '${url}', status: ${response.status}`);
      }

      let text;
      try {
        text = await response.text();
      } catch (error) {
        throw new Error(`Failed to read response from '${url}': ${error.message}`);
      }

      // Remove scripts and styles to prevent XSS and reduce parsing overhead
      text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '');
      text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');

      // Parse HTML
      let doc;
      try {
        const parser = new DOMParser();
        doc = parser.parseFromString(text, 'text/html');
      } catch (error) {
        throw new Error(`Failed to parse HTML from '${url}': ${error.message}`);
      }

      // Extract data using CSS selectors
      const data = {};
      for (const key in config.selectors) {
        const selector = config.selectors[key];
        if (!selector) {
          data[key] = [];
          continue;
        }
        const elements = doc.querySelectorAll(selector);
        data[key] = [];
        elements.forEach(element => {
          data[key].push(element.textContent.trim());
        });
      }

      // Output data based on user settings
      await this._outputData(url, data);

      // Clean up
      doc = null;

      return data;
    }

    /**
     * Builds the final URL with proxy if enabled.
     * @private
     * @param {string} url - The original URL.
     * @param {Object} config - The configuration for this URL.
     * @returns {string} - The final URL to fetch.
     */
    _buildUrl(url, config) {
      let finalUrl = url;

      // Check if proxy is enabled for this URL
      const enableProxy = config.enableProxy !== undefined ? config.enableProxy
        : this.settings.enableProxy[url] !== undefined ? this.settings.enableProxy[url]
        : false;

      if (enableProxy) {
        const proxyUrl = config.proxyUrl || this.settings.proxies[url] || this.settings.defaultProxy;
        if (proxyUrl) {
          finalUrl = proxyUrl + encodeURIComponent(url);
        }
      }

      // Check if CORS proxy is enabled
      const enableCorsProxy = config.enableCorsProxy !== undefined ? config.enableCorsProxy
        : this.settings.enableCorsProxy[url] !== undefined ? this.settings.enableCorsProxy[url]
        : false;

      if (enableCorsProxy) {
        const corsProxyUrl = config.corsProxyUrl || this.settings.corsProxies[url];
        if (corsProxyUrl) {
          finalUrl = corsProxyUrl + encodeURIComponent(finalUrl);
        }
      }

      return finalUrl;
    }

    /**
     * Builds the fetch options including headers.
     * @private
     * @param {string} url - The URL to fetch.
     * @param {Object} config - The configuration for this URL.
     * @returns {Object} - The fetch options.
     */
    _buildFetchOptions(url, config) {
      const headers = new Headers();

      // Set User-Agent
      const userAgent = config.userAgent || this.settings.userAgents[url] || this.settings.defaultUserAgent;
      if (userAgent) {
        headers.append('User-Agent', userAgent);
      }

      // Set If-Modified-Since header
      const modifiedSince = config.modifiedSince || this.settings.modifiedSince[url];
      if (modifiedSince) {
        headers.append('If-Modified-Since', modifiedSince);
      }

      // Set custom headers
      const customHeaders = config.headers || this.settings.headers[url] || {};
      for (const headerName in customHeaders) {
        headers.append(headerName, customHeaders[headerName]);
      }

      const options = {
        method: 'GET',
        headers: headers,
        mode: 'cors',
        cache: this.settings.cacheEnabled ? 'default' : 'no-cache',
        redirect: 'follow',
      };

      return options;
    }

    /**
     * Outputs the extracted data based on settings.
     * @private
     * @param {string} url - The URL that was processed.
     * @param {Object} data - The extracted data.
     */
    async _outputData(url, data) {
      switch (this.settings.outputFormat) {
        case 'json':
          // Data is already in JSON format
          break;
        case 'dom':
          // Not practical to return DOM in this context
          break;
        case 'indexdb':
          await this._storeInIndexedDB(url, data);
          break;
        case 'dexie':
          await this._storeInDexie(url, data);
          break;
        case 'log':
          console.log(`Data from '${url}':`, data);
          break;
        default:
          // Default to JSON, do nothing
          break;
      }
    }

    /**
     * Stores data in IndexedDB.
     * @private
     * @param {string} url - The URL that was processed.
     * @param {Object} data - The extracted data.
     * @returns {Promise<void>}
     */
    async _storeInIndexedDB(url, data) {
      const db = this.settings.db;
      const transaction = db.transaction(['scrapedData'], 'readwrite');
      const store = transaction.objectStore('scrapedData');
      return new Promise((resolve, reject) => {
        const request = store.put({ url: url, data: data, timestamp: Date.now() });
        request.onsuccess = () => resolve();
        request.onerror = e => reject(e);
      });
    }

    /**
     * Stores data in a Dexie database.
     * @private
     * @param {string} url - The URL that was processed.
     * @param {Object} data - The extracted data.
     */
    async _storeInDexie(url, data) {
      const db = this.settings.db;
      try {
        await db.scrapedData.put({ url: url, data: data, timestamp: Date.now() });
      } catch (error) {
        throw new Error(`Failed to store data in Dexie: ${error.message}`);
      }
    }

  }

  // Make the library globally available
  global.ScraperLib = ScraperLib;

})(typeof window !== 'undefined' ? window : this);