hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
146 lines • 6.11 kB
JavaScript
import path from "node:path";
import { readJsonFile, writeJsonFile, FileNotFoundError, } from "@nomicfoundation/hardhat-utils/fs";
import { getCacheDir } from "@nomicfoundation/hardhat-utils/global-dir";
import { isObject } from "@nomicfoundation/hardhat-utils/lang";
import { getRequest } from "@nomicfoundation/hardhat-utils/request";
import debug from "debug";
const log = debug("hardhat:util:banner-manager");
export const BANNER_CONFIG_URL = "https://raw.githubusercontent.com/NomicFoundation/hardhat/refs/heads/main/banner-config-v3.json";
export const BANNER_CACHE_FILE_NAME = "banner-config-v3.json";
export class BannerManager {
static #instance;
#bannerConfig;
#lastDisplayTime;
#lastRequestTime;
#dispatcher;
#print;
constructor(bannerConfig, lastDisplayTime, lastRequestTime, dispatcher, print) {
this.#bannerConfig = bannerConfig;
this.#lastDisplayTime = lastDisplayTime;
this.#lastRequestTime = lastRequestTime;
this.#dispatcher = dispatcher;
this.#print = print;
}
/**
* Returns a global instance of BannerManager.
*
* @param options Options used for testing purposes. Only used in the first
* invocation of this function, or after calling `resetInstance`.
* @returns The current global instance of BannerManager.
*/
static async getInstance(options) {
if (this.#instance === undefined) {
log("Initializing BannerManager");
const { bannerConfig, lastDisplayTime, lastRequestTime } = await readCache();
this.#instance = new BannerManager(bannerConfig, lastDisplayTime, lastRequestTime, options?.testDispatcher, options?.print ?? console.log);
}
return this.#instance;
}
static resetInstance() {
this.#instance = undefined;
}
/**
* Displays a banner message, if any.
*
* @param timeout The timeout in milliseconds to wait for the banner display.
*/
async showBanner(timeout) {
await this.#requestBannerConfig(timeout);
if (this.#bannerConfig === undefined ||
!this.#bannerConfig.enabled ||
this.#bannerConfig.formattedMessages.length === 0) {
log("Banner is disabled or no messages available.");
return;
}
const { formattedMessages, minSecondsBetweenDisplays } = this.#bannerConfig;
const timeSinceLastDisplay = Date.now() - this.#lastDisplayTime;
if (timeSinceLastDisplay < minSecondsBetweenDisplays * 1000) {
log(`Skipping banner display. Time since last display: ${timeSinceLastDisplay}ms`);
return;
}
// select a random message from the formattedMessages array
const randomIndex = Math.floor(Math.random() * formattedMessages.length);
const message = formattedMessages[randomIndex];
this.#print(message);
this.#lastDisplayTime = Date.now();
await writeCache({
bannerConfig: this.#bannerConfig,
lastDisplayTime: this.#lastDisplayTime,
lastRequestTime: this.#lastRequestTime,
});
}
async #requestBannerConfig(timeout) {
if (this.#bannerConfig !== undefined) {
const timeSinceLastRequest = Date.now() - this.#lastRequestTime;
if (timeSinceLastRequest <
this.#bannerConfig.minSecondsBetweenRequests * 1000) {
log(`Skipping banner config request. Time since last request: ${timeSinceLastRequest}ms`);
return;
}
}
try {
const response = await getRequest(BANNER_CONFIG_URL, undefined, this.#dispatcher ?? { timeout });
const bannerConfig = await response.body.json();
if (!this.#isBannerConfig(bannerConfig)) {
log(`Invalid banner config received:`, bannerConfig);
return;
}
this.#bannerConfig = bannerConfig;
this.#lastRequestTime = Date.now();
await writeCache({
bannerConfig: this.#bannerConfig,
lastDisplayTime: this.#lastDisplayTime,
lastRequestTime: this.#lastRequestTime,
});
}
catch (error) {
log(`Error requesting banner config: ${error instanceof Error ? error.message : JSON.stringify(error)}`);
}
}
#isBannerConfig(value) {
if (!isObject(value)) {
return false;
}
return (Object.getOwnPropertyNames(value).length === 4 &&
"enabled" in value &&
typeof value.enabled === "boolean" &&
"formattedMessages" in value &&
Array.isArray(value.formattedMessages) &&
value.formattedMessages.every((message) => typeof message === "string") &&
"minSecondsBetweenDisplays" in value &&
typeof value.minSecondsBetweenDisplays === "number" &&
"minSecondsBetweenRequests" in value &&
typeof value.minSecondsBetweenRequests === "number");
}
}
async function readCache() {
const cacheDir = await getCacheDir();
const bannerCacheFilePath = path.join(cacheDir, BANNER_CACHE_FILE_NAME);
try {
return await readJsonFile(bannerCacheFilePath);
}
catch (error) {
if (error instanceof FileNotFoundError) {
log("No banner cache file found, using defaults");
}
else {
log(`Error reading cache file: ${error instanceof Error ? error.message : JSON.stringify(error)}`);
}
return {
bannerConfig: undefined,
lastDisplayTime: 0,
lastRequestTime: 0,
};
}
}
async function writeCache(cache) {
const cacheDir = await getCacheDir();
const bannerCacheFilePath = path.join(cacheDir, BANNER_CACHE_FILE_NAME);
try {
await writeJsonFile(bannerCacheFilePath, cache);
}
catch (error) {
log(`Error writing cache file: ${error instanceof Error ? error.message : JSON.stringify(error)}`);
}
}
//# sourceMappingURL=banner-manager.js.map