All files / src/storage/remoting StorageServer.ts

8.19% Statements 5/61
0% Branches 0/27
0% Functions 0/8
8.47% Lines 5/59

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150                50x 50x 50x 50x                     50x                                                                                                                                                                                                                                                              
/**
 * StorageServer.ts
 *
 * A server-side class that "has a" local WalletStorage (like a StorageKnex instance),
 * and exposes it via a JSON-RPC POST endpoint using Express.
 */
 
import { WalletInterface } from '@bsv/sdk'
import express, { Request, Response } from 'express'
import { AuthMiddlewareOptions, createAuthMiddleware } from '@bsv/auth-express-middleware'
import { createPaymentMiddleware } from '@bsv/payment-express-middleware'
import { sdk, Wallet, StorageProvider } from '../../index.all'
 
import { StorageKnex } from '../StorageKnex'
 
export interface WalletStorageServerOptions {
  port: number
  wallet: Wallet
  monetize: boolean
  calculateRequestPrice?: (req: Request) => number | Promise<number>
}
 
export class StorageServer {
  private app = express()
  private port: number
  private storage: StorageProvider
  private wallet: Wallet
  private monetize: boolean
  private calculateRequestPrice?: (req: Request) => number | Promise<number>
 
  constructor(storage: StorageProvider, options: WalletStorageServerOptions) {
    this.storage = storage
    this.port = options.port
    this.wallet = options.wallet
    this.monetize = options.monetize
    this.calculateRequestPrice = options.calculateRequestPrice
 
    this.setupRoutes()
  }
 
  private setupRoutes(): void {
    this.app.use(express.json({ limit: '30mb' }))
 
    // This allows the API to be used everywhere when CORS is enforced
    this.app.use((req, res, next) => {
      res.header('Access-Control-Allow-Origin', '*')
      res.header('Access-Control-Allow-Headers', '*')
      res.header('Access-Control-Allow-Methods', '*')
      res.header('Access-Control-Expose-Headers', '*')
      res.header('Access-Control-Allow-Private-Network', 'true')
      if (req.method === 'OPTIONS') {
        // Handle CORS preflight requests to allow cross-origin POST/PUT requests
        res.sendStatus(200)
      } else {
        next()
      }
    })
 
    const options: AuthMiddlewareOptions = {
      wallet: this.wallet as WalletInterface
    }
    this.app.use(createAuthMiddleware(options))
    Iif (this.monetize) {
      this.app.use(
        createPaymentMiddleware({
          wallet: this.wallet,
          calculateRequestPrice: this.calculateRequestPrice || (() => 100)
        })
      )
    }
 
    // A single POST endpoint for JSON-RPC:
    this.app.post('/', async (req: Request, res: Response) => {
      let { jsonrpc, method, params, id } = req.body
 
      // Basic JSON-RPC protocol checks:
      Iif (jsonrpc !== '2.0' || !method || typeof method !== 'string') {
        return res.status(400).json({ error: { code: -32600, message: 'Invalid Request' } })
      }
 
      try {
        // Dispatch the method call:
        if (typeof (this as any)[method] === 'function') {
          // if you wanted to handle certain methods on the server class itself
          // e.g. this['someServerMethod'](params)
          throw new Error('Server method dispatch not used in this approach.')
        } else if (typeof (this.storage as any)[method] === 'function') {
          // method is on the walletStorage:
          // Find user
          switch (method) {
            case 'destroy': {
              console.log(`StorageServer: method=${method} IGNORED`)
              return res.json({ jsonrpc: '2.0', result: undefined, id })
            }
            case 'getSettings': {
              /** */
            } break;
            case 'findOrInsertUser': {
              Iif (params[0] !== req.auth.identityKey)
                throw new sdk.WERR_UNAUTHORIZED('function may only access authenticated user.');
            } break;
            default: {
              Iif (typeof params[0] !== 'object' || !params[0]) {
                params = [{}]
              }
              Iif (params[0]['identityKey'] && params[0]['identityKey'] !== req.auth.identityKey)
                throw new sdk.WERR_UNAUTHORIZED('identityKey does not match authentiation')
              console.log('looking up user with identityKey:', req.auth.identityKey)
              const { user, isNew } = await this.storage.findOrInsertUser(req.auth.identityKey)
              params[0].reqAuthUserId = user.userId
              Iif (params[0]['identityKey']) params[0].userId = user.userId;
            } break;
          }
          console.log(`StorageServer: method=${method} params=${JSON.stringify(params).slice(0, 100)}`)
          const result = await (this.storage as any)[method](...(params || []))
          return res.json({ jsonrpc: '2.0', result, id })
        } else {
          // Unknown method
          return res.status(400).json({
            jsonrpc: '2.0',
            error: { code: -32601, message: `Method not found: ${method}` },
            id
          })
        }
      } catch (error) {
        // Catch any thrown errors from the local walletStorage method
        const err = error as Error
        return res.status(200).json({
          jsonrpc: '2.0',
          error: {
            code: -32000,
            message: err.message,
            data: {
              name: err.name,
              stack: err.stack
            }
          },
          id
        })
      }
    })
  }
 
  public start(): void {
    this.app.listen(this.port, () => {
      console.log(`WalletStorageServer listening at http://localhost:${this.port}`)
    })
  }
}