diff --git a/src/Netscript/NetscriptHelpers.ts b/src/Netscript/NetscriptHelpers.ts index 5ce438fd7..5cffb1d18 100644 --- a/src/Netscript/NetscriptHelpers.ts +++ b/src/Netscript/NetscriptHelpers.ts @@ -65,6 +65,9 @@ export const helpers = { failOnHacknetServer, }; + +/** Will probably remove the below function in favor of a different approach to object type assertion. + * This method cannot be used to handle optional properties. */ export function assertObjectType( ctx: NetscriptContext, name: string, diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 516f57dfd..7066f75b6 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -61,14 +61,7 @@ import { NetscriptCorporation } from "./NetscriptFunctions/Corporation"; import { NetscriptFormulas } from "./NetscriptFunctions/Formulas"; import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket"; import { NetscriptGrafting } from "./NetscriptFunctions/Grafting"; -import { - NS, - RecentScript as IRecentScript, - BasicHGWOptions, - ProcessInfo, - MoneySource as IMoneySource, - MoneySources as IMoneySources, -} from "./ScriptEditor/NetscriptDefinitions"; +import { NS, RecentScript as IRecentScript, BasicHGWOptions, ProcessInfo } from "./ScriptEditor/NetscriptDefinitions"; import { NetscriptSingularity } from "./NetscriptFunctions/Singularity"; import { dialogBoxCreate } from "./ui/React/DialogBox"; @@ -83,6 +76,7 @@ import { InternalAPI, wrapAPI } from "./Netscript/APIWrapper"; import { INetscriptExtra } from "./NetscriptFunctions/Extra"; import { ScriptDeath } from "./Netscript/ScriptDeath"; import { getBitNodeMultipliers } from "./BitNode/BitNode"; +import { assert, arrayAssert, stringAssert, objectAssert } from "./utils/helpers/typeAssertion"; // "Enums" as object export const enums = { @@ -1759,60 +1753,81 @@ const base: InternalAPI = { throw new Error(`variant must be one of ${Object.values(ToastVariant).join(", ")}`); SnackbarEvents.emit(message, variant as ToastVariant, duration); }, - prompt: - (ctx) => - (_txt, options = {}) => { - const txt = helpers.string(ctx, "txt", _txt); - const optionsValidator: { type?: string; options?: string[] } = {}; - assertObjectType(ctx, "options", options, optionsValidator); - - return new Promise(function (resolve) { - PromptEvent.emit({ - txt: txt, - options, - resolve: resolve, - }); - }); - }, - wget: - (ctx) => - async (_url, _target, _hostname = ctx.workerScript.hostname) => { - const url = helpers.string(ctx, "url", _url); - const target = helpers.string(ctx, "target", _target); - const hostname = helpers.string(ctx, "hostname", _hostname); - if (!isScriptFilename(target) && !target.endsWith(".txt")) { - helpers.log(ctx, () => `Invalid target file: '${target}'. Must be a script or text file.`); - return Promise.resolve(false); + prompt: (ctx) => (_txt, _options) => { + const options: { type?: string; choices?: string[] } = {}; + _options ??= options; + const txt = helpers.string(ctx, "txt", _txt); + assert(_options, objectAssert, (type) => + helpers.makeRuntimeErrorMsg(ctx, `Invalid type for options: ${type}, should be object.`, "TYPE"), + ); + if (_options.type !== undefined) { + assert(_options.type, stringAssert, (type) => + helpers.makeRuntimeErrorMsg(ctx, `Invalid type for options.type: ${type}, should be string.`, "TYPE"), + ); + options.type = _options.type; + const validTypes = ["boolean", "text", "select"]; + if (!["boolean", "text", "select"].includes(options.type)) { + throw helpers.makeRuntimeErrorMsg( + ctx, + `Invalid value for options.type: ${options.type}. Must be one of ${validTypes.join(", ")}.`, + ); } - const s = helpers.getServer(ctx, hostname); - return new Promise(function (resolve) { - $.get( - url, - function (data) { - let res; - if (isScriptFilename(target)) { - res = s.writeToScriptFile(target, data); - } else { - res = s.writeToTextFile(target, data); - } - if (!res.success) { - helpers.log(ctx, () => "Failed."); - return resolve(false); - } - if (res.overwritten) { - helpers.log(ctx, () => `Successfully retrieved content and overwrote '${target}' on '${hostname}'`); - return resolve(true); - } - helpers.log(ctx, () => `Successfully retrieved content to new file '${target}' on '${hostname}'`); - return resolve(true); - }, - "text", - ).fail(function (e) { - helpers.log(ctx, () => JSON.stringify(e)); - return resolve(false); - }); + if (options.type === "select") { + assert(_options.choices, arrayAssert, (type) => + helpers.makeRuntimeErrorMsg( + ctx, + `Invalid type for options.choices: ${type}. If options.type is "select", options.choices must be an array.`, + "TYPE", + ), + ); + options.choices = _options.choices.map((choice, i) => helpers.string(ctx, `options.choices[${i}]`, choice)); + } + } + return new Promise(function (resolve) { + PromptEvent.emit({ + txt: txt, + options, + resolve: resolve, }); - }, + }); + }, + wget: (ctx) => async (_url, _target, _hostname) => { + const url = helpers.string(ctx, "url", _url); + const target = helpers.string(ctx, "target", _target); + const hostname = _hostname ? helpers.string(ctx, "hostname", _hostname) : ctx.workerScript.hostname; + if (!isScriptFilename(target) && !target.endsWith(".txt")) { + helpers.log(ctx, () => `Invalid target file: '${target}'. Must be a script or text file.`); + return Promise.resolve(false); + } + const s = helpers.getServer(ctx, hostname); + return new Promise(function (resolve) { + $.get( + url, + function (data) { + let res; + if (isScriptFilename(target)) { + res = s.writeToScriptFile(target, data); + } else { + res = s.writeToTextFile(target, data); + } + if (!res.success) { + helpers.log(ctx, () => "Failed."); + return resolve(false); + } + if (res.overwritten) { + helpers.log(ctx, () => `Successfully retrieved content and overwrote '${target}' on '${hostname}'`); + return resolve(true); + } + helpers.log(ctx, () => `Successfully retrieved content to new file '${target}' on '${hostname}'`); + return resolve(true); + }, + "text", + ).fail(function (e) { + helpers.log(ctx, () => JSON.stringify(e)); + return resolve(false); + }); + }); + }, getFavorToDonate: () => () => { return Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction); }, diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index ef3fa68e9..0be47d220 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -6613,7 +6613,7 @@ export interface NS { */ prompt( txt: string, - options?: { type?: "boolean" | "text" | "select" | undefined; choices?: string[] }, + options?: { type?: "boolean" | "text" | "select"; choices?: string[] }, ): Promise; /** diff --git a/src/utils/helpers/typeAssertion.ts b/src/utils/helpers/typeAssertion.ts new file mode 100644 index 000000000..d5736b857 --- /dev/null +++ b/src/utils/helpers/typeAssertion.ts @@ -0,0 +1,42 @@ +// Various functions for asserting types. + +/** Function for providing custom error message to throw for a type assertion. + * @param v: Value to assert type of + * @param assertFn: Typechecking function to use for asserting type of v. + * @param msgFn: Function to use to generate an error message if an error is produced. */ +export function assert( + v: unknown, + assertFn: (v: unknown) => asserts v is T, + msgFn: (type: string) => string, +): asserts v is T { + try { + assertFn(v); + } catch (type: unknown) { + if (type !== "string") type = "unknown"; + throw msgFn(type as string); + } +} + +/** Returns the friendlyType of v. arrays are "array" and null is "null". */ +export function getFriendlyType(v: unknown): string { + return v === null ? "null" : Array.isArray(v) ? "array" : typeof v; +} + +//All assertion functions used here should return the friendlyType of the input. + +/** For non-objects, and for array/null, throws the friendlyType of v. */ +export function objectAssert(v: unknown): asserts v is Partial> { + const type = getFriendlyType(v); + if (type !== "object") throw type; +} + +/** For non-string, throws the friendlyType of v. */ +export function stringAssert(v: unknown): asserts v is string { + const type = getFriendlyType(v); + if (type !== "string") throw type; +} + +/** For non-array, throws the friendlyType of v. */ +export function arrayAssert(v: unknown): asserts v is unknown[] { + if (!Array.isArray(v)) throw getFriendlyType(v); +}