]> git.codecow.com Git - libnemo.git/commitdiff
Fix hoisted process env var. Improve post action and data validation.
authorChris Duncan <chris@codecow.com>
Mon, 3 Aug 2026 17:55:38 +0000 (10:55 -0700)
committerChris Duncan <chris@codecow.com>
Mon, 3 Aug 2026 17:55:38 +0000 (10:55 -0700)
src/lib/rpc/index.ts

index f2990026447f5ab9d31db46e3ad5e2327cf58d27..6bfcd6c8102161879a4be700170b61b97c179c75 100644 (file)
@@ -1,21 +1,22 @@
 //! 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:'
@@ -23,31 +24,37 @@ export class Rpc {
        }
 
        /**
-       * 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')
@@ -74,7 +81,7 @@ export class Rpc {
                }
        }
 
-       #validate (action: string): void {
+       #validate (action: unknown): asserts action is string {
                if (!action) {
                        throw new ReferenceError('Action is required for RPCs')
                }
@@ -84,5 +91,8 @@ export class Rpc {
                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')
+               }
        }
 }