{"id":15043753,"url":"https://github.com/jyelewis/rpcapi","last_synced_at":"2026-02-11T06:33:27.040Z","repository":{"id":39800922,"uuid":"114563098","full_name":"jyelewis/RPCAPI","owner":"jyelewis","description":"Provides a struture for hosting RPC style APIs, supports both http and websocket access out of the box","archived":false,"fork":false,"pushed_at":"2023-01-04T21:50:37.000Z","size":880,"stargazers_count":1,"open_issues_count":46,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-09T15:34:55.401Z","etag":null,"topics":["api","api-client","api-server","javascript","nodejs","rest-api","rpc-api","socket","socket-io","typescript","websocket-access"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jyelewis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-12-17T19:34:58.000Z","updated_at":"2020-02-23T19:35:16.000Z","dependencies_parsed_at":"2023-02-02T21:15:42.078Z","dependency_job_id":null,"html_url":"https://github.com/jyelewis/RPCAPI","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jyelewis/RPCAPI","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jyelewis%2FRPCAPI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jyelewis%2FRPCAPI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jyelewis%2FRPCAPI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jyelewis%2FRPCAPI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jyelewis","download_url":"https://codeload.github.com/jyelewis/RPCAPI/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jyelewis%2FRPCAPI/sbom","scorecard":{"id":545739,"data":{"date":"2025-08-11","repo":{"name":"github.com/jyelewis/RPCAPI","commit":"7b2a829f504244cff1f967ec2be7a7e30168acef"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.3,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"135 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx","Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-hr2v-3952-633q","Warn: Project is vulnerable to: GHSA-ff7x-qrg7-qggm","Warn: Project is vulnerable to: GHSA-qrmc-fj45-qfc2","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3","Warn: Project is vulnerable to: MAL-2023-462","Warn: Project is vulnerable to: GHSA-xf7w-r453-m56c","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-44pw-h2cw-w3vq","Warn: Project is vulnerable to: GHSA-jp4x-w63m-7wgm","Warn: Project is vulnerable to: GHSA-c429-5p7v-vgjp","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-p9w8-2mpq-49h9","Warn: Project is vulnerable to: GHSA-2pr6-76vf-7546","Warn: Project is vulnerable to: GHSA-8j8c-7jfh-h6hx","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-fvqr-27wr-82fm","Warn: Project is vulnerable to: GHSA-4xc9-xhrj-v574","Warn: Project is vulnerable to: GHSA-x5rq-j2xg-h7qm","Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-2m96-9w4j-wgv7","Warn: Project is vulnerable to: GHSA-h726-x36v-rx45","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-6g33-f262-xjp4","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-2m39-62fm-q8r3","Warn: Project is vulnerable to: GHSA-mf6x-7mm4-x2g7","Warn: Project is vulnerable to: GHSA-j44m-qm6p-hp7m","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-7p7h-4mm5-852v","Warn: Project is vulnerable to: GHSA-38fc-wpqx-33j7","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-rq8g-5pc5-wrhr","Warn: Project is vulnerable to: GHSA-j4f2-536g-r55m","Warn: Project is vulnerable to: GHSA-r7qp-cfhv-p84w","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-6fx8-h7jm-663j","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-fxwf-4rqh-v8g3","Warn: Project is vulnerable to: GHSA-25hc-qcg6-38wj","Warn: Project is vulnerable to: GHSA-xfhh-g9f5-x4m4","Warn: Project is vulnerable to: GHSA-qm95-pgcg-qqfq","Warn: Project is vulnerable to: GHSA-cqmj-92xf-r6r9","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-72mh-269x-7mh5","Warn: Project is vulnerable to: GHSA-h4j5-c7cj-74xg","Warn: Project is vulnerable to: GHSA-whgm-jr23-g3j9","Warn: Project is vulnerable to: GHSA-fwr7-v2mv-hh25","Warn: Project is vulnerable to: GHSA-x9w5-v3q2-3rhw","Warn: Project is vulnerable to: GHSA-9vvw-cc9w-f27h","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-3wcq-x3mq-6r9p","Warn: Project is vulnerable to: GHSA-vh7m-p724-62c2","Warn: Project is vulnerable to: GHSA-r9p9-mrjm-926w","Warn: Project is vulnerable to: GHSA-434g-2637-qmqr","Warn: Project is vulnerable to: GHSA-49q7-c7j4-3p7m","Warn: Project is vulnerable to: GHSA-977x-g7h5-7qgw","Warn: Project is vulnerable to: GHSA-f7q4-pwc6-w24p","Warn: Project is vulnerable to: GHSA-fc9h-whq2-v747","Warn: Project is vulnerable to: GHSA-vjh7-7g9h-fjfh","Warn: Project is vulnerable to: GHSA-4gmj-3p3h-gm8h","Warn: Project is vulnerable to: GHSA-6h5x-7c5m-7cr7","Warn: Project is vulnerable to: GHSA-6x33-pw7p-hmpq","Warn: Project is vulnerable to: GHSA-c7qv-q95q-8v27","Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22","Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp","Warn: Project is vulnerable to: GHSA-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-3rfm-jhwj-7488","Warn: Project is vulnerable to: GHSA-hhq3-ff78-jv3g","Warn: Project is vulnerable to: GHSA-4xcv-9jjx-gfj3","Warn: Project is vulnerable to: GHSA-92xj-mqp7-vmcj","Warn: Project is vulnerable to: GHSA-wxgw-qj99-44c2","Warn: Project is vulnerable to: GHSA-5rrq-pxf6-6jx5","Warn: Project is vulnerable to: GHSA-8fr3-hfg3-gpgp","Warn: Project is vulnerable to: GHSA-gf8q-jrpm-jvxq","Warn: Project is vulnerable to: GHSA-2r2c-g63r-vccr","Warn: Project is vulnerable to: GHSA-cfm4-qjh2-4765","Warn: Project is vulnerable to: GHSA-x4jg-mjrx-434g","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-h7cp-r72f-jxh6","Warn: Project is vulnerable to: GHSA-v62p-rq8g-8h59","Warn: Project is vulnerable to: GHSA-hxcm-v35h-mg2x","Warn: Project is vulnerable to: GHSA-c9g6-9335-x697","Warn: Project is vulnerable to: GHSA-g7q5-pjjr-gqvp","Warn: Project is vulnerable to: GHSA-pv4c-p2j5-38j4","Warn: Project is vulnerable to: GHSA-46c4-8wrp-j99v","Warn: Project is vulnerable to: GHSA-9m6j-fcg5-2442","Warn: Project is vulnerable to: GHSA-hh27-ffr2-f2jc","Warn: Project is vulnerable to: GHSA-rqff-837h-mm52","Warn: Project is vulnerable to: GHSA-8v38-pw62-9cw2","Warn: Project is vulnerable to: GHSA-hgjh-723h-mx2j","Warn: Project is vulnerable to: GHSA-jf5r-8hm2-f872","Warn: Project is vulnerable to: GHSA-wr3j-pwj9-hqq6","Warn: Project is vulnerable to: GHSA-cf66-xwfp-gvc4","Warn: Project is vulnerable to: GHSA-4v9v-hfq4-rm2v","Warn: Project is vulnerable to: GHSA-9jgg-88mc-972h","Warn: Project is vulnerable to: GHSA-g78m-2chm-r7qv","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp","Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44","Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-w7rc-rwvf-8q5r","Warn: Project is vulnerable to: GHSA-r683-j2x4-v87g","Warn: Project is vulnerable to: GHSA-px4h-xg32-q955","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T09:26:56.417Z","repository_id":39800922,"created_at":"2025-08-20T09:26:56.417Z","updated_at":"2025-08-20T09:26:56.417Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272869938,"owners_count":25007130,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-client","api-server","javascript","nodejs","rest-api","rpc-api","socket","socket-io","typescript","websocket-access"],"created_at":"2024-09-24T20:49:33.114Z","updated_at":"2026-02-11T06:33:27.006Z","avatar_url":"https://github.com/jyelewis.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build status](https://travis-ci.org/jyelewis/RPCAPI.svg?branch=master)](https://travis-ci.org/jyelewis/RPCAPI) [![Coverage Status](https://coveralls.io/repos/github/jyelewis/RPCAPI/badge.svg?branch=master)](https://coveralls.io/github/jyelewis/RPCAPI?branch=master)\n\n# RPC API\nProvides a structure for hosting RPC style APIs, supports both http and websocket access out of the box.\n\nRPCAPI is designed to be used within a node.js server,\ngenerally alongside the client application (rpcapi-websocket-client) running in the web browser.\n\nThe server can then easily define 'endpoints' which are classes containing a collection of associated 'actions'\nThese actions are simple javascript functions, which take parameters and return a value.\nActions can then be called remotely, either using a websocket and the client SDK or via a web api. \n\nDesigned to solve the problem of constantly building project structures around socket.io to manage many endpoints\nas well as providing a much nicer way to communicate with the server (RPC instead of messaging).\n\n## Contents\n- [Benefits of RPC over socket messaging](#benefits-of-rpc-over-socket-messaging)\n- [Getting started](#getting-started)\n  - [Installation](#installation)\n  - [Viewing the examples](#viewing-the-examples)\n- [Server side](#server-side) \n  - [Starting a server](#starting-a-server)\n  - [Defining endpoints](#defining-endpoints)\n    - [Complete endpoint example](#complete-endpoint-example)\n    - [Registering an endpoint](#registering-an-endpoint)\n    - [Lifecycle hooks](#lifecycle-hooks)\n    - [Defining an action](#defining-an-action)\n    - [Action parameters](#action-parameters)\n    - [Pushing to the client](#pushing-to-the-client)\n- [Client side](#client-side)\n  - [Accessing actions via the web api](#accessing-actions-via-the-web-api)\n  - [Using the websocket client](#using-the-websocket-client)\n- [Pushing to the client (implementation)](#pushing-to-the-client-implementation)\n  - [Server code](#pushing-to-the-client---server-code)\n  - [Client code](#pushing-to-the-client---client-code)\n- [Authentication](#authentication)\n  - [Checking access keys](#checking-access-keys)\n  - [Providing an access key via webapi](#providing-an-access-key-via-webapi)\n  - [Providing an access key via websocket client](#providing-an-access-key-via-websocket-client)\n- [Advanced topics](#advanced-topics)\n  - [Mocking](#mocking)\n    - [Mocking APIClient example](#mocking-apiclient-example)\n    - [Mocking APIEndpointClient example](#mocking-apiendpointclient-example)\n  - [Creating custom access methods](#creating-custom-access-methods)\n    \n\n## Benefits of RPC over socket messaging\nTraditional socket.io code looks like this\n```javascript\nsocket.emit('userService.authenticateUser', email, password);\nsocket.on('userService.authenticateUser', function(err, isAuthenticated) {\n    if (err) { throw err; }\n\n    if (isAuthenticated) {\n        console.log('Yay we are authenticated');\n    } else {\n        console.log('Authentication failed :(');\n    }\n});\n```\n\nIf we want to handle connection dropouts, timeouts, invalid arguments, unexpected internal server errors etc, this code gets much larger.\n\nOut of the box, RPC API provides a much neater syntax\n```javascript\nconst isAuthenticated = await userService.callAction('authenticateUser', { email, password });\n\nif (isAuthenticated) {\n    console.log('Yay we are authenticated');\n} else {\n    console.log('Authentication failed :(');\n}\n```\nTimeouts, invalid arguments and server errors are all automatically handled.\n\n## Getting started\n\n### Installation\nThis application is most useful with both RPCAPI on the server and RPCAPI-websocket-client on the client.\nThis allows a client application to easily call endpoint actions.\n\nTo install:\n```bash\nnpm install --save rpcapi\n```\n\nor using yarn\n```bash\nyarn add rpcapi\n```\n\n### Viewing the examples\nThe best way to get started is to take a look at the examples directory, which includes a basic project with a few different endpoints\ndesigned to show how to create and register endpoints and actions.\n\nThere is also a client example application which demonstrates how to use rpcapi-websocket-client. This client expects the server example to be running.\n\nTo start the server navigate to `examples/server` then run `npm start`\nThis will start a local server on port 8080\n\nTo view an example endpoint via the webapi navigate to `http://localhost:8081/api/calculator/add?a=1\u0026b=2` in a web browser.\n\nTo start the client, open a new terminal window and navigate to `examples/websocketClient` and run npm start\nThe client will be served on port 8081 and can be viewed in a web browser.\n \n \n\n## Server side\n\n### Starting a server\nThe easiest way to start a server is to use the built in `api.listen()` method.\nThis will set up a web server and websocket server on the given port and respond to api requests.\n```javascript\nconst rpcapi = require('rpcapi');\n\n//Create our API instance\n//This is what will be given to our access methods, we will register all our endpoints against this object\nconst api = new rpcapi.API();\n\n//Register your endpoint classes here\n//api.registerEndpoint('test', TestEndpoint);\n\n//server listen\napi.listen(8081).then(() =\u003e {\n    console.log('Example API Server listening on port 8081');\n});\n```\n\nIf you want to manually manage your server, have a look at the advanced topic [Manage express and socketio manually](#manage-express-and-socketio-manually)\n\n### Defining endpoints\nEndpoints are defined as classes, extending rpcapi.APIEndpoint\n\nEndpoints contain actions, which can be remotely called.\nActions can define, which are passed to them when they are called\n\n#### Complete endpoint example\n```javascript\nclass ExampleEndpoint extends rpcapi.APIEndpoint {\n    constructor() {\n        super();\n        \n        $sayHelloParams = { name: 'string' };\n    }\n    \n    connect() {\n        //Runs when a client connects\n    }\n    \n    disconnect() {\n        //Runs when a client disconnects\n    }\n    \n    $sayHello({ name }) {\n        return {\n            greeting: `Hello ${name}!`\n        };\n    }\n}\n\napi.registerEndpoint('example', ExampleEndpoint);\n```\n\n#### Registering an endpoint\nEndpoint classes must be registered to the api.\nYou can register many endpoints, but they must all have different names\n```javascript\napi.registerEndpoint('example', ExampleEndpoint);\n```\n\n#### Lifecycle hooks\nLifecycle hooks fire when an endpoint is connected and disconnected.\nThese functions can be used to subscribe to events that the client might be interested in (for example a redis pub/sub service) or setting timers.\nIt is important to clean up and listeners and timers on disconnect to avoid wasting server time after a client has left.\nAn error will be thrown if .emit() is called after disconnect.\n\n```javascript\nclass EmitterEndpoint extends rpcapi.APIEndpoint {\n    connect() {\n        //Runs when a client connects\n        this.emitTimer = setInterval(() =\u003e {\n            this.emit('randomNumber', Math.random());\n        });\n    }\n    \n    disconnect() {\n        //Runs when a client disconnects\n        clearTimeout(this.emitTimer);\n    }\n}\n```\n\n#### Defining an action\nActions are just functions on an endpoint class.\n\n - They must start with a '$' (this is not included as part of the action name)\n - They must return an object\n - They may return a promise that eventually resolves to an object (the request will wait for the action to resolve)\n - Any parameters must be explicitly defined (see below 'Action parameters')\n \n```javascript\nclass ExampleEndpoint extends rpcapi.APIEndpoint {\n    $sayHello() {\n        return {\n            greeting: 'Hi!'\n        };\n    }\n}\n```\n\n#### Action parameters\n - All action parameters must be explicitly defined.\n - They are defined in an object on the endpoint, named ${actionName}Params\n - They must specify a variable type\n\nAction parameter objects are written in the format\n```javascript\n$sayHelloParams = { name: 'string' }\n```\nWhen using javascript, these must be defined in the constructor of the class\n```javascript\nclass ExampleEndpoint extends rpcapi.APIEndpoint {\n    constructor() {\n        super();\n        \n        $sayHelloParams = { name: 'string' };\n    }\n    \n    $sayHello({ name }) {\n        return {\n            greeting: `Hello ${name}!`\n        };\n    }\n}\n```\n\nIf you are using typescript, these can be defined throughout the body of the class, which makes it easier to keep the params definition with the action function\n```typescript\nclass ExampleEndpoint extends rpcapi.APIEndpoint {\n    $sayHelloParams = { name: 'string' };\n    $sayHello({ name }: { name: string }) {\n        return {\n            greeting: `Hello ${name}!`\n        };\n    }\n}\n```\n\n#### Pushing to the client\nSee further down for more details.\nThere are 2 important functions to use when pushing data to the client\n\n`this.canEmit()` - Boolean, returns true if the current connection method supports pushing, this.emit() will crash if called when this is false\n`this.emit(eventName, arg1, arg2, etc...)` - Send an event to the client \n\nSee implementation details below at [Pushing to the client (implementation)](#pushing-to-the-client-implementation)\n\n## Client side\n### Accessing actions via the web api\nBy default the `WebAPIAccessMethod` binds to the path `/api`\nThis can be changed by passing in the `prefix` configuration parameter.\n\nAll actions must be called using the 'post' http method.\nparameters can either be given as json or in a url encoded format\n\n```javascript\nconst webApi = new rpcapi.WebAPIAccessMethod(api, { prefix: '/myApi' });\n```\n\nOnce the server is running, you can access actions directly by requesting the url\n```\n/api/{endpoint name}/{action name}\nparam1={value1}\u0026param2={value2}\n```\n\nFor example, an authentication action\n```\n/api/login/authenticateUser\nusername=admin\u0026password=Qwerty1\n```\n\nWhen using the web api, values are automatically converted to the correct type (as specified in `${action}Params`)\nTypes are checked and the endpoint will return an error if the parameters are not given correctly.\n\nIf an action requires an object or array for a parameter, you can use JSON to provide this value\nFor example\n```\n/api/calculator/sumAll\nvalues=[1, 2, 3, 4]\n```\n\n#### Web api limitations\nBecause the web api does not involve a persistent connection, endpoints behave slightly differently to using the websocket client\nAnytime an action is called, a new instance of the appropriate action is created, and then destroyed when the request is complete.\nThis means that every api call is executed in its own instance of the endpoint.\n\nAnother limitation is pushing data to the client, because http is not bidirectional the server cannot push data to the client in the background.\nIt is possible to check if the current connection supports pushing to the client using `this.canEmit()` within the endpoint.\nSee the `PushToClientEndpoint.js` file in the examples for reference. \n\n### Using the websocket client\nThe websocket client is the easiest way to call actions on the server from a web browser.\nThere is an example of the websocket client in the examples directory, but the basic structure is straightforward.\n\n```javascript\nconst rpcapiClient = require('rpcapi-websocket-client');\n\nconst api = new rpcapiClient.APIClient('http://localhost:8081/');\n\napi.connect()\n    .then(doMaths)\n    .catch(console.error);\n\nasync function doMaths() {\n    const calculatorEndpoint = await api.connectToEndpoint('calculator');\n    \n    //Call a remote method on the CalculatorEndpoint instance\n    const addResult = await calculatorEndpoint.callAction('add', { a: 1, b: 2 });\n    console.log('1 + 2 =', addResult.value);\n\n\n    //Call another method on the same instance\n    const multiplyResult = await calculatorEndpoint.callAction('multiply', { a: 5, b: 4 });\n    console.log('5 * 4 =', multiplyResult.value);\n    \n    calculatorEndpoint.disconnect();\n}\n```\n\n#### Long lived endpoints\nIt is important to note that when you call `api.connectToEndpoint('calculator')` you are creating a new instance of the CalculatorEndpoint on the server.\nThis is beneficial as this class instance can keep state across many action calls\nFor example\n```javascript\nconst adderEndpoint = await api.connectToEndpoint('adder');\n\nconsole.log(await adderEndpoint.callAction('getValue')); //0\n\nadderEndpoint.callAction('add', { number: 1 });\nconsole.log(await adderEndpoint.callAction('getValue')); //1\n\nadderEndpoint.callAction('add', { number: 5 });\nconsole.log(await adderEndpoint.callAction('getValue')); //6\n \n``` \n\nThese instances are individual to each client, you can even create many instances/connections from the same client.\nFor example\n```javascript\n//Create 2 adder connections\nconst adder1Endpoint = await api.connectToEndpoint('adder');\nconst adder2Endpoint = await api.connectToEndpoint('adder');\n\nconsole.log(await adder1Endpoint.callAction('getValue')); //0\nconsole.log(await adder2Endpoint.callAction('getValue')); //0\n\nadder1Endpoint.callAction('add', { number: 14 });\nadder2Endpoint.callAction('add', { number: 2 });\n\nconsole.log(await adder1Endpoint.callAction('getValue')); //14\nconsole.log(await adder2Endpoint.callAction('getValue')); //2\n \n```\n\n## Pushing to the client (implementation)\nOne of the biggest advantages of sockets is the ability to push data from the server to the client without the client explicitly asking for data.\nThis is possible using the websocket client.\n\nPushing data to the client requires a websocket conenction, it will not work over a webapi connection.\nTo ensure the current connection method supports pushing/emitting, call `this.canEmit()`\n\n### Pushing to the client - server code\n```javascript\nclass PushToClientEndpoint extends rpcapi.APIEndpoint {\n    //Cleanup when the client disconnects\n    disconnect() {\n        clearTimeout(this.pushTimer);\n    }\n\n    $startPushing() {\n        if (!this.canEmit()) {\n            return { result: 'Cannot push, the connected method does not support pushing' };\n        }\n\n        clearTimeout(this.pushTimer);\n        this.pushTimer = setInterval(() =\u003e {\n            this.emit('time', Date.now()); //Will push to the client, the client can listen via apiEndpoint.on('time', cb);\n        }, 1000);\n\n        return { result: 'Pushing the current time every second (event: time)' };\n    }\n}\n```\n\n### Pushing to the client - client code\n```javascript\nconst pushToClientEndpoint = api.connectToEndpoint('pushToClient');\n\n//Register code to run when the server sends us a 'time' event\npushToClientEndpoint.on('time', (currentTime) =\u003e {\n    console.log('The server says the time is', currentTime);\n});\n\n//Call the startPushing action to request the server pushes the time to us every second\nawait pushToClientEndpoint.callAction('startPushing');\n```\n\n## Authentication\nWhen connecting to an endpoint, you can optionally provide an accessKey, this is available to the endpoint class via `this.accessKey`\n\n### Checking access keys\nFrom the api endpoint on the class, throw AccessDeniedError to reject a request.\nGenerally this will be done after doing a lookup on the accessKey (`this.accessKey`) to determine whether the user has access to the requested resource.\n\nAccessDeniedError can be thrown from connect() to prevent the connection being completed.\nIt can also be thrown from a specific action.\n\n### Providing an access key via webapi\nAccess keys are simply passed as url parameters eg.\n```\nhttp://localhost:8081/api/calculator/add?accessKey=qwer2134\u0026a=1\u0026b=2\n```\n\n### Providing an access key via websocket client\nWhen connecting to an endpoint, pass the access key as the second parameter to .connectToEndpoint()\n```javascript\nconst ep = await apiClient.connectToEndpoint('test', 'myAccessKey');\n```\n\nIt is also possible to set a default access key at a connection level.\n\nThis is useful when using access keys to identify a user, the default access key can be set after they login\nand from then on all requests will be authenticated. (NOTE: Existing connections will remain unchanged)\n\n```javascript\napiClient.accessKey = 'myAccessKey';\nconst ep = await apiClient.connectToEndpoint('test');\n```\n\n\n## Advanced topics\n\n### Manage express and socketio manually\nBy default, RPCAPI will register its own express app and socket io server on the port given when you call `api.listen()`\nHowever, sometimes control is required over these services.\nFor example when,\n - Creating a custom 404 page\n - Sharing a single port for both RPCAPI and another web service\n - Sending custom socket messages using a different namespace\n - Using RPCAPI in an application where express and socketio are already configured \n\nThe webapi access method is designed to be run with an express webserver,\nthe websocket access method is designed to be run with a socket io instance.\n\nThese services need to be configured in order to provide access into your api endpoints.\n```javascript\nconst rpcapi = require('rpcapi');\nconst http = require('http');\nconst express = require('express');\nconst socketio = require('socket.io');\n\n//Setup express web server\nconst app = express();\nconst server = new http.Server(app);\nconst io = socketio(server);\n\n//Create our API instance\n//This is what will be given to our access methods, we will register all our endpoints against this object\nconst api = new rpcapi.API();\n\n//Setup websocket access method\nconst socketApi = new rpcapi.WebSocketAccessMethod(api);\nsocketApi.bind(io); //Bind socket access method to socket.io instance\n\n//Setup webapi access method\nconst webApi = new rpcapi.WebAPIAccessMethod(api);\nwebApi.bind(app); //Bind webapi access method to express web server\n\n//server listen\nserver.listen(8081, () =\u003e {\n    console.log('Example API Server listening on port 8081');\n});\n``` \n\n### Mocking\nOn the client it can be difficult to test modules that directly communicate with the server.\n\nRPCAPI-websocket-client provides ready to go mocking classes for both\n - APIClient (the single connection object to the server)\n - APIEndpointClient (An endpoint, created by api.connectToEndpoint())\n \nThese classes do not establish any connection with the server, they simply simulate a predefined api structure for testing\n \n#### Mocking APIClient example\n - Many endpoints and actions can be mocked (you could mock your entire backend if you wanted to)\n - There is fake delay of 10ms each call to simulate 'server lag'\n   - This may be changed in a future version, while writing test cases it is not nice to be 'waiting' an arbitrary length of time before making more assertion\n\n```javascript\nconst mockAPIClient = new MockAPIClient({\n    endpoints: {\n        testEndpoint: {\n            actions: {\n                testAction: () =\u003e {\n                    return { someValue: 123 };\n                }\n            }\n        }\n    }\n});\n\nawait mockAPIClient.connect();\n\nconst mockEP = await mockAPIClient.connectToEndpoint('testEndpoint');\nconst response = await mockEP.callAction('testAction');\nconsole.log(response); // { someValue: 123 }\n``` \n\n#### Mocking APIEndpointClient example\nMockAPIEndpointClient is very similar to MockAPIClient, however it only mocks a single endpoint, and does not require 'connecting' (it simulates a single connected endpoint)\n```javascript\n    const mockAPIEndpointClient = new MockAPIEndpointClient({\n        actions: {\n            testAction: () =\u003e {\n                return { a: 1 };\n            },\n            otherAction: () =\u003e {\n                return { a: 2 };\n            }\n        }\n    });\n    \n    const result1 = await mockAPIEndpointClient.callAction('testAction');\n    console.log(result1); //{ a: 1 }\n    \n    const result2 = await mockAPIEndpointClient.callAction('otherAction');\n    console.log(result2); //{ a: 2 }\n```\n\n### Creating custom access methods\nAccess methods are nothing special, they are just a module that takes in an API instance.\nThey can create new endpoint instances by calling api.getEndpoint(endpointName) to get an APIEndpoint instance\n\nOn this instance you can then call:\n - `actionExists(actionName)` - Boolean, if action exists\n - `actionParams(actionName)` - Object, keyed list of parameters and their types\n - `connect()` - Call this when the client is connected to this endpoint (generally immediately after creation). Only call this if the connection is long lived\n - `disconnect()` - Call when the client disconnects / the endpoint is not required anymore. Only call this if the connection is long lived\n - `registerEmitHandler(handlerFunc)` - Provide a function that will be called if the endpoint calls this.emit(), once you have provided a function this.canEmit() will return true\n - `callAction(actionName, args)` - Call an action by name","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjyelewis%2Frpcapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjyelewis%2Frpcapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjyelewis%2Frpcapi/lists"}