{"id":28285055,"url":"https://github.com/crossroads/socket.io-webservice","last_synced_at":"2026-04-30T14:31:20.634Z","repository":{"id":26070760,"uuid":"29514520","full_name":"crossroads/socket.io-webservice","owner":"crossroads","description":"A web service that provides push message services using socket.io","archived":false,"fork":false,"pushed_at":"2024-03-05T11:55:11.000Z","size":237,"stargazers_count":1,"open_issues_count":1,"forks_count":1,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-06-16T07:45:29.057Z","etag":null,"topics":["javascript","realtime","socket-io"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/crossroads.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2015-01-20T06:01:49.000Z","updated_at":"2023-10-04T04:39:57.000Z","dependencies_parsed_at":"2024-03-05T04:31:41.475Z","dependency_job_id":"cc5357c1-a8f1-4dba-a0c1-40164f4176ac","html_url":"https://github.com/crossroads/socket.io-webservice","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/crossroads/socket.io-webservice","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crossroads%2Fsocket.io-webservice","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crossroads%2Fsocket.io-webservice/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crossroads%2Fsocket.io-webservice/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crossroads%2Fsocket.io-webservice/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/crossroads","download_url":"https://codeload.github.com/crossroads/socket.io-webservice/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crossroads%2Fsocket.io-webservice/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32468009,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"ssl_error","status_checked_at":"2026-04-30T13:12:06.837Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["javascript","realtime","socket-io"],"created_at":"2025-05-21T18:16:20.597Z","updated_at":"2026-04-30T14:31:20.628Z","avatar_url":"https://github.com/crossroads.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SocketIO Web Service\n\n[![Code Climate](https://codeclimate.com/github/crossroads/socket.io-webservice/badges/gpa.svg)](https://codeclimate.com/github/crossroads/socket.io-webservice)\n[![Issue Count](https://codeclimate.com/github/crossroads/socket.io-webservice/badges/issue_count.svg)](https://codeclimate.com/github/crossroads/socket.io-webservice)\n\nThis project currently runs on NodeJS v16 LTS (boron).\n\nThe SocketIO Web Service allows multiple sites, differentiated via socket.io namespace, to send push messages to connected clients using socket.io.\n\nWhen a client connects a request is made to your website to authenticate the user via the Authorization header, and to retrieve the list of rooms the client belongs to which can be a group name or individual name for direct communication. Your website can then send messages via this webservice to the clients, while the clients can communicate with your website directly.\n\n* [Sending Messages](#sending-messages)\n* [Handling unreliable connections](#handling-unreliable-connections)\n* [Client](#client)\n  * [Special events](#special-events)\n* [Config](#config)\n  * [Add new site](#add-new-site)\n* [Development](#development)\n  * [Installation](#installation)\n  * [Running](#running)\n  * [Debugging](#debugging)\n* [Production](#production)\n  * [Installing Nginx / Passenger](#installing-nginx--passenger)\n  * [Capistrano Deployment](#capistrano-deployment)\n* [Known Issues](#known-issues)\n\n## Sending Messages\n\nThe intention is client apps communicate directly with the server app, and the server app uses the following POST request on this app to communicate with the client app.\n\nPOST `http://socketio-webservice.com/send?site=newsite\u0026apiKey=54321\u0026resync=false`\n\nSupports `Content-Type` `application/json` and `application/x-www-form-urlencoded`. Example:\n\n```json\n{\n  \"rooms\": [\"user_1\", \"staff\"],\n  \"event\": \"update_store\",\n  \"args\": [\"user\", {\"id\":1,\"age\":5}]\n}\n```\n\n* `resync` - performs a check when client indicates a message is received to ensure it's the first in the queue. If not, it will clear the queue and send a message to client to resync all data (default `false`).\n\n## Handling unreliable connections\n\nTo handle unreliable connections socket.io web service uses a Redis cache to store messages and waits for the client to invoke the callback function before removing the message from redis. This is only enabled if the `userRoomPrefix` setting (see add new site section) is specified and if it is then every user must belong to a user room that represents a single user.\n\n```js\n// client app\nsocket.on(\"update_store\", function(entityType, entity, successCallback) {\n  successCallback();\n});\n```\n\nUpon reconnection all the messages for the connecting user will be sent to the client at once as the `_batch` event with just a single successCallback for the batch event:\n\n```js\n// client app\nsocket.on(\"_batch\", batch);\nsocket.on(\"update_store\", update_store);\n\nfunction batch(events, successCallback) {\n  events.forEach(function(args) {\n    var event = args[0]; // e.g. update_store\n    var eventArgs = args.slice(1);\n    window[event].apply(this, eventArgs);\n  });\n  successCallback();\n}\n\nfunction update_store(entityType, entity, successCallback) {\n  ...\n  if (successCallback) {\n    successCallback();\n  }\n}\n```\n\nFor scenarios where socket.io is used to keep a local database in sync with a server database. There is an option you can turn on called `resync` when sending a message to the client (see send messages section). On success callback a check is made on the server to ensure the message being removed is the first one in the queue for that user and event. If not, say due to a client error processing previous message so that success callback was never called, all the queued messages for that user are cleared and a `_resync` event will be sent to the client allowing a full resync to be performed:\n\n```js\n// client app\nsocket.on(\"_resync\", function() {\n  window.location.reload(); // assumes client app performs full sync on page load\n});\n```\n\nIt is up to the client to specify what action to take when the `_resync` is received.\n\n## Client\n\n```js\n// client app\nvar socket = io(\"http://domain.com/\u003cnamespace\u003e?token=12345\u0026deviceId=090909\");\n```\n\n* namespace - the namespace should match the name used in `sites.yml` (see add new site)\n* token - is the token that will form the authorization header with authScheme in sites.yml that will be sent with the request to retrieve rooms from authUrl defined in sites.yml (see add new site)\n* deviceId - this is intended for support of a single user having multiple devices (e.g. could be a user with two browser tabs open if not updating a shared data storage)\n\n## Special events\n\n```js\n// client app\nsocket.on(\"_batch\", function(events, success) {\n  events.forEach(function(args) {\n    window[args[0]].apply(this, args.slice(1));\n  }, this);\n  if (success) { success(); }\n});\n\nsocket.on(\"_resync\", function() {\n  window.location.reload();\n});\n```\n\n* `_batch` - when client connects and if there are awaiting messages they are sent all at once as this event (see Handling unreliable connections)\n* `_resync` - if resync was turned on when sending a message, if message is not first in message queue when success callback was called then this event is sent to let client know to perform a full sync  (see Handling unreliable connections)\n* `_settings` - when client connects an object with settings is sent, currently it just contains `device_ttl`\n\n## Config\n\nYou can configure an app suitable for docker using just environment variables. For example:\n\n```\nNODE_ENV=production\nREDIS_URL=rediss://:\u003cpassword\u003e@\u003chost\u003e:\u003cport\u003e/\u003cdatabase\u003e\nDEVICE_TTL=3600\nPORT=80\nSITE_NAME=goodcity\nAUTH_URL=https://\u003cpath to api\u003e/api/v1/auth/current_user_rooms\nAUTH_SCHEME=Bearer\nAPI_KEY=\u003cinsert api key\u003e\nUSER_ROOM_PREFIX=user_\nUPDATE_USER_URL=https://\u003cpath to api\u003e/api/v1/users/:id\nPUBLIC_CHANNEL=browse\n```\n\nThe above will default to STDOUT logging.\n\nConfiguration details can also be stored in `config.yml` if you want more sites or more finegrained control over logging. Note: if no environment is specified it defaults to production.\n\n```yml\nproduction:\n  port: 80\n  device_ttl: 3600\n  redis:\n    url: rediss://:\u003cpassword\u003e@\u003chost\u003e:\u003cport\u003e/\u003cdatabase\u003e\n  flakeid:\n    datacenter: 0\n    worker: 0\n  io:\n    pingTimeout: 60000\n  servers:\n    - deployer@server1.example.com\n    - deployer@server2.example.com\n```\n\n* port - the port the socket.io webservice will be available at (default 1337)\n* device_ttl - the time messages will continue to be kept once the client becomes offline, set to 0 for messages to be kept indefinitely (default 3600 sec)\n* redis\n  * url - standard Redis connection url\n* flakeid (used to id messages in redis queue)\n  * a full list of options can be found here https://github.com/T-PWK/flake-idgen#usage\n* io\n  * a full list of options can be found here https://github.com/Automattic/socket.io#serveroptsobject\n* winston - a logging library; can use a list of the built-in transports (default is console), options can be found here https://github.com/winstonjs/winston#working-with-transports\n\n## Add new site\nA single site can be configured using ENV variables however, you can also specify a `sites.yml` as follows:\n\n```yml\nnewsite:\n  authUrl: http://newsite.com/socketrooms\n  authScheme: Bearer\n  apiKey: 54321\n  userRoomPrefix: user_\n  updateUserUrl: http://newsite.com/users/:id\n```\n\n* newsite - is the namespace for socketio, the client connects as `io(\"/newsite\")`\n* authUrl - this is the url the socketio webservice will use to retrieve the rooms the authenticated user belongs to, the client is required to provide the token `io(\"/newsite?token=12345\")` on connection and will form part of the Authorization header that will be used when the request is made to authUrl\n* authScheme - is used as part of the Authorization header that will be sent to the authUrl\n* apiKey - used to authenticate requests when sending messages to clients via this webservice\n* userRoomPrefix - the prefix rooms belonging to a single user starts with, used for determining whether to enable handling connection reliability functionality, if specified then every user must belong to a private room\n* updateUserUrl - this is the url the socketio webservice will use to update about the user's last connected and disconnected time. It will be PUT request with parameters example: {\"id\"=\u003e\"8\", \"user\"=\u003e{\"last_connected\"=\u003e\"2015-05-06T07:49:29.196Z\"}}\n\nUse sites.yml if you have more than one site to configure.\n\n## Manual Installation\n\n* Install redis - `sudo apt-get install redis-server redis-tools`\n* Install nodejs - https://github.com/creationix/nvm\n* `git clone` this repository\n* `yarn install`\n\nInstall `nvm` and Node v16:\n\n```shell\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\nnvm install lts/gallium    # v16\n```\n\nAdd the following line to Passenger conf\n\n```\npassenger_nodejs /home/deployer/.nvm/versions/node/v16.20.2/bin/node;\n```\n\nRun the app normally (starts server at http://localhost:1337):\n\n`yarn start`\n\nRun the app in `dev` mode with `nodemon`:\n\n`yarn dev`\n\nRun the app in `debug` mode with `node-debug`:\n\n`yarn debug`\n\n* Add a break point at start of app and hit continue in the debugger\n* Visit your app at e.g. http://localhost:1337 in a different tab\n* Now you can add a break point where you want to debug\n\n\n## Docker setup\n\n* `git clone` this repository\n* `yarn install`\n\nRun the app in `dev` mode with `nodemon`:\n\n* `docker-compose --file docker-compose.local.yml up -d`\n* Visit your app at http://localhost:1337.\n\nRun the app in `debug` mode with `node-debug`:\n\n* Change the start command of `app` in  `docker-compose.local.yml` to `npm run debug`\n* Add a break point at start of app and hit continue in the debugger\n* `docker-compose --file docker-compose.local.yml up -d`\n* Visit your app at e.g. http://localhost:1337 in a different tab\n* Now you can add a break point where you want to debug\n\n## Installing nginx / passenger\n\nAssumes you've installed rvm already\n\n```shell\nyum install nodejs npm\ngem install passenger\nrvmsudo passenger-install-nginx-module --languages nodejs --auto\n```\n\n## Capistrano Deployment\n\nCheck in your code and push it upstream as Capistrano will checkout code from your git repo rather than uploading your local files.\n\n    cap staging deploy\n    cap production deploy\n    cap production deploy:rollback\n\nThe deploy script automatically handles `npm install` on the remote machine and ensures shared files are symlinked to the current folder.\n\n## Docker Compose\n\n* `git clone` this repository and `cd` into the repository folder\n* Run `docker-compose build` to build the container image from source\n* Now you can move the `docker-compose.yml` to another folder together with a `config.yml` and `sites.yml`\n* In the folder with the `docker-compose.yml` run `docker-compose up -d`\n* Your app is now reachable on port `1337` on the server\n\n## Docker Deployment\n\n```\ndocker build -t socketio .\ndocker run \\\n  -p 80:80 \\\n  -e REDIS_URL=redis://host:6379 \\\n  socketio\n\ndocker login \u003cregistry\u003e.azurecr.io\ndocker tag socketio \u003cregistry\u003e.azurecr.io/socketio:master\ndocker push \u003cregistry\u003e.azurecr.io/socketio:master\ndocker push \u003cregistry\u003e.azurecr.io/socketio:live\n```\n\n## Azure Container Registry Builds\n\n```\naz login\naz account set --subscription \"\u003cname of subscription\u003e\"\naz acr build --registry \u003cregistry name\u003e --image socketio:master .\n```\n\n## Known issues\n\n* Nodejs has trouble connecting to local websites via localhost during authentication, the workaround is to start rails (or some other web app) bound to 127.0.0.1 instead (the socket.io authUrl can still say localhost) .e.g\n\n  `rails s --binding=127.0.0.1`\n\n* The app hangs if you set logging mode to `DEBUG=*` when debugging the app\n* Debugger does not work in node versions 0.10.34 and 0.10.35\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrossroads%2Fsocket.io-webservice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrossroads%2Fsocket.io-webservice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrossroads%2Fsocket.io-webservice/lists"}