{"_id":"sockudo","_rev":"4-149988a021033a6db9d36b20c870bc16","name":"sockudo","dist-tags":{"latest":"2.2.0"},"versions":{"1.0.0":{"name":"sockudo","version":"1.0.0","keywords":["sockudo","websockets","realtime"],"author":{"name":"Sockudo"},"license":"MIT","_id":"sockudo@1.0.0","maintainers":[{"name":"sockudorealtime","email":"radu@sockudo.io"}],"contributors":[{"name":"fabrik42","email":"fabrik42@gmail.com"},{"name":"Jaewoong Kim","email":"jwoongkim@gmail.com"},{"name":"Phil Leggetter","email":"phil@leggetter.co.uk"},{"name":"Paweł Ledwoń","email":"me@ludvik.pl"}],"homepage":"https://github.com/sockudo/sockudo/tree/master/server-sdks/sockudo-http-node#readme","bugs":{"url":"https://github.com/sockudo/sockudo/issues"},"dist":{"shasum":"2104b3a4086f0ceba65baac295a973633b837c43","tarball":"https://registry.npmjs.org/sockudo/-/sockudo-1.0.0.tgz","fileCount":17,"integrity":"sha512-fttyhJeoMOHkmpbkngoG7jonTMvUsppo0W2dY5K+T33aAckzS+NWSGgm73PXOSKsmweMae9CklaP2oLPoHTABg==","signatures":[{"sig":"MEQCIBl7/ux23SAmn4+B8EOJwiJPdPIdM/bac21HtHE7YmsiAiAlizck+diQ00psv7UOE/a4YdhzhmiK68dC7W5UmdqdQg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":71745},"main":"dist/sockudo.js","types":"index.d.ts","engines":{"node":">=16"},"gitHead":"4530b32926b5dcfc364d82b196bce6ed69e178f4","scripts":{"lint":"npx oxlint --config .oxlintrc.json src tests examples index.d.ts && npx oxfmt --check src tests examples index.d.ts package.json tsconfig.json .oxlintrc.json","test":"npx mocha 'tests/{integration,remote}/**/*.js'","build":"npm run clean && tsc -p tsconfig.json","clean":"rm -rf dist","format":"npx oxfmt src tests examples index.d.ts package.json tsconfig.json .oxlintrc.json","pretest":"npm run build","typecheck":"tsc --noEmit -p tsconfig.json","local-test":"npx mocha 'tests/integration/**/*.js'","prelocal-test":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"sockudorealtime","email":"radu@sockudo.io"},"overrides":{"qs":"^6.14.0","tar":"^6.2.1","cookie":"^0.7.1","lodash":"^4.17.23","pbkdf2":"^3.1.5","sha.js":"^2.4.12","elliptic":"^6.6.1","form-data":"^3.0.4","cipher-base":"^1.0.6","serialize-javascript":"^7.0.5"},"repository":{"url":"git+https://github.com/sockudo/sockudo.git","type":"git","directory":"server-sdks/sockudo-http-node"},"_npmVersion":"11.13.0","description":"Node.js client to interact with the Sockudo REST API","directories":{},"_nodeVersion":"24.16.0","dependencies":{"is-base64":"^1.1.0","tweetnacl":"^1.0.0","node-fetch":"^2.7.0","tweetnacl-util":"^0.15.0","abort-controller":"^3.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"nock":"^12.0.3","mocha":"^11.3.0","oxfmt":"^0.4.0","sinon":"=1.10.2","oxlint":"^0.15.14","express":"^4.22.1","webpack":"^5.107.2","expect.js":"=0.3.1","typescript":"^5.9.2","@types/node":"^14.14.6","webpack-cli":"^7.0.3","@types/node-fetch":"^2.5.7","https-proxy-agent":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/sockudo_1.0.0_1781943276696_0.8287777743052709","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"sockudo","version":"2.0.0","keywords":["realtime","sockudo","websockets"],"author":{"name":"Sockudo"},"license":"MIT","_id":"sockudo@2.0.0","maintainers":[{"name":"sockudorealtime","email":"radu@sockudo.io"}],"contributors":[{"name":"fabrik42","email":"fabrik42@gmail.com"},{"name":"Jaewoong Kim","email":"jwoongkim@gmail.com"},{"name":"Phil Leggetter","email":"phil@leggetter.co.uk"},{"name":"Paweł Ledwoń","email":"me@ludvik.pl"}],"homepage":"https://github.com/sockudo/sockudo/tree/master/server-sdks/sockudo-http-node#readme","bugs":{"url":"https://github.com/sockudo/sockudo/issues"},"dist":{"shasum":"8fb086f1f859da5592ae78b9e984cbc62cf51e91","tarball":"https://registry.npmjs.org/sockudo/-/sockudo-2.0.0.tgz","fileCount":17,"integrity":"sha512-fzmOyPc7RAu9R9SmdX2PTk+E3cYqRR2tvLVdc6CjTrqguMx2QVOqDbygSHvlr1/RKQdZLUEbxLCDqTXI9XrL7w==","signatures":[{"sig":"MEUCIQDTgWs8A/bFWBnCdM1aau/bUs3ASkszJMs+azxWZLhHkwIgMNOumBRHOR7Z+YoESLfEajwMSKjGsYFdasMp6wmQ8nU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/sockudo@2.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":71359},"main":"dist/sockudo.js","types":"index.d.ts","engines":{"node":">=16"},"gitHead":"ff3d5f949f336b46c538c85b2ad91d7b9d862e97","scripts":{"lint":"npx oxlint --config .oxlintrc.json src tests examples index.d.ts","test":"npx mocha 'tests/{integration,remote}/**/*.js'","build":"npm run clean && tsc -p tsconfig.json","clean":"rm -rf dist","format":"npx oxfmt src tests examples index.d.ts package.json tsconfig.json .oxlintrc.json","pretest":"npm run build","typecheck":"tsc --noEmit -p tsconfig.json","local-test":"npx mocha 'tests/integration/**/*.js'","format:check":"npx oxfmt --check src tests examples index.d.ts package.json tsconfig.json .oxlintrc.json","prelocal-test":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:1abe12fa-37c6-4b0a-b34d-73b2d88a59be"}},"overrides":{"qs":"^6.14.0","tar":"^6.2.1","cookie":"^0.7.1","lodash":"^4.17.23","pbkdf2":"^3.1.5","sha.js":"^2.4.12","elliptic":"^6.6.1","form-data":"^3.0.4","cipher-base":"^1.0.6","serialize-javascript":"^7.0.5"},"repository":{"url":"git+https://github.com/sockudo/sockudo.git","type":"git","directory":"server-sdks/sockudo-http-node"},"_npmVersion":"11.17.0","description":"Node.js client to interact with the Sockudo REST API","directories":{},"_nodeVersion":"24.16.0","dependencies":{"is-base64":"^1.1.0","tweetnacl":"^1.0.3","node-fetch":"^2.7.0","tweetnacl-util":"^0.15.1","abort-controller":"^3.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"nock":"^14.0.15","mocha":"^11.3.0","oxfmt":"^0.55.0","sinon":"^22.0.0","oxlint":"^1.70.0","express":"^4.22.2","webpack":"^5.107.2","expect.js":"=0.3.1","typescript":"^5.9.3","@types/node":"^24.13.2","webpack-cli":"^7.0.3","@types/node-fetch":"^2.6.13","https-proxy-agent":"^9.1.0"},"_npmOperationalInternal":{"tmp":"tmp/sockudo_2.0.0_1782022594615_0.7440743774189789","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"sockudo","version":"2.1.0","keywords":["realtime","sockudo","websockets"],"author":{"name":"Sockudo"},"license":"MIT","_id":"sockudo@2.1.0","maintainers":[{"name":"sockudorealtime","email":"radu@sockudo.io"}],"contributors":[{"name":"fabrik42","email":"fabrik42@gmail.com"},{"name":"Jaewoong Kim","email":"jwoongkim@gmail.com"},{"name":"Phil Leggetter","email":"phil@leggetter.co.uk"},{"name":"Paweł Ledwoń","email":"me@ludvik.pl"}],"homepage":"https://github.com/sockudo/sockudo/tree/master/server-sdks/sockudo-http-node#readme","bugs":{"url":"https://github.com/sockudo/sockudo/issues"},"dist":{"shasum":"c4b9dbcf4d14030d4bf1d2cc19001884826a1549","tarball":"https://registry.npmjs.org/sockudo/-/sockudo-2.1.0.tgz","fileCount":17,"integrity":"sha512-nkl571w7b3fDN5H46xWWdHGKGMWNPjnQ9ccD5fWuOWUV9mgoSMD+C0X3l8ezOL1BxD8SN/fTuKfNQ7wswFo9BQ==","signatures":[{"sig":"MEQCIDePQbAhCVitHhNfVV39RDf2kgY1XiJeQPcc/7ep6ASPAiA51om3AeNvhwxTbH7JGoU6DWhcQL714Y4EcNmxHcw6Nw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/sockudo@2.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":71520},"main":"dist/sockudo.js","types":"index.d.ts","engines":{"node":">=16"},"gitHead":"1d80be3685abb0b3e878a3704595b2538748b53a","scripts":{"lint":"npx oxlint --config .oxlintrc.json src tests examples index.d.ts","test":"npx mocha 'tests/{integration,remote}/**/*.js'","build":"npm run clean && tsc -p tsconfig.json","clean":"rm -rf dist","format":"npx oxfmt src tests examples index.d.ts package.json tsconfig.json .oxlintrc.json","pretest":"npm run build","typecheck":"tsc --noEmit -p tsconfig.json","local-test":"npx mocha 'tests/integration/**/*.js'","format:check":"npx oxfmt --check src tests examples index.d.ts package.json tsconfig.json .oxlintrc.json","prelocal-test":"npm run build","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:1abe12fa-37c6-4b0a-b34d-73b2d88a59be"}},"overrides":{"qs":"^6.14.0","tar":"^6.2.1","cookie":"^0.7.1","lodash":"^4.17.23","pbkdf2":"^3.1.5","sha.js":"^2.4.12","elliptic":"^6.6.1","form-data":"^3.0.4","cipher-base":"^1.0.6","serialize-javascript":"^7.0.5"},"repository":{"url":"git+https://github.com/sockudo/sockudo.git","type":"git","directory":"server-sdks/sockudo-http-node"},"_npmVersion":"11.17.0","description":"Node.js client to interact with the Sockudo REST API","directories":{},"_nodeVersion":"24.17.0","dependencies":{"is-base64":"^1.1.0","tweetnacl":"^1.0.3","node-fetch":"^2.7.0","tweetnacl-util":"^0.15.1","abort-controller":"^3.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"nock":"^14.0.15","mocha":"^11.3.0","oxfmt":"^0.55.0","sinon":"^22.0.0","oxlint":"^1.70.0","express":"^4.22.2","webpack":"^5.107.2","expect.js":"=0.3.1","typescript":"^5.9.3","@types/node":"^24.13.2","webpack-cli":"^7.0.3","@types/node-fetch":"^2.6.13","https-proxy-agent":"^9.1.0"},"_npmOperationalInternal":{"tmp":"tmp/sockudo_2.1.0_1782635006896_0.37645911350926053","host":"s3://npm-registry-packages-npm-production"}},"2.2.0":{"name":"sockudo","version":"2.2.0","description":"Node.js client to interact with the Sockudo REST API","keywords":["realtime","sockudo","websockets"],"homepage":"https://github.com/sockudo/sockudo/tree/master/server-sdks/sockudo-http-node#readme","bugs":{"url":"https://github.com/sockudo/sockudo/issues"},"license":"MIT","author":{"name":"Sockudo"},"contributors":[{"name":"fabrik42","email":"fabrik42@gmail.com"},{"name":"Jaewoong Kim","email":"jwoongkim@gmail.com"},{"name":"Phil Leggetter","email":"phil@leggetter.co.uk"},{"name":"Paweł Ledwoń","email":"me@ludvik.pl"}],"repository":{"type":"git","url":"git+https://github.com/sockudo/sockudo.git","directory":"server-sdks/sockudo-http-node"},"main":"dist/sockudo.js","types":"index.d.ts","publishConfig":{"access":"public"},"scripts":{"clean":"rm -rf dist","build":"npm run clean && tsc -p tsconfig.json","prepublishOnly":"npm run build","local-test":"npx mocha 'tests/integration/**/*.js'","test":"npx mocha 'tests/{integration,remote}/**/*.js'","typecheck":"tsc --noEmit -p tsconfig.json","prelocal-test":"npm run build","pretest":"npm run build","lint":"npx oxlint --config .oxlintrc.json src tests examples index.d.ts","format":"npx oxfmt src tests examples index.d.ts package.json tsconfig.json .oxlintrc.json","format:check":"npx oxfmt --check src tests examples index.d.ts package.json tsconfig.json .oxlintrc.json"},"dependencies":{"abort-controller":"^3.0.0","is-base64":"^1.1.0","node-fetch":"^2.7.0","tweetnacl":"^1.0.3","tweetnacl-util":"^0.15.1"},"devDependencies":{"@types/node":"^24.13.2","@types/node-fetch":"^2.6.13","expect.js":"=0.3.1","express":"^4.22.2","https-proxy-agent":"^9.1.0","mocha":"^11.3.0","nock":"^14.0.15","oxfmt":"^0.55.0","oxlint":"^1.70.0","sinon":"^22.0.0","typescript":"^5.9.3","webpack":"^5.107.2","webpack-cli":"^7.0.3"},"overrides":{"body-parser":"^1.20.6","brace-expansion":"^2.1.4","cipher-base":"^1.0.6","cookie":"^0.7.1","elliptic":"^6.6.1","fast-uri":"^3.1.5","form-data":"^3.0.4","js-yaml":"^4.3.1","lodash":"^4.17.23","pbkdf2":"^3.1.5","qs":"^6.14.0","serialize-javascript":"^7.0.5","sha.js":"^2.4.12","tar":"^6.2.1"},"engines":{"node":">=16"},"gitHead":"c69e23af4738e162f0e1bb0089992e00621cf075","_id":"sockudo@2.2.0","_nodeVersion":"24.19.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-AoFNmXkhi1kKmkCKX4rSR3XmlBkPqbXQmLeC0i8GMkNTTdIwVJswiY5O4hZXg8zbKYtm5b3id3Q0PyP+kcNoRw==","shasum":"e5275f4ab0c4af7e7a29dee48125bb3e452242fe","tarball":"https://registry.npmjs.org/sockudo/-/sockudo-2.2.0.tgz","fileCount":17,"unpackedSize":72340,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/sockudo@2.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIG00DjiknNurSq0J5jZ2v0jtKkJi5dGZP9iIXdZ8RvkMAiEAiZK8ze4TEGGuzpjs+lpsRxnWLTc+4Fsk2KZimwK30DA="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:1abe12fa-37c6-4b0a-b34d-73b2d88a59be"}},"directories":{},"maintainers":[{"name":"sockudorealtime","email":"radu@sockudo.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sockudo_2.2.0_1787293850076_0.9722138288075541"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-20T08:14:36.547Z","modified":"2026-08-21T06:30:50.664Z","1.0.0":"2026-06-20T08:14:36.851Z","2.0.0":"2026-06-21T06:16:34.762Z","2.1.0":"2026-06-28T08:23:27.027Z","2.2.0":"2026-08-21T06:30:50.215Z"},"bugs":{"url":"https://github.com/sockudo/sockudo/issues"},"author":{"name":"Sockudo"},"license":"MIT","homepage":"https://github.com/sockudo/sockudo/tree/master/server-sdks/sockudo-http-node#readme","keywords":["realtime","sockudo","websockets"],"repository":{"type":"git","url":"git+https://github.com/sockudo/sockudo.git","directory":"server-sdks/sockudo-http-node"},"description":"Node.js client to interact with the Sockudo REST API","contributors":[{"name":"fabrik42","email":"fabrik42@gmail.com"},{"name":"Jaewoong Kim","email":"jwoongkim@gmail.com"},{"name":"Phil Leggetter","email":"phil@leggetter.co.uk"},{"name":"Paweł Ledwoń","email":"me@ludvik.pl"}],"maintainers":[{"name":"sockudorealtime","email":"radu@sockudo.io"}],"readme":"# sockudo\n\nOfficial Node.js server SDK for [Sockudo](https://github.com/sockudo/sockudo) — a fast, self-hosted WebSocket server with full Pusher HTTP API compatibility.\n\n## Supported Platforms\n\nThis SDK supports **Node.js 16+**.\n\n## Installation\n\nFor apps, install the published package:\n\n```bash\nnpm install sockudo\n# or: yarn add sockudo\n# or: bun add sockudo\n```\n\nFor contributors working inside this repository, build the SDK from its package directory:\n\n```bash\ncd server-sdks/sockudo-http-node\nnpm install\nnpm run build\n```\n\n## Importing\n\n```javascript\n// CommonJS\nconst { Sockudo } = require(\"sockudo\")\n\n// ESM / TypeScript\nimport { Sockudo } from \"sockudo\"\n```\n\nAll external APIs have TypeScript definitions included — no separate `@types` package required.\n\n## Configuration\n\n```javascript\nconst sockudo = new Sockudo({\n  appId: \"your-app-id\",\n  key: \"your-app-key\",\n  secret: \"your-app-secret\",\n  host: \"127.0.0.1\",    // self-hosted Sockudo instance\n  port: 6001,\n  useTLS: false,\n})\n```\n\n| Option    | Type    | Default      | Description                           |\n|-----------|---------|--------------|---------------------------------------|\n| `appId`   | string  | —            | Application ID                        |\n| `key`     | string  | —            | Application key                       |\n| `secret`  | string  | —            | Application secret                    |\n| `host`    | string  | `127.0.0.1` | Sockudo server host                   |\n| `port`    | number  | `6001`       | Sockudo server port                   |\n| `useTLS`  | boolean | `false`      | Use HTTPS/WSS                         |\n| `timeout` | number  | `30000`      | Request timeout in milliseconds       |\n| `proxy`   | string  | —            | HTTP proxy URL                        |\n\n### Instantiation from URL\n\n```javascript\nconst sockudo = Sockudo.forURL(\"http://key:secret@127.0.0.1:6001/apps/app-id\")\n```\n\n## Publishing Events\n\n### Single Channel\n\n```javascript\nawait sockudo.trigger(\"my-channel\", \"my-event\", { message: \"hello world\" })\n```\n\n### Multiple Channels\n\n```javascript\nawait sockudo.trigger(\n  [\"channel-1\", \"channel-2\", \"channel-3\"],\n  \"my-event\",\n  { message: \"hello world\" }\n)\n```\n\nYou can trigger on up to 100 channels in a single call.\n\n### Batch Events\n\nSend multiple events in a single HTTP request (up to 10 events per call):\n\n```javascript\nawait sockudo.triggerBatch([\n  { channel: \"channel-1\", name: \"event-1\", data: { x: 1 } },\n  { channel: \"channel-2\", name: \"event-2\", data: { x: 2 } },\n  { channel: \"channel-3\", name: \"event-3\", data: { x: 3 } },\n])\n```\n\n### Excluding a Socket Recipient\n\nPass `socket_id` to prevent the triggering connection from receiving its own event:\n\n```javascript\nawait sockudo.trigger(\"my-channel\", \"my-event\", { message: \"hello\" }, {\n  socket_id: \"123.456\",\n})\n```\n\n## Idempotent Publishing\n\nUse `idempotency_key` to safely retry publishes without causing duplicate deliveries. The server deduplicates events with the same key within the configured window.\n\n### Explicit key\n\n```javascript\nawait sockudo.trigger(\"my-channel\", \"my-event\", { message: \"hello\" }, {\n  idempotency_key: \"order-shipped-order-789\",\n})\n```\n\n### Auto-generated UUID\n\nPass `true` to have the SDK generate a UUID automatically:\n\n```javascript\nawait sockudo.trigger(\"my-channel\", \"my-event\", { message: \"hello\" }, {\n  idempotency_key: true,\n})\n```\n\n## Channel Authorization\n\n### Private Channel\n\n```javascript\nconst auth = sockudo.authorizeChannel(socketId, channelName)\n// Returns: { auth: \"key:signature\" }\n```\n\n### Presence Channel\n\nPass user data as the third argument to authorize a presence channel subscription:\n\n```javascript\nconst auth = sockudo.authorizeChannel(socketId, channelName, {\n  user_id: \"user-123\",\n  user_info: {\n    name: \"Jane Doe\",\n    email: \"jane@example.com\",\n  },\n})\n// Returns: { auth: \"key:signature\", channel_data: \"...\" }\n```\n\n## User Authentication\n\nAuthenticate a user connection for user-targeted events:\n\n```javascript\nconst auth = sockudo.authenticateUser(socketId, {\n  id: \"user-123\",\n  user_info: {\n    name: \"Jane Doe\",\n    role: \"admin\",\n  },\n})\n// Returns: { auth: \"key:signature\", user_data: \"...\" }\n```\n\n## Terminating User Connections\n\nDisconnect all active connections for a given user:\n\n```javascript\nawait sockudo.terminateUserConnections(\"user-123\")\n```\n\n### Force Reconnect User\n\nClose all active connections for a user with code `4200`, prompting clients to reconnect:\n\n```javascript\nawait sockudo.forceReconnectUser(\"user-123\");\n```\n\n## Webhooks\n\nVerify and parse incoming Sockudo webhooks:\n\n```javascript\nconst webhook = sockudo.webhook({\n  rawBody: req.rawBody,     // raw string body\n  headers: req.headers,\n})\n\nif (webhook.isValid()) {\n  const data = webhook.getData()\n  const events = webhook.getEvents()\n  const time = webhook.getTime()\n\n  for (const event of events) {\n    console.log(event.name, event.channel, event.data)\n  }\n}\n```\n\n`isValid()` also accepts additional tokens to check against (useful during key rotation):\n\n```javascript\nwebhook.isValid([\n  { key: \"old-key\", secret: \"old-secret\" },\n])\n```\n\n## Application State\n\n```javascript\n// List all channels\nconst response = await sockudo.get({ path: \"/channels\", params: {} })\nconst body = await response.json()\n\n// Get a specific channel\nconst response = await sockudo.get({ path: \"/channels/my-channel\", params: {} })\n\n// List users in a presence channel\nconst response = await sockudo.get({ path: \"/channels/presence-room/users\" })\n```\n\n## Express Integration\n\n### Channel Authorization Endpoint\n\n```javascript\nimport express from \"express\"\nimport { Sockudo } from \"sockudo\"\n\nconst app = express()\nconst sockudo = new Sockudo({\n  appId: process.env.SOCKUDO_APP_ID,\n  key: process.env.SOCKUDO_KEY,\n  secret: process.env.SOCKUDO_SECRET,\n  host: \"127.0.0.1\",\n  port: 6001,\n})\n\napp.post(\"/sockudo/auth\", express.urlencoded({ extended: false }), (req, res) => {\n  const { socket_id, channel_name } = req.body\n\n  if (!isUserAuthorized(req, channel_name)) {\n    return res.status(403).json({ error: \"Forbidden\" })\n  }\n\n  let presenceData\n  if (channel_name.startsWith(\"presence-\")) {\n    presenceData = {\n      user_id: req.user.id,\n      user_info: { name: req.user.name },\n    }\n  }\n\n  const auth = sockudo.authorizeChannel(socket_id, channel_name, presenceData)\n  res.json(auth)\n})\n```\n\n### Webhook Endpoint\n\n```javascript\napp.post(\n  \"/sockudo/webhook\",\n  express.raw({ type: \"application/json\" }),\n  (req, res) => {\n    const webhook = sockudo.webhook({\n      rawBody: req.body.toString(),\n      headers: req.headers,\n    })\n\n    if (!webhook.isValid()) {\n      return res.status(401).send(\"Invalid signature\")\n    }\n\n    for (const event of webhook.getEvents()) {\n      console.log(\"Webhook event:\", event)\n    }\n\n    res.sendStatus(200)\n  }\n)\n```\n\n## Fastify Integration\n\n```javascript\nimport Fastify from \"fastify\"\nimport { Sockudo } from \"sockudo\"\n\nconst app = Fastify()\nconst sockudo = new Sockudo({\n  appId: process.env.SOCKUDO_APP_ID,\n  key: process.env.SOCKUDO_KEY,\n  secret: process.env.SOCKUDO_SECRET,\n})\n\napp.addContentTypeParser(\n  \"application/x-www-form-urlencoded\",\n  { parseAs: \"string\" },\n  (req, body, done) => {\n    done(null, Object.fromEntries(new URLSearchParams(body)))\n  }\n)\n\napp.post(\"/sockudo/auth\", (req, reply) => {\n  const { socket_id, channel_name } = req.body\n  const auth = sockudo.authorizeChannel(socket_id, channel_name)\n  reply.send(auth)\n})\n```\n\n## TypeScript\n\n```typescript\nimport { Sockudo, TriggerOptions, BatchEvent } from \"sockudo\"\n\nconst sockudo = new Sockudo({\n  appId: process.env.SOCKUDO_APP_ID!,\n  key: process.env.SOCKUDO_KEY!,\n  secret: process.env.SOCKUDO_SECRET!,\n})\n\nconst options: TriggerOptions = {\n  socket_id: \"123.456\",\n  idempotency_key: \"my-idempotency-key\",\n}\n\nawait sockudo.trigger(\"my-channel\", \"my-event\", { data: true }, options)\n\nconst batch: BatchEvent[] = [\n  { channel: \"ch-1\", name: \"event-a\", data: { value: 1 } },\n  { channel: \"ch-2\", name: \"event-b\", data: { value: 2 } },\n]\n\nawait sockudo.triggerBatch(batch)\n```\n\n## Testing\n\n```bash\nnpm install\nnpm test\n```\n\n## Channel History\n\n```javascript\nconst page = await sockudo.channelHistory(\"my-channel\", {\n  limit: 50,\n  direction: \"newest_first\",\n})\n\nconst nextPage = await sockudo.channelHistory(\"my-channel\", {\n  cursor: \"opaque-cursor-from-previous-page\",\n})\n```\n\n## Pusher SDK Compatibility\n\nSockudo implements the full Pusher HTTP API. If you prefer to use the official `pusher` npm package or are migrating from Pusher, point it at your Sockudo instance without any other changes:\n\n```javascript\nconst Pusher = require(\"pusher\")\n\nconst client = new Pusher({\n  appId: \"app-id\",\n  key: \"app-key\",\n  secret: \"app-secret\",\n  host: \"127.0.0.1\",\n  port: \"6001\",\n  useTLS: false,\n})\n```\n\nAll standard Pusher SDK calls work against a self-hosted Sockudo server without modification.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}