//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
//! SPDX-License-Identifier: GPL-3.0-or-later
+import { SCHEMA } from './schema'
/**
-* Represents a Nano network node. It primarily consists of a URL which will
-* accept RPC calls, and an optional API key header construction can be passed if
-* required by the node. Once instantiated, the Rpc object can be used to call
-* any action supported by the Nano protocol. The URL protocol must be HTTPS; any
-* other value will be changed automatically.
-*/
+ * Represents a Nano network node. It primarily consists of a URL which will
+ * accept RPC calls, and an optional API key header construction can be passed if
+ * required by the node. Once instantiated, the Rpc object can be used to call
+ * any action supported by the Nano protocol. The URL protocol must be HTTPS; any
+ * other value will be changed automatically.
+ */
export class Rpc {
#u: URL
#n?: string
/**
- * @param {(string|URL)} url
- * @param {string} [apiKeyName]
- */
+ * @param {(string|URL)} url
+ * @param {string} [apiKeyName]
+ */
constructor (url: string | URL, apiKeyName?: string) {
this.#u = new URL(url)
this.#u.protocol = 'https:'
}
/**
- * Sends a nano RPC call to a node endpoint.
- *
- * @param {string} action - Nano protocol RPC call to execute
- * @param {object} [data] - JSON to send to the node as defined by the action
- * @returns {Promise<any>} JSON-formatted RPC results from the node
- */
- async post (action: string, data?: Record<string, unknown>): Promise<unknown> {
- var process: any = process || null
+ * Sends a nano RPC call to a node endpoint.
+ *
+ * @param {string} action - Nano protocol RPC call to execute
+ * @param {object} [data] - JSON to send to the node as defined by the action
+ * @returns {Promise<any>} JSON-formatted RPC results from the node
+ */
+ async post (action: string, data?: Record<string, unknown>): Promise<unknown>
+ async post (action: unknown, data: unknown): Promise<unknown> {
+ const env = typeof process !== 'undefined' && 'env' in process ? process.env : null
this.#validate(action)
+ action = action.toLowerCase()
const headers: Record<string, string> = {}
headers['Content-Type'] = 'application/json'
- if (this.#n && process?.env?.LIBNEMO_RPC_API_KEY) {
- headers[this.#n] = process.env.LIBNEMO_RPC_API_KEY
+ if (this.#n && env?.LIBNEMO_RPC_API_KEY) {
+ headers[this.#n] = env.LIBNEMO_RPC_API_KEY
}
-
- data ??= {}
- data.action = action.toLowerCase()
- const body = JSON.stringify(data)
+ if (data !== undefined && typeof data !== 'object') {
+ throw new TypeError('Invalid RPC post data')
+ }
+ const body = (data as Record<string, unknown>) ?? {}
+ if ('action' in body && (typeof body.action !== 'string' || body.action.toLowerCase() !== action)) {
+ throw new RangeError(`RPC post data contains 'action' property and does not match 'action' parameter argument. Do not include 'action' in request data.`)
+ }
+ body.action = action
const aborter = new AbortController()
const req = new Request(this.#u, {
signal: aborter.signal,
method: 'POST',
headers,
- body
+ body: JSON.stringify(body)
})
const kill = setTimeout(() => {
console.log('aborting RPC call')
}
}
- #validate (action: string): void {
+ #validate (action: unknown): asserts action is string {
if (!action) {
throw new ReferenceError('Action is required for RPCs')
}
if (!/^[A-Za-z]+(_[A-Za-z]+)*$/.test(action)) {
throw new TypeError('RPC action contains invalid characters')
}
+ if (!(action in SCHEMA)) {
+ throw new TypeError('Unknown RPC action')
+ }
}
}