{"_id":"@codingame/json-rpc-2.0","_rev":"2-5906411ad9aafe4ad59a6336030fb72d","name":"@codingame/json-rpc-2.0","dist-tags":{"latest":"1.7.0-next.1"},"versions":{"1.7.0-next.1":{"name":"@codingame/json-rpc-2.0","version":"1.7.0-next.1","keywords":["json-rpc"],"author":{"name":"Shogo Wada"},"license":"MIT","_id":"@codingame/json-rpc-2.0@1.7.0-next.1","maintainers":[{"name":"beli4l","email":"vetter.valentin@gmail.com"},{"name":"nonofr","email":"loic@codingame.com"},{"name":"samuel.olivier","email":"samuel@coderpad.io"},{"name":"nantoniazzi","email":"nicolas@codingame.com"},{"name":"maximecg","email":"maxime@codingame.com"},{"name":"codingame_team","email":"developers@codingame.com"}],"homepage":"https://github.com/shogowada/json-rpc-2.0#readme","bugs":{"url":"https://github.com/shogowada/json-rpc-2.0/issues"},"dist":{"shasum":"f1b8158bf2baaa28b8264acf3ba8d588ee98701b","tarball":"https://registry.npmjs.org/@codingame/json-rpc-2.0/-/json-rpc-2.0-1.7.0-next.1.tgz","fileCount":27,"integrity":"sha512-+qwluIRQ+EkxwSmZ3X2aP+sSOkdoO5bPY4Eb6+pjRalpWN4+td0y57iBOFlLxUAEuSglfiKd1BkQsFcpQlYPvA==","signatures":[{"sig":"MEUCIEyTmfrH0ygb+ZCg1y/hRsovZaP+63qlhbq6S2q7bBfUAiEAu8brVK9OpAx7JTdAe+/ypk/deVBuAk1cNfsXy7l6Ew4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":91889},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"e268f0f960a787c312423a32c0b96b5e0a4c5a36","scripts":{"test":"npm run format && mocha --require ts-node/register \"./src/**/*.spec.ts\"","build":"npm run format && npm run clean && tsc","clean":"del \"dist\"","format":"pretty-quick || echo \"Failed to format. Continuing...\""},"_npmUser":{"name":"nonofr","email":"loic@codingame.com"},"repository":{"url":"git+https://github.com/shogowada/json-rpc-2.0.git","type":"git"},"_npmVersion":"10.3.0","description":"JSON-RPC 2.0 client and server","directories":{},"_nodeVersion":"22.11.0","_hasShrinkwrap":false,"devDependencies":{"chai":"^5.1.2","mocha":"^10.8.2","sinon":"^19.0.2","del-cli":"^6.0.0","ts-node":"^10.9.2","prettier":"^3.4.1","typescript":"^5.7.2","@types/chai":"^5.0.1","@types/node":"^22.10.0","@types/mocha":"^10.0.10","@types/sinon":"^17.0.3","pretty-quick":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/json-rpc-2.0_1.7.0-next.1_1732706756408_0.06628435739844041","host":"s3://npm-registry-packages"}}},"time":{"created":"2024-11-27T11:25:56.253Z","modified":"2025-07-15T14:00:00.778Z","1.7.0-next.1":"2024-11-27T11:25:56.558Z"},"bugs":{"url":"https://github.com/shogowada/json-rpc-2.0/issues"},"author":{"name":"Shogo Wada"},"license":"MIT","homepage":"https://github.com/shogowada/json-rpc-2.0#readme","keywords":["json-rpc"],"repository":{"url":"git+https://github.com/shogowada/json-rpc-2.0.git","type":"git"},"description":"JSON-RPC 2.0 client and server","maintainers":[{"email":"loic@codingame.com","name":"nonofr"},{"email":"samuel@coderpad.io","name":"samuel.olivier"},{"email":"nicolas@codingame.com","name":"nantoniazzi"},{"email":"maxime@codingame.com","name":"maximecg"},{"email":"developers@codingame.com","name":"codingame_team"}],"readme":"# json-rpc-2.0\n\nLet your client and server talk over function calls under [JSON-RPC 2.0 spec](https://www.jsonrpc.org/specification).\n\n- Protocol agnostic\n  - Use over HTTP, WebSocket, TCP, UDP, inter-process, whatever else\n    - Easy migration from HTTP to WebSocket, for example\n- No external dependencies\n  - Keep your package small\n  - Stay away from dependency hell\n- Works in both browser and Node.js\n- First-class TypeScript support\n  - Written in TypeScript\n  - [Strongly typed client and server calls](#typed-client-and-server)\n\n## Install\n\n`npm install --save json-rpc-2.0`\n\n## Example\n\nThe example uses HTTP for communication protocol, but it can be anything.\n\n### Server\n\n```javascript\nconst express = require(\"express\");\nconst bodyParser = require(\"body-parser\");\nconst { JSONRPCServer } = require(\"json-rpc-2.0\");\n\nconst server = new JSONRPCServer();\n\n// First parameter is a method name.\n// Second parameter is a method itself.\n// A method takes JSON-RPC params and returns a result.\n// It can also return a promise of the result.\nserver.addMethod(\"echo\", ({ text }) => text);\nserver.addMethod(\"log\", ({ message }) => console.log(message));\n\nconst app = express();\napp.use(bodyParser.json());\n\napp.post(\"/json-rpc\", (req, res) => {\n  const jsonRPCRequest = req.body;\n  // server.receive takes a JSON-RPC request and returns a promise of a JSON-RPC response.\n  // It can also receive an array of requests, in which case it may return an array of responses.\n  // Alternatively, you can use server.receiveJSON, which takes JSON string as is (in this case req.body).\n  server.receive(jsonRPCRequest).then((jsonRPCResponse) => {\n    if (jsonRPCResponse) {\n      res.json(jsonRPCResponse);\n    } else {\n      // If response is absent, it was a JSON-RPC notification method.\n      // Respond with no content status (204).\n      res.sendStatus(204);\n    }\n  });\n});\n\napp.listen(80);\n```\n\n#### With authentication\n\nTo hook authentication into the API, inject custom params:\n\n```javascript\nconst server = new JSONRPCServer();\n\n// The method can also take a custom parameter as the second parameter.\n// Use this to inject whatever information that method needs outside the regular JSON-RPC request.\nserver.addMethod(\"echo\", ({ text }, { userID }) => `${userID} said ${text}`);\n\napp.post(\"/json-rpc\", (req, res) => {\n  const jsonRPCRequest = req.body;\n  const userID = getUserID(req);\n\n  // server.receive takes an optional second parameter.\n  // The parameter will be injected to the JSON-RPC method as the second parameter.\n  server.receive(jsonRPCRequest, { userID }).then((jsonRPCResponse) => {\n    if (jsonRPCResponse) {\n      res.json(jsonRPCResponse);\n    } else {\n      res.sendStatus(204);\n    }\n  });\n});\n\nconst getUserID = (req) => {\n  // Do whatever to get user ID out of the request\n};\n```\n\n#### Middleware\n\nUse middleware to intercept request and response:\n\n```javascript\nconst server = new JSONRPCServer();\n\n// next will call the next middleware\nconst logMiddleware = (next, request, serverParams) => {\n  console.log(`Received ${JSON.stringify(request)}`);\n  return next(request, serverParams).then((response) => {\n    console.log(`Responding ${JSON.stringify(response)}`);\n    return response;\n  });\n};\n\nconst exceptionMiddleware = async (next, request, serverParams) => {\n  try {\n    return await next(request, serverParams);\n  } catch (error) {\n    if (error.code) {\n      return createJSONRPCErrorResponse(request.id, error.code, error.message);\n    } else {\n      throw error;\n    }\n  }\n};\n\n// Middleware will be called in the same order they are applied\nserver.applyMiddleware(logMiddleware, exceptionMiddleware);\n```\n\n#### Constructor Options\n\nOptionally, you can pass options to `JSONRPCServer` constructor:\n\n```typescript\nnew JSONRPCServer({\n  errorListener: (message: string, data: unknown): void => {\n    // Listen to error here. By default, it will use console.warn to log errors.\n  },\n});\n```\n\n### Client\n\n```javascript\nimport { JSONRPCClient } from \"json-rpc-2.0\";\n\n// JSONRPCClient needs to know how to send a JSON-RPC request.\n// Tell it by passing a function to its constructor. The function must take a JSON-RPC request and send it.\nconst client = new JSONRPCClient((jsonRPCRequest) =>\n  fetch(\"http://localhost/json-rpc\", {\n    method: \"POST\",\n    headers: {\n      \"content-type\": \"application/json\",\n    },\n    body: JSON.stringify(jsonRPCRequest),\n  }).then((response) => {\n    if (response.status === 200) {\n      // Use client.receive when you received a JSON-RPC response.\n      return response\n        .json()\n        .then((jsonRPCResponse) => client.receive(jsonRPCResponse));\n    } else if (jsonRPCRequest.id !== undefined) {\n      return Promise.reject(new Error(response.statusText));\n    }\n  })\n);\n\n// Use client.request to make a JSON-RPC request call.\n// The function returns a promise of the result.\nclient\n  .request(\"echo\", { text: \"Hello, World!\" })\n  .then((result) => console.log(result));\n\n// Use client.notify to make a JSON-RPC notification call.\n// By definition, JSON-RPC notification does not respond.\nclient.notify(\"log\", { message: \"Hello, World!\" });\n```\n\n#### With authentication\n\nJust like `JSONRPCServer`, you can inject custom params to `JSONRPCClient` too:\n\n```javascript\nconst client = new JSONRPCClient(\n  // It can also take a custom parameter as the second parameter.\n  (jsonRPCRequest, { token }) =>\n    fetch(\"http://localhost/json-rpc\", {\n      method: \"POST\",\n      headers: {\n        \"content-type\": \"application/json\",\n        authorization: `Bearer ${token}`, // Use the passed token\n      },\n      body: JSON.stringify(jsonRPCRequest),\n    }).then((response) => {\n      // ...\n    })\n);\n\n// Pass the custom params as the third argument.\nclient.request(\"echo\", { text: \"Hello, World!\" }, { token: \"foo's token\" });\nclient.notify(\"log\", { message: \"Hello, World!\" }, { token: \"foo's token\" });\n```\n\n#### With timeout\n\nSometimes you don't want to wait for the response indefinitely. You can use `timeout` to automatically fail the request after certain delay:\n\n```typescript\nconst client = new JSONRPCClient(/* ... */);\n\nclient\n  .timeout(10 * 1000) // Automatically fails if it didn't get a response within 10 sec\n  .request(\"echo\", { text: \"Hello, World!\" });\n\n// Create a custom error response\nconst createTimeoutJSONRPCErrorResponse = (\n  id: JSONRPCID\n): JSONRPCErrorResponse =>\n  createJSONRPCErrorResponse(id, 123, \"Custom error message\");\n\nclient\n  .timeout(10 * 1000, createTimeoutJSONRPCErrorResponse)\n  .request(\"echo\", { text: \"Hello, World!\" });\n```\n\n### Bi-directional JSON-RPC\n\nFor bi-directional JSON-RPC, use `JSONRPCServerAndClient`.\n\n```javascript\nconst webSocket = new WebSocket(\"ws://localhost\");\n\nconst serverAndClient = new JSONRPCServerAndClient(\n  new JSONRPCServer(),\n  new JSONRPCClient((request) => {\n    try {\n      webSocket.send(JSON.stringify(request));\n      return Promise.resolve();\n    } catch (error) {\n      return Promise.reject(error);\n    }\n  })\n);\n\nwebSocket.onmessage = (event) => {\n  serverAndClient.receiveAndSend(JSON.parse(event.data.toString()));\n};\n\n// On close, make sure to reject all the pending requests to prevent hanging.\nwebSocket.onclose = (event) => {\n  serverAndClient.rejectAllPendingRequests(\n    `Connection is closed (${event.reason}).`\n  );\n};\n\nserverAndClient.addMethod(\"echo\", ({ text }) => text);\n\nserverAndClient\n  .request(\"add\", { x: 1, y: 2 })\n  .then((result) => console.log(`1 + 2 = ${result}`));\n```\n\n#### Constructor Options\n\nOptionally, you can pass options to `JSONRPCServerAndClient` constructor:\n\n```typescript\nnew JSONRPCServerAndClient(server, client, {\n  errorListener: (message: string, data: unknown): void => {\n    // Listen to error here. By default, it will use console.warn to log errors.\n  },\n});\n```\n\n### Error handling\n\nTo respond an error, reject with an `Error`. On the client side, the promise will be rejected with an `Error` object with the same message.\n\n```javascript\nserver.addMethod(\"fail\", () =>\n  Promise.reject(new Error(\"This is an error message.\"))\n);\n\nclient.request(\"fail\").then(\n  () => console.log(\"This does not get called\"),\n  (error) => console.error(error.message) // Outputs \"This is an error message.\"\n);\n```\n\nIf you want to return a custom error response, use `JSONRPCErrorException`:\n\n```typescript\nimport { JSONRPCErrorException } from \"json-rpc-2.0\";\n\nconst server = new JSONRPCServer();\n\nserver.addMethod(\"throws\", () => {\n  const errorCode = 123;\n  const errorData = {\n    foo: \"bar\",\n  };\n\n  throw new JSONRPCErrorException(\n    \"A human readable error message\",\n    errorCode,\n    errorData\n  );\n});\n```\n\nAlternatively, use [advanced APIs](#advanced-apis) or implement `mapErrorToJSONRPCErrorResponse`:\n\n```typescript\nimport {\n  createJSONRPCErrorResponse,\n  JSONRPCErrorResponse,\n  JSONRPCID,\n  JSONRPCServer,\n} from \"json-rpc-2.0\";\n\nconst server = new JSONRPCServer();\n\nserver.mapErrorToJSONRPCErrorResponse = (\n  id: JSONRPCID,\n  error: any\n): JSONRPCErrorResponse => {\n  return createJSONRPCErrorResponse(\n    id,\n    error?.code || 0,\n    error?.message || \"An unexpected error occurred\",\n    // Optional 4th argument. It maps to error.data of the response.\n    { foo: \"bar\" }\n  );\n};\n```\n\n### Advanced APIs\n\nUse the advanced APIs to handle raw JSON-RPC messages.\n\n#### Server\n\n```typescript\nimport { JSONRPC, JSONRPCResponse, JSONRPCServer } from \"json-rpc-2.0\";\n\nconst server = new JSONRPCServer();\n\n// Advanced method takes a raw JSON-RPC request and returns a raw JSON-RPC response\nserver.addMethodAdvanced(\n  \"doSomething\",\n  (jsonRPCRequest: JSONRPCRequest): PromiseLike<JSONRPCResponse> => {\n    if (isValid(jsonRPCRequest.params)) {\n      return {\n        jsonrpc: JSONRPC,\n        id: jsonRPCRequest.id,\n        result: \"Params are valid\",\n      };\n    } else {\n      return {\n        jsonrpc: JSONRPC,\n        id: jsonRPCRequest.id,\n        error: {\n          code: -100,\n          message: \"Params are invalid\",\n          data: jsonRPCRequest.params,\n        },\n      };\n    }\n  }\n);\n```\n\n```typescript\n// You can remove the added method if needed\nserver.removeMethod(\"doSomething\");\n```\n\n#### Client\n\n```typescript\nimport {\n  JSONRPC,\n  JSONRPCClient,\n  JSONRPCRequest,\n  JSONRPCResponse,\n} from \"json-rpc-2.0\";\n\nconst send = () => {\n  // ...\n};\nlet nextID: number = 0;\nconst createID = () => nextID++;\n\n// To avoid conflict ID between basic and advanced method request, inject a custom ID factory function.\nconst client = new JSONRPCClient(send, createID);\n\nconst jsonRPCRequest: JSONRPCRequest = {\n  jsonrpc: JSONRPC,\n  id: createID(),\n  method: \"doSomething\",\n  params: {\n    foo: \"foo\",\n    bar: \"bar\",\n  },\n};\n\n// Advanced method takes a raw JSON-RPC request and returns a raw JSON-RPC response\n// It can also send an array of requests, in which case it returns an array of responses.\nclient\n  .requestAdvanced(jsonRPCRequest)\n  .then((jsonRPCResponse: JSONRPCResponse) => {\n    if (jsonRPCResponse.error) {\n      console.log(\n        `Received an error with code ${jsonRPCResponse.error.code} and message ${jsonRPCResponse.error.message}`\n      );\n    } else {\n      doSomethingWithResult(jsonRPCResponse.result);\n    }\n  });\n```\n\n### Typed client and server\n\nTo strongly type `request` and `addMethod` methods, use `TypedJSONRPCClient`, `TypedJSONRPCServer` and `TypedJSONRPCServerAndClient` interfaces.\n\n```typescript\nimport {\n  JSONRPCClient,\n  JSONRPCServer,\n  JSONRPCServerAndClient,\n  TypedJSONRPCClient,\n  TypedJSONRPCServer,\n  TypedJSONRPCServerAndClient,\n} from \"json-rpc-2.0\";\n\n// Use `type` instead of `interface`. In TypeScript, interface can be exnteded,\n// so the type checker considers the possibility of the interface being extended,\n// resulting in an error.\n// Reference: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces\ntype Methods = {\n  echo(params: { message: string }): string;\n  sum(params: { x: number; y: number }): number;\n};\n\nconst server: TypedJSONRPCServer<Methods> = new JSONRPCServer(/* ... */);\nconst client: TypedJSONRPCClient<Methods> = new JSONRPCClient(/* ... */);\n\n// Types are infered from the Methods type\nserver.addMethod(\"echo\", ({ message }) => message);\nserver.addMethod(\"sum\", ({ x, y }) => x + y);\n// These result in type error\n// server.addMethod(\"ech0\", ({ message }) => message); // typo in method name\n// server.addMethod(\"echo\", ({ messagE }) => messagE); // typo in param name\n// server.addMethod(\"echo\", ({ message }) => 123); // return type must be string\n\nclient\n  .request(\"echo\", { message: \"hello\" })\n  .then((result) => console.log(result));\nclient.request(\"sum\", { x: 1, y: 2 }).then((result) => console.log(result));\n// These result in type error\n// client.request(\"ech0\", { message: \"hello\" }); // typo in method name\n// client.request(\"echo\", { messagE: \"hello\" }); // typo in param name\n// client.request(\"echo\", { message: 123 }); // message param must be string\n// client\n//   .request(\"echo\", { message: \"hello\" })\n//   .then((result: number) => console.log(result)); // return type must be string\n\n// The same rule applies to TypedJSONRPCServerAndClient\ntype ServerAMethods = {\n  echo(params: { message: string }): string;\n};\n\ntype ServerBMethods = {\n  sum(params: { x: number; y: number }): number;\n};\n\nconst serverAndClientA: TypedJSONRPCServerAndClient<\n  ServerAMethods,\n  ServerBMethods\n> = new JSONRPCServerAndClient(/* ... */);\nconst serverAndClientB: TypedJSONRPCServerAndClient<\n  ServerBMethods,\n  ServerAMethods\n> = new JSONRPCServerAndClient(/* ... */);\n\nserverAndClientA.addMethod(\"echo\", ({ message }) => message);\nserverAndClientB.addMethod(\"sum\", ({ x, y }) => x + y);\n\nserverAndClientA\n  .request(\"sum\", { x: 1, y: 2 })\n  .then((result) => console.log(result));\nserverAndClientB\n  .request(\"echo\", { message: \"hello\" })\n  .then((result) => console.log(result));\n```\n\n## Build\n\n`npm run build`\n\n## Test\n\n`npm test`\n","readmeFilename":"README.md"}