{"id":18544609,"url":"https://github.com/instructure/pandapush","last_synced_at":"2026-03-13T14:30:55.872Z","repository":{"id":33838706,"uuid":"151296676","full_name":"instructure/pandapush","owner":"instructure","description":"Browser-based, multi-tenant, pub/sub service","archived":false,"fork":false,"pushed_at":"2026-03-12T15:36:37.000Z","size":1716,"stargazers_count":4,"open_issues_count":1,"forks_count":2,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-03-12T17:42:50.734Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/instructure.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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-10-02T17:39:46.000Z","updated_at":"2026-03-12T15:32:13.000Z","dependencies_parsed_at":"2025-11-20T14:02:24.535Z","dependency_job_id":null,"html_url":"https://github.com/instructure/pandapush","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/instructure/pandapush","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Fpandapush","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Fpandapush/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Fpandapush/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Fpandapush/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/instructure","download_url":"https://codeload.github.com/instructure/pandapush/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Fpandapush/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30468265,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T11:00:43.441Z","status":"ssl_error","status_checked_at":"2026-03-13T11:00:23.173Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":[],"created_at":"2024-11-06T20:17:05.443Z","updated_at":"2026-03-13T14:30:55.862Z","avatar_url":"https://github.com/instructure.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Overview\n\nPandapush is a web pub/sub system similar to [Pusher](http://pusher.com/).\n\nCurrently built on [Faye](http://faye.jcoglan.com/) for event routing\nand dispatch. (Pandapush adds a layer of multi-tenancy and\nauthentication.)\n\n## Getting Started\n\n### Using the standalone Docker image\n\n```bash\n$ docker run -ti -p 3000:80 -e AUTH_METHOD=basic -e ADMIN_USERNAME=admin -e ADMIN_PASSWORD=password instructure/pandapush:latest\n```\n\nThis will start Pandapush running on your docker host. If that's\nlocalhost, then access the web ui at http://localhost:49000/admin. Log\nin with `admin`/`password`.\n\n### From the repository, using docker-compose\n\n```bash\n$ docker compose run --rm -u root web chown docker:docker node_modules\n$ docker compose run --rm web npm install\n$ docker compose run --rm webpack npm install\n$ docker compose up\n```\n\nThis will start Pandapush on http://pandapush.docker/admin.\n\n### Create an Application and Key:\n\nCreate an application, and a key.\n\nExamples below will use these values:\n\n| Name             | Value                                    |\n| ---------------- | ---------------------------------------- |\n| application name | testapp                                  |\n| application id   | fRP0y2aVpYCKiW6PIFOK                     |\n| key id           | PSIDv4ADyV6V9fQ2BgJZ                     |\n| key secret       | aWvMCPXnV599u6hJ71YJqAKSz0t0Lihs09DM92xS |\n| key expires      | 2020-01-01T07:00:00.000Z                 |\n\n### Subscribe to a channel in a browser\n\nUse the client library to subscribe to a public channel (see discussion\nbelow about public vs private).\n\n```html\n\u003c!-- pull in the client in your html --\u003e\n\u003cscript src=\"http://localhost:5000/client.js\"\u003e\u003c/script\u003e\n```\n\n```javascript\nvar client = new Pandapush.Client(\"http://localhost:5000/push\");\nclient.subscribe(\"/fRP0y2aVpYCKiW6PIFOK/public/messages\", function(message) {\n  console.log(\"got message: \", message);\n});\n```\n\n### Push an event\n\nCreate a token and push an event via HTTP POST.\n\n(We need a library for this for Ruby and JavaScript as well.)\n\n```ruby\nrequire 'httparty'\n\nHTTParty.post(\"http://localhost:5000/channel/fRP0y2aVpYCKiW6PIFOK/public/messages\",\n  basic_auth: {\n    username: 'PSIDv4ADyV6V9fQ2BgJZ',\n    password: 'aWvMCPXnV599u6hJ71YJqAKSz0t0Lihs09DM92xS'\n  },\n  body: {\n    # this is your message payload, which can be whatever you want\n    foo: \"bar\"\n  })\n```\n\nYou should see the message arrive in your browser console.\n\n## Channel Names\n\nChannel names must be formatted as absolute path names whose segments\nmay contain only letters, number, and the symbols -, \\_, and ~.\n\nThe first segment of the channel must be the applicationId, the second\nsegment must be \"public\" or \"private\", and the remainder is up to you:\n\n`/\u003capplicationId\u003e/\u003c\"public\" or \"private\"\u003e/whatever/you/want`\n\nFor example:\n\n`/fRP0y2aVpYCKiW6PIFOK/private/users/412342/message_count`\n\nThe public/private portion designates whether or not a token is needed\nto subscribe to that channel. Publishing always requires authentication.\n\nThere is another channel type that you'll probably never use: `meta`.\nEvents are pushed to `meta` channels with monitoring information on the\napplication (like # of connected clients). You can't push to `meta`\nchannels.\n\n### Wildcards\n\nYou can also subscribe to all channels under a path using wildcards. Wildcards can\nonly appear as the last path component, and must be either `*` or `**`.\nA single `*` signals to receive messages for all channels under that\npath for a single level, and `**` is recursive.\n\nFor example, if you subscribe to the channel `/users/1/**`,\nyou will receive notifications for `/users/1/foo` and\n`/users/1/foo/bar`. If you subscribe to `/users/1/*` you will receive\npushes for `/users/1/foo` but not `/users/1/foo/bar`.\n\n|                         | sub `/users/1/*` | sub `/users/1/**` |\n| ----------------------- | ---------------- | ----------------- |\n| push `/users/1`         | not received     | not received      |\n| push `/users/1/foo`     | received         | received          |\n| push `/users/1/foo/bar` | not received     | received          |\n\n## Authentication\n\nAuthentication is done either by supplying a key and secret or a token\nsigned with a key. The key/secret method can be used for server-side\npushes. Normally you will generate tokens to give to clients in\nbrowsers. (You never want to give your key secret to a browser, as it\nshould be kept... secret.)\n\nTokens are scoped to publishing/subscribing to a specific channel. You\nneed a token to subscribe to `/private/` and `/presence/` channels. Generally, you\nwill be using private channels. You should generate the tokens server\nside, because they require using the token secret to sign.\n\nTokens are [JWTs](http://www.intridea.com/blog/2013/11/7/json-web-token-the-useful-little-standard-you-haven-t-heard-about).\nYou should use a library to generate them. The payload has the contents:\n\n| Field    | Required? | Description                                                                                  |\n| -------- | --------- | -------------------------------------------------------------------------------------------- |\n| keyId    | required  | The id of the key used for signing the token.                                                |\n| channel  | required  | The channel name the token works for. Must start with `/\u003capp\u003e/private/` or `/\u003capp\u003e/public/`. |\n| pub      | optional  | `true` if this token allows publishing.                                                      |\n| sub      | optional  | `true` if this token allows subscribing. Note that `sub` on a public channel is redundant.   |\n| presence | optional  | An object identifying the user for presence channels.                                        |\n| exp      | optional  | Unix timestamp of when the token should expire.                                              |\n\n[jwt.io](http://jwt.io) is useful when debugging JSON web tokens.\n\nRuby example:\n\n```ruby\nrequire 'jwt'\n\ntoken = JWT.encode({\n  keyId: \"PSIDv4ADyV6V9fQ2BgJZ\",\n  channel: \"/fRP0y2aVpYCKiW6PIFOK/public/messages\",\n  pub: true\n}, \"aWvMCPXnV599u6hJ71YJqAKSz0t0Lihs09DM92xS\")\n```\n\n## Publishing REST API\n\nIf using a key/secret for auth:\n\n```bash\n$ curl -u \u003ckeyId\u003e:\u003ckeySecret\u003e -H \"Content-Type: application/json\" -d '{text:\"hello\"}' https://pp.instructure.com/channel/\u003capp\u003e/private/users/123/messages\n```\n\nUsing a token for auth:\n\n```bash\n$ curl -H \"Authorization: Token \u003ctoken\u003e\" -H \"Content-Type: application/json\" -d '{text:\"hello\"}' https://pp.instructure.com/channel/\u003capp\u003e/private/users/123/messages\n```\n\n## Subscribing with the Pandapush client\n\nTo subscribe to private channels, you must specify authentication information.\nThis is easiest to do by using the Pandapush client:\n\n```html\n\u003cscript src=\"https://pandapush.hostname/client.js\"\u003e\u003c/script\u003e\n```\n\n```javascript\nconst CHANNEL = \"/applicationid/private/foo\"; // sent by server\nconst TOKEN = \"...\"; // sent by server\nclient = new Pandapush.Client(\"https://pandapush.hostname/push\");\nclient.subscribeTo(CHANNEL, TOKEN, function(message) {\n  console.log(\"got message!\");\n});\n```\n\nThe Pandapush client is also a Faye client, and supports all the\nmethod described in the [Faye documentation](http://faye.jcoglan.com/security/authentication.html).\n\n## Presence\n\nPresence is a feature you can use to signal to a group of subscribers\nto a channel who else is subscribed to that channel. Presence channels\nbegin with `/presence/`, and when a user subscribes to a presence\nchannel, they must have a `presence` object in their token that has\nat least an `id` field. For example, the token may be an encoded JWT of:\n\n```json\n{\n  \"keyId\": \"PSIDv4ADyV6V9fQ2BgJZ\",\n  \"channel\": \"/fRP0y2aVpYCKiW6PIFOK/presence/generaltalk\",\n  \"sub\": true,\n  \"presence\": {\n    \"id\": \"user1\",\n    \"name\": \"Joe\",\n    \"avatar_url\": \"https://gravatar/foo\"\n  }\n}\n```\n\nWhen the client subscribes to the channel `/fRP0y2aVpYCKiW6PIFOK/presence/generaltalk`\nusing the given token, all other subscribers to that channel will receive a\nnotification:\n\n```json\n{\n  \"subscribe\": {\n    \"user1\": {\n      \"id\": \"user1\",\n      \"name\": \"Joe\",\n      \"avatar_url\": \"https://gravatar/foo\"\n    }\n  }\n}\n```\n\n\"Joe\" will also receive a callback similar to the one above, but with all\nusers currently subscribed to that channel.\n\nWhen Joe disconnects, all other users will receive a message:\n\n```json\n{\n  \"unsubscribe\": {\n    \"user1\": null\n  }\n}\n```\n\nPresence data should be kept small, as it is persisted in redis in memory.\n\n# Running in Production\n\nThe standalone docker image uses an embedded redis process and sqlite for storing\napplication metadata, but you don't want that in production.\n\n## Redis hosts\n\nYou can specify one or more redis hosts with the `REDIS_HOSTS` environment variable.\nPass `hostname:port` pairs, separated by commas.\n\n## Database (postgres)\n\nYou can specify a postgres database to use with the following environment vars:\n\n```\nDATABASE=postgres\nDATABASE_ADDRESS=\u003chost or ip\u003e\nDATABASE_PORT=\u003cport\u003e\nDATABASE_USERNAME=\u003cusername\u003e\nDATABASE_PASSWORD=\u003cpassword\u003e\nDATABASE_NAME=pandapush\n```\n\nInitialize the database (and apply further migrations) with the following command run\ninside one of your pandapush containers:\n\n```\nknex --knexfile server/knexfile.js migrate:latest\n```\n\n# Mobile\n\nNote that this is _not_ a \"Push Notification\" service like for iOS and\nAndroid. There do appear to be some open-source Faye clients\nfor iOS and Android, but I have not tested any of them.\n\n# Testing\n\n- Run tests in docker compose: `docker compose run --rm web npm run test:coverage`\n\n## Manual Testing\n\nCurrently, there is lots of room for improving our test coverage/quality.\nTherefore, it's important that we manually test the basic pub-sub functionality via the UI as well.\n\n**Prerequisite**: setup dinghy-http-proxy\n\n1. Run dinghy:\n    ```shell\n    docker run -d --restart=always \\\n      -v /var/run/docker.sock:/tmp/docker.sock:ro \\\n      -v ~/.dinghy/certs:/etc/nginx/certs \\\n      -p 80:80 -p 443:443 -p 19322:19322/udp \\\n      -e DNS_IP=127.0.0.1 -e CONTAINER_NAME=http-proxy \\\n      --name http-proxy ktgeek/dinghy-http-proxy\n    ```\n2. Create `/etc/resolver/docker`\n   ```text\n   nameserver 127.0.0.1\n   port 19322\n   ```\n\nAfter setting up dinghy, we can\n\n1. spin up the docker stack: `docker compose up -d`\n2. check out the UI: `open http://pandapush.docker/admin`\n\nFor testing, we can use the existing `devapp`: `open http://pandapush.docker/admin/application/devapp/console`.\n\nSubscribe to a topic and then publish a message. The message should be visible in the bottom of the page.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finstructure%2Fpandapush","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finstructure%2Fpandapush","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finstructure%2Fpandapush/lists"}