better-sse
Advanced tools
Comparing version 0.6.0 to 0.7.0
import { TypedEmitter, EventMap } from "./lib/TypedEmitter"; | ||
import Session from "./Session"; | ||
import { Session } from "./Session"; | ||
interface BroadcastOptions { | ||
@@ -7,11 +7,11 @@ /** | ||
* | ||
* Called with each session and should return a truthy value to allow the event to be sent, otherwise return a falsy value to prevent the session from receiving the event. | ||
* Called with each session and should return `true` to allow the event to be sent and otherwise return `false` to prevent the session from receiving the event. | ||
*/ | ||
filter?: (session: Session) => unknown; | ||
filter?: (session: Session) => boolean; | ||
} | ||
interface Events extends EventMap { | ||
interface ChannelEvents extends EventMap { | ||
"session-registered": (session: Session) => void; | ||
"session-deregistered": (session: Session) => void; | ||
"session-disconnected": (session: Session) => void; | ||
broadcast: (eventName: string, data: unknown) => void; | ||
broadcast: (data: unknown, eventName: string) => void; | ||
} | ||
@@ -23,3 +23,8 @@ /** | ||
*/ | ||
declare class Channel extends TypedEmitter<Events> { | ||
declare class Channel<State extends Record<string, unknown> = Record<string, unknown>> extends TypedEmitter<ChannelEvents> { | ||
/** | ||
* Custom state for this channel. | ||
* Use this object to safely store information related to the channel. | ||
*/ | ||
state: State; | ||
private sessions; | ||
@@ -52,5 +57,5 @@ constructor(); | ||
*/ | ||
broadcast(eventName: string, data: unknown, options?: BroadcastOptions): this; | ||
broadcast: (data: unknown, eventName?: string | undefined, options?: BroadcastOptions) => this; | ||
} | ||
export type { BroadcastOptions }; | ||
export default Channel; | ||
export type { BroadcastOptions, ChannelEvents }; | ||
export { Channel }; |
@@ -1,3 +0,3 @@ | ||
import Channel from "./Channel"; | ||
declare const createChannel: () => Channel; | ||
export default createChannel; | ||
import { Channel } from "./Channel"; | ||
declare const createChannel: <State extends Record<string, unknown>>() => Channel<State>; | ||
export { createChannel }; |
/// <reference types="node" /> | ||
import Session from "./Session"; | ||
import { Session, SessionState } from "./Session"; | ||
/** | ||
* Create a new session and return the session instance once it has connected. | ||
*/ | ||
declare const createSession: <State extends Record<string, unknown> = Record<string, unknown>>(req: import("http").IncomingMessage, res: import("http").ServerResponse, options?: import("./Session").SessionOptions | undefined) => Promise<Session<State>>; | ||
export default createSession; | ||
declare const createSession: <State extends Record<string, unknown> = SessionState>(req: import("http").IncomingMessage, res: import("http").ServerResponse, options?: import("./Session").SessionOptions | undefined) => Promise<Session<State>>; | ||
export { createSession }; |
@@ -1,4 +0,4 @@ | ||
export { default as Session } from "./Session"; | ||
export { default as createSession } from "./createSession"; | ||
export { default as Channel } from "./Channel"; | ||
export { default as createChannel } from "./createChannel"; | ||
export * from "./Session"; | ||
export * from "./createSession"; | ||
export * from "./Channel"; | ||
export * from "./createChannel"; |
@@ -1,2 +0,2 @@ | ||
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var s=t();for(var i in s)("object"==typeof exports?exports:e)[i]=s[i]}}(global,(function(){return(()=>{"use strict";var e={n:t=>{var s=t&&t.__esModule?()=>t.default:()=>t;return e.d(s,{a:s}),s},d:(t,s)=>{for(var i in s)e.o(s,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:s[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{Channel:()=>d,Session:()=>a,createChannel:()=>c,createSession:()=>l});const s=require("crypto"),i=require("events");var r=e.n(i);class n extends(r()){addListener(e,t){return super.addListener(e,t)}prependListener(e,t){return super.prependListener(e,t)}prependOnceListener(e,t){return super.prependOnceListener(e,t)}on(e,t){return super.on(e,t)}once(e,t){return super.once(e,t)}emit(e,...t){return super.emit(e,...t)}off(e,t){return super.off(e,t)}removeListener(e,t){return super.removeListener(e,t)}}const o=e=>JSON.stringify(e),h=e=>{let t=e;return t=t.replace(/(\r\n|\r|\n)/g,"\n"),t=t.replace(/\n+$/g,""),t},a=class extends n{constructor(e,t,i={}){var r,n,a,l,d,c,u;super(),this.lastId="",this.isConnected=!1,this.state={},this.onConnected=()=>{var e,t,s;const i=`http://${this.req.headers.host}${this.req.url}`,r=new URL(i).searchParams;if(this.trustClientEventId){const i=null!==(s=null!==(t=null!==(e=this.req.headers["last-event-id"])&&void 0!==e?e:r.get("lastEventId"))&&void 0!==t?t:r.get("evs_last_event_id"))&&void 0!==s?s:"";this.lastId=i}Object.entries(this.headers).forEach((([e,t])=>{this.res.setHeader(e,null!=t?t:"")})),this.res.statusCode=this.statusCode,this.res.setHeader("Content-Type","text/event-stream"),this.res.setHeader("Cache-Control","no-cache, no-transform"),this.res.setHeader("Connection","keep-alive"),this.res.flushHeaders(),r.has("padding")&&this.comment(" ".repeat(2049)).dispatch(),r.has("evs_preamble")&&this.comment(" ".repeat(2056)).dispatch(),null!==this.initialRetry&&this.retry(this.initialRetry).dispatch(),null!==this.keepAliveInterval&&(this.keepAliveTimer=setInterval(this.keepAlive,this.keepAliveInterval)),this.isConnected=!0,this.emit("connected")},this.onDisconnected=()=>{this.keepAliveTimer&&clearInterval(this.keepAliveTimer),this.isConnected=!1,this.emit("disconnected")},this.writeField=(e,t)=>{const s=`${e}:${this.sanitize(t)}\n`;return this.res.write(s),this},this.keepAlive=()=>{this.comment().dispatch()},this.dispatch=()=>(this.res.write("\n"),this),this.data=e=>{const t=this.serialize(e);return this.writeField("data",t),this},this.id=e=>{const t=e||"";return this.writeField("id",t),this.lastId=t,this},this.retry=e=>{const t=e.toString();return this.writeField("retry",t),this},this.comment=e=>(this.writeField("",null!=e?e:""),this),this.push=(e,t)=>{let i,r;e&&void 0===t?(i="message",r=e):(i=e.toString(),r=t);const n=(0,s.randomBytes)(4).toString("hex");return this.event(i).id(n).data(r).dispatch(),this},this.stream=async(e,t={})=>{const{eventName:s="stream"}=t;return new Promise(((t,i)=>{e.on("data",(e=>{let t;t=Buffer.isBuffer(e)?e.toString():e,this.push(s,t)})),e.once("end",(()=>t(!0))),e.once("close",(()=>t(!0))),e.once("error",(e=>i(e)))}))},this.iterate=async(e,t={})=>{const{eventName:s="iteration"}=t;for await(const t of e)this.push(s,t)},this.req=e,this.res=t,this.serialize=null!==(r=i.serializer)&&void 0!==r?r:o,this.sanitize=null!==(n=i.sanitizer)&&void 0!==n?n:h,this.trustClientEventId=null===(a=i.trustClientEventId)||void 0===a||a,this.initialRetry=null===i.retry?null:null!==(l=i.retry)&&void 0!==l?l:2e3,this.keepAliveInterval=null===i.keepAlive?null:null!==(d=i.keepAlive)&&void 0!==d?d:1e4,this.statusCode=null!==(c=i.statusCode)&&void 0!==c?c:200,this.headers=null!==(u=i.headers)&&void 0!==u?u:{},this.req.on("close",this.onDisconnected),setImmediate(this.onConnected)}event(e){return this.writeField("event",e),this}},l=(...e)=>new Promise((t=>{const s=new a(...e);s.once("connected",(()=>{t(s)}))})),d=class extends n{constructor(){super(),this.sessions=[]}get activeSessions(){return this.sessions}get sessionCount(){return this.sessions.length}register(e){if(!e.isConnected)throw new Error("Cannot register a non-active session.");return e.once("disconnected",(()=>{this.deregister(e),this.emit("session-disconnected",e)})),this.sessions.push(e),this.emit("session-registered",e),this}deregister(e){return this.sessions=this.sessions.filter((t=>t!==e)),this.emit("session-deregistered",e),this}broadcast(e,t,s={}){for(const i of this.sessions)s.filter&&!s.filter(i)||i.push(e,t);return this.emit("broadcast",e,t),this}},c=(...e)=>new d(...e);return t})()})); | ||
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var s=t();for(var i in s)("object"==typeof exports?exports:e)[i]=s[i]}}(global,(function(){return(()=>{"use strict";var e={n:t=>{var s=t&&t.__esModule?()=>t.default:()=>t;return e.d(s,{a:s}),s},d:(t,s)=>{for(var i in s)e.o(s,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:s[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{Channel:()=>d,Session:()=>a,createChannel:()=>c,createSession:()=>l});const s=require("crypto"),i=require("events");var n=e.n(i);class r extends(n()){addListener(e,t){return super.addListener(e,t)}prependListener(e,t){return super.prependListener(e,t)}prependOnceListener(e,t){return super.prependOnceListener(e,t)}on(e,t){return super.on(e,t)}once(e,t){return super.once(e,t)}emit(e,...t){return super.emit(e,...t)}off(e,t){return super.off(e,t)}removeListener(e,t){return super.removeListener(e,t)}}const o=e=>JSON.stringify(e),h=e=>{let t=e;return t=t.replace(/(\r\n|\r|\n)/g,"\n"),t=t.replace(/\n+$/g,""),t};class a extends r{constructor(e,t,i={}){var n,r,a,l,d,c,u;super(),this.lastId="",this.isConnected=!1,this.state={},this.onConnected=()=>{var e,t,s;const i=`http://${this.req.headers.host}${this.req.url}`,n=new URL(i).searchParams;if(this.trustClientEventId){const i=null!==(s=null!==(t=null!==(e=this.req.headers["last-event-id"])&&void 0!==e?e:n.get("lastEventId"))&&void 0!==t?t:n.get("evs_last_event_id"))&&void 0!==s?s:"";this.lastId=i}Object.entries(this.headers).forEach((([e,t])=>{this.res.setHeader(e,null!=t?t:"")})),this.res.statusCode=this.statusCode,this.res.setHeader("Content-Type","text/event-stream"),this.res.setHeader("Cache-Control","no-cache, no-transform"),this.res.setHeader("Connection","keep-alive"),this.res.flushHeaders(),n.has("padding")&&this.comment(" ".repeat(2049)).dispatch(),n.has("evs_preamble")&&this.comment(" ".repeat(2056)).dispatch(),null!==this.initialRetry&&this.retry(this.initialRetry).dispatch(),null!==this.keepAliveInterval&&(this.keepAliveTimer=setInterval(this.keepAlive,this.keepAliveInterval)),this.isConnected=!0,this.emit("connected")},this.onDisconnected=()=>{this.keepAliveTimer&&clearInterval(this.keepAliveTimer),this.isConnected=!1,this.emit("disconnected")},this.writeField=(e,t)=>{const s=`${e}:${this.sanitize(t)}\n`;return this.res.write(s),this},this.keepAlive=()=>{this.comment().dispatch()},this.dispatch=()=>(this.res.write("\n"),this),this.data=e=>{const t=this.serialize(e);return this.writeField("data",t),this},this.id=e=>{const t=e||"";return this.writeField("id",t),this.lastId=t,this},this.retry=e=>{const t=e.toString();return this.writeField("retry",t),this},this.comment=e=>(this.writeField("",null!=e?e:""),this),this.push=(e,t,i)=>(t||(t="message"),i||(i=(0,s.randomBytes)(4).toString("hex")),this.event(t).id(i).data(e).dispatch(),this.emit("push",e,t,i),this),this.stream=async(e,t={})=>{const{eventName:s="stream"}=t;return new Promise(((t,i)=>{e.on("data",(e=>{let t;t=Buffer.isBuffer(e)?e.toString():e,this.push(t,s)})),e.once("end",(()=>t(!0))),e.once("close",(()=>t(!0))),e.once("error",(e=>i(e)))}))},this.iterate=async(e,t={})=>{const{eventName:s="iteration"}=t;for await(const t of e)this.push(t,s)},this.req=e,this.res=t,this.serialize=null!==(n=i.serializer)&&void 0!==n?n:o,this.sanitize=null!==(r=i.sanitizer)&&void 0!==r?r:h,this.trustClientEventId=null===(a=i.trustClientEventId)||void 0===a||a,this.initialRetry=null===i.retry?null:null!==(l=i.retry)&&void 0!==l?l:2e3,this.keepAliveInterval=null===i.keepAlive?null:null!==(d=i.keepAlive)&&void 0!==d?d:1e4,this.statusCode=null!==(c=i.statusCode)&&void 0!==c?c:200,this.headers=null!==(u=i.headers)&&void 0!==u?u:{},this.req.on("close",this.onDisconnected),setImmediate(this.onConnected)}event(e){return this.writeField("event",e),this}}const l=(...e)=>new Promise((t=>{const s=new a(...e);s.once("connected",(()=>{t(s)}))}));class d extends r{constructor(){super(),this.state={},this.sessions=[],this.broadcast=(e,t,s={})=>{t||(t="message");const i=s.filter?this.sessions.filter(s.filter):this.sessions;for(const s of i)s.push(e,t);return this.emit("broadcast",e,t),this}}get activeSessions(){return this.sessions}get sessionCount(){return this.sessions.length}register(e){if(!e.isConnected)throw new Error("Cannot register a non-active session.");return e.once("disconnected",(()=>{this.deregister(e),this.emit("session-disconnected",e)})),this.sessions.push(e),this.emit("session-registered",e),this}deregister(e){return this.sessions=this.sessions.filter((t=>t!==e)),this.emit("session-deregistered",e),this}}const c=(...e)=>new d(...e);return t})()})); | ||
//# sourceMappingURL=index.js.map |
@@ -1,5 +0,6 @@ | ||
export interface SanitizerFunction { | ||
interface SanitizerFunction { | ||
(text: string): string; | ||
} | ||
declare const sanitize: SanitizerFunction; | ||
export default sanitize; | ||
export type { SanitizerFunction }; | ||
export { sanitize }; |
/** | ||
* Serialize arbitrary data to a string that can be sent over the wire to the client. | ||
*/ | ||
export interface SerializerFunction { | ||
interface SerializerFunction { | ||
(data: unknown): string; | ||
} | ||
declare const serialize: SerializerFunction; | ||
export default serialize; | ||
export type { SerializerFunction }; | ||
export { serialize }; |
@@ -83,5 +83,9 @@ /// <reference types="node" /> | ||
} | ||
interface Events extends EventMap { | ||
interface SessionState { | ||
[key: string]: unknown; | ||
} | ||
interface SessionEvents extends EventMap { | ||
connected: () => void; | ||
disconnected: () => void; | ||
push: (data: unknown, eventName: string, eventId: string) => void; | ||
} | ||
@@ -100,3 +104,3 @@ /** | ||
*/ | ||
declare class Session<State extends Record<string, unknown> = Record<string, unknown>> extends TypedEmitter<Events> { | ||
declare class Session<State extends Record<string, unknown> = SessionState> extends TypedEmitter<SessionEvents> { | ||
/** | ||
@@ -184,6 +188,6 @@ * The last ID sent to the client. | ||
* | ||
* @param eventOrData - Event name or data to write. | ||
* @param data - Data to write if `eventOrData` was an event name. | ||
* @param data - Data to write. | ||
* @param eventName - Event name to write. | ||
*/ | ||
push: (eventOrData: string | unknown, data?: unknown) => this; | ||
push: (data: unknown, eventName?: string | undefined, eventId?: string | undefined) => this; | ||
/** | ||
@@ -217,3 +221,3 @@ * Pipe readable stream data to the client. | ||
} | ||
export type { SessionOptions }; | ||
export default Session; | ||
export type { SessionOptions, StreamOptions, IterateOptions, SessionState, SessionEvents, }; | ||
export { Session }; |
{ | ||
"name": "better-sse", | ||
"description": "Dead simple, dependency-less, spec-compliant server-side events implementation for Node, written in TypeScript.", | ||
"version": "0.6.0", | ||
"version": "0.7.0", | ||
"main": "./build/index.js", | ||
@@ -29,21 +29,21 @@ "types": "./build/index.d.ts", | ||
"devDependencies": { | ||
"@types/eventsource": "^1.1.5", | ||
"@types/express": "^4.17.9", | ||
"@types/jest": "^26.0.19", | ||
"@types/node": "^14.14.20", | ||
"@typescript-eslint/eslint-plugin": "^4.11.0", | ||
"@typescript-eslint/parser": "^4.11.0", | ||
"eslint": "^7.16.0", | ||
"eslint-plugin-tsdoc": "^0.2.10", | ||
"@types/eventsource": "^1.1.8", | ||
"@types/express": "^4.17.13", | ||
"@types/jest": "^26.0.14", | ||
"@types/node": "^17.0.8", | ||
"@typescript-eslint/eslint-plugin": "^5.9.0", | ||
"@typescript-eslint/parser": "^5.9.0", | ||
"eslint": "^8.6.0", | ||
"eslint-plugin-tsdoc": "^0.2.14", | ||
"eventsource": "^1.1.0", | ||
"jest": "^26.6.3", | ||
"npm-run-all": "^4.1.5", | ||
"prettier": "^2.2.1", | ||
"prettier": "^2.5.1", | ||
"rimraf": "^3.0.2", | ||
"ts-jest": "^26.4.4", | ||
"ts-loader": "^9.2.3", | ||
"ts-node": "^10.0.0", | ||
"typescript": "^4.4.4", | ||
"webpack": "^5.38.1", | ||
"webpack-cli": "^4.7.0" | ||
"ts-jest": "^26.5.6", | ||
"ts-loader": "^9.2.6", | ||
"ts-node": "^10.4.0", | ||
"typescript": "^4.5.4", | ||
"webpack": "^5.65.0", | ||
"webpack-cli": "^4.9.1" | ||
}, | ||
@@ -58,3 +58,4 @@ "scripts": { | ||
"lint": "eslint \"./src/**/*.ts\"" | ||
} | ||
}, | ||
"readme": "# Better SSE\n\n<p>\n\t<img src=\"https://img.shields.io/npm/v/better-sse?color=blue&style=flat-square\" />\n\t<img src=\"https://img.shields.io/npm/l/better-sse?color=green&style=flat-square\" />\n\t<img src=\"https://img.shields.io/npm/dt/better-sse?color=grey&style=flat-square\" />\n\t<a href=\"https://github.com/MatthewWid/better-sse\"><img src=\"https://img.shields.io/github/stars/MatthewWid/better-sse?style=social\" /></a>\n</p>\n\nA dead simple, dependency-less, spec-compliant server-side events implementation for Node, written in TypeScript.\n\nThis package aims to be the easiest to use, most compliant and most streamlined solution to server-side events with Node that is framework agnostic and feature rich.\n\nPlease consider starring the project [on GitHub ⭐](https://github.com/MatthewWid/better-sse).\n\n## Why use Server-sent Events?\n\nServer-sent events (SSE) is a standardised protocol that allows web-servers to push data to clients without the need for alternative mechanisms such as pinging or long-polling.\n\nUsing SSE can allow for significant savings in bandwidth and battery life on portable devices, and will work with your existing infrastructure as it operates directly over the HTTP protocol without the need for the connection upgrade that [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) require.\n\nCompared to WebSockets it has comparable performance and bandwidth usage, especially over HTTP/2, and natively includes event ID generation and automatic reconnection when clients are disconnected.\n\n* [Comparison: Server-sent Events vs WebSockets vs Polling](https://medium.com/dailyjs/a-comparison-between-websockets-server-sent-events-and-polling-7a27c98cb1e3)\n* [WHATWG standards section for server-sent events](https://html.spec.whatwg.org/multipage/server-sent-events.html)\n* [MDN guide to server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)\n\n## Highlights\n\n* Compatible with all popular Node HTTP frameworks ([http](https://nodejs.org/api/http.html), [Express](https://nodejs.org/api/http.html), [Koa](https://www.npmjs.com/package/koa), [Fastify](https://www.npmjs.com/package/fastify), etc.)\n* Fully written in TypeScript (+ ships with types directly).\n* [Thoroughly tested](./src/Session.test.ts) (+ 100% code coverage!).\n* [Comprehensively documented](./docs) with guides and API documentation.\n* [Channels](./docs/channels.md) allow you to broadcast events to many clients at once.\n* Configurable reconnection time, message serialization and data sanitization (but with good defaults).\n* Trust or ignore the client-given last event ID.\n* Automatically send keep-alive pings to keep connections open.\n* Add or override the response status code and headers.\n* Fine-grained control by either sending [individual fields](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#fields) of events or sending full events with simple helpers.\n* Pipe [streams](https://nodejs.org/api/stream.html#stream_readable_streams) and [iterables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) directly from the server to the client as a stream of events.\n* Support for popular EventStream polyfills [`event-source-polyfill`](https://www.npmjs.com/package/event-source-polyfill) and [`eventsource-polyfill`](https://www.npmjs.com/package/eventsource-polyfill).\n\n[See a comparison with other Node SSE libraries in the documentation.](./docs/comparison.md)\n\n# Installation\n\n```bash\n# npm\nnpm install better-sse\n\n# Yarn\nyarn add better-sse\n\n# pnpm\npnpm add better-sse\n```\n\n_Better SSE ships with types built in. No need to install from `@types` for TypeScript users!_\n\n# Usage\n\nThe following example shows usage with [Express](http://expressjs.com/), but Better SSE works with any web-server framework (that uses the underlying Node [HTTP module](https://nodejs.org/api/http.html)).\n\nSee the [Recipes](./docs/recipes.md) section of the documentation for use with other frameworks and libraries.\n\n---\n\nUse [sessions](./docs/api.md#session) to push events to clients:\n\n```typescript\n// Server\nimport {createSession} from \"better-sse\";\n\napp.get(\"/sse\", async (req, res) => {\n\tconst session = await createSession(req, res);\n\n\tsession.push(\"Hello world!\");\n});\n```\n\n```typescript\n// Client\nconst sse = new EventSource(\"/sse\");\n\nsse.addEventListener(\"message\", ({data}) => {\n\tconsole.log(data);\n});\n```\n\n---\n\nUse [channels](./docs/channels.md) to send events to many clients at once:\n\n```typescript\nimport {createSession, createChannel} from \"better-sse\";\n\nconst channel = createChannel();\n\napp.get(\"/sse\", async (req, res) => {\n\tconst session = await createSession(req, res);\n\n\tchannel.register(session);\n\n\tchannel.broadcast(\"A user has joined.\", \"join-notification\");\n});\n```\n\n---\n\nLoop over sync and async [iterables](./docs/api.md#sessioniterate-iterable-iterable--asynciterable-options-object--promisevoid) and send each value as an event:\n\n```typescript\nconst session = await createSession(req, res);\n\nconst list = [1, 2, 3];\n\nawait session.iterate(list);\n```\n\n---\n\nPipe [readable stream](#sessionstream-stream-readable-options-object--promiseboolean) data to the client as a stream of events:\n\n```typescript\nconst session = await createSession(req, res);\n\nconst stream = Readable.from([1, 2, 3]);\n\nawait session.stream(stream);\n```\n\n---\n\nCheck the [API documentation](./docs/api.md) and [live examples](https://github.com/MatthewWid/better-sse/tree/master/examples) for information on getting more fine-tuned control over your data such as managing event IDs, data serialization, event filtering, dispatch controls and more!\n\n# Documentation\n\nAPI documentation, getting started guides and usage with other frameworks is [available on GitHub](https://github.com/MatthewWid/better-sse/tree/master/docs).\n\n# Contributing\n\nThis library is always open to contributions, whether it be code, bug reports, documentation or anything else.\n\nPlease submit suggestions, bugs and issues to the [GitHub issues page](https://github.com/MatthewWid/better-sse/issues).\n\nFor code or documentation changes, [submit a pull request on GitHub](https://github.com/MatthewWid/better-sse/pulls).\n\n## Local Development\n\nInstall Node:\n\n```bash\ncurl -L https://git.io/n-install | bash\nn auto\n```\n\nInstall pnpm:\n\n```bash\nnpm i -g pnpm\n```\n\nInstall dependencies:\n\n```bash\npnpm i\n```\n\nRun tests:\n\n```bash\npnpm t\n```\n\n# License\n\nThis project is licensed under the MIT license.\n" | ||
} |
@@ -60,3 +60,3 @@ # Better SSE | ||
# Basic Usage | ||
# Usage | ||
@@ -67,3 +67,7 @@ The following example shows usage with [Express](http://expressjs.com/), but Better SSE works with any web-server framework (that uses the underlying Node [HTTP module](https://nodejs.org/api/http.html)). | ||
```javascript | ||
--- | ||
Use [sessions](./docs/api.md#session) to push events to clients: | ||
```typescript | ||
// Server | ||
@@ -79,3 +83,3 @@ import {createSession} from "better-sse"; | ||
```javascript | ||
```typescript | ||
// Client | ||
@@ -89,4 +93,48 @@ const sse = new EventSource("/sse"); | ||
Check [the API documentation](./docs/api.md) and [live examples](https://github.com/MatthewWid/better-sse/tree/master/examples) for information on getting more fine-tuned control over your data such as managing event IDs, data serialization, streams, dispatch controls and more! | ||
--- | ||
Use [channels](./docs/channels.md) to send events to many clients at once: | ||
```typescript | ||
import {createSession, createChannel} from "better-sse"; | ||
const channel = createChannel(); | ||
app.get("/sse", async (req, res) => { | ||
const session = await createSession(req, res); | ||
channel.register(session); | ||
channel.broadcast("A user has joined.", "join-notification"); | ||
}); | ||
``` | ||
--- | ||
Loop over sync and async [iterables](./docs/api.md#sessioniterate-iterable-iterable--asynciterable-options-object--promisevoid) and send each value as an event: | ||
```typescript | ||
const session = await createSession(req, res); | ||
const list = [1, 2, 3]; | ||
await session.iterate(list); | ||
``` | ||
--- | ||
Pipe [readable stream](#sessionstream-stream-readable-options-object--promiseboolean) data to the client as a stream of events: | ||
```typescript | ||
const session = await createSession(req, res); | ||
const stream = Readable.from([1, 2, 3]); | ||
await session.stream(stream); | ||
``` | ||
--- | ||
Check the [API documentation](./docs/api.md) and [live examples](https://github.com/MatthewWid/better-sse/tree/master/examples) for information on getting more fine-tuned control over your data such as managing event IDs, data serialization, event filtering, dispatch controls and more! | ||
# Documentation | ||
@@ -93,0 +141,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34344
19
362
178