better-sse 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,14 @@
1
1
  import { Session, DefaultSessionState } from "./Session";
2
2
  import { TypedEmitter, EventMap } from "./lib/TypedEmitter";
3
- interface BroadcastOptions<SessionState extends Record<string, unknown> = DefaultSessionState> {
3
+ interface ChannelOptions<State = DefaultChannelState> {
4
+ /**
5
+ * Custom state for this channel.
6
+ *
7
+ * Use this object to safely store information related to the channel.
8
+ */
9
+ state?: State;
10
+ }
11
+ interface BroadcastOptions<SessionState = DefaultSessionState> {
4
12
  /**
5
13
  * Unique ID for the event being broadcast.
6
14
  *
@@ -14,7 +22,7 @@ interface BroadcastOptions<SessionState extends Record<string, unknown> = Defaul
14
22
  */
15
23
  filter?: (session: Session<SessionState>) => boolean;
16
24
  }
17
- interface ChannelEvents<SessionState extends Record<string, unknown> = DefaultSessionState> extends EventMap {
25
+ interface ChannelEvents<SessionState = DefaultSessionState> extends EventMap {
18
26
  "session-registered": (session: Session<SessionState>) => void;
19
27
  "session-deregistered": (session: Session<SessionState>) => void;
20
28
  "session-disconnected": (session: Session<SessionState>) => void;
@@ -30,7 +38,7 @@ interface DefaultChannelState {
30
38
  *
31
39
  * You may use the second generic argument `SessionState` to enforce that only sessions with the same state type may be registered with this channel.
32
40
  */
33
- declare class Channel<State extends Record<string, unknown> = DefaultChannelState, SessionState extends Record<string, unknown> = DefaultSessionState> extends TypedEmitter<ChannelEvents<SessionState>> {
41
+ declare class Channel<State = DefaultChannelState, SessionState = DefaultSessionState> extends TypedEmitter<ChannelEvents<SessionState>> {
34
42
  /**
35
43
  * Custom state for this channel.
36
44
  *
@@ -38,7 +46,7 @@ declare class Channel<State extends Record<string, unknown> = DefaultChannelStat
38
46
  */
39
47
  state: State;
40
48
  private sessions;
41
- constructor();
49
+ constructor(options?: ChannelOptions<State>);
42
50
  /**
43
51
  * List of the currently active sessions subscribed to this channel.
44
52
  */
@@ -77,5 +85,5 @@ declare class Channel<State extends Record<string, unknown> = DefaultChannelStat
77
85
  */
78
86
  broadcast: (data: unknown, eventName?: string, options?: BroadcastOptions<SessionState>) => this;
79
87
  }
80
- export type { BroadcastOptions, ChannelEvents, DefaultChannelState };
88
+ export type { ChannelOptions, BroadcastOptions, ChannelEvents, DefaultChannelState, };
81
89
  export { Channel };
@@ -4,7 +4,7 @@ import { IncomingMessage as Http1ServerRequest, ServerResponse as Http1ServerRes
4
4
  import { Http2ServerRequest, Http2ServerResponse } from "http2";
5
5
  import { EventBuffer, EventBufferOptions } from "./EventBuffer";
6
6
  import { TypedEmitter, EventMap } from "./lib/TypedEmitter";
7
- interface SessionOptions extends Pick<EventBufferOptions, "serializer" | "sanitizer"> {
7
+ interface SessionOptions<State = DefaultSessionState> extends Pick<EventBufferOptions, "serializer" | "sanitizer"> {
8
8
  /**
9
9
  * Whether to trust or ignore the last event ID given by the client in the `Last-Event-ID` request header.
10
10
  *
@@ -50,6 +50,12 @@ interface SessionOptions extends Pick<EventBufferOptions, "serializer" | "saniti
50
50
  * Additional headers to be sent along with the response.
51
51
  */
52
52
  headers?: OutgoingHttpHeaders;
53
+ /**
54
+ * Custom state for this session.
55
+ *
56
+ * Use this object to safely store information related to the session and user.
57
+ */
58
+ state?: State;
53
59
  }
54
60
  interface DefaultSessionState {
55
61
  [key: string]: unknown;
@@ -74,7 +80,7 @@ interface SessionEvents extends EventMap {
74
80
  * @param res - The Node HTTP {@link https://nodejs.org/api/http.html#http_class_http_serverresponse | IncomingMessage} object.
75
81
  * @param options - Options given to the session instance.
76
82
  */
77
- declare class Session<State extends Record<string, unknown> = DefaultSessionState> extends TypedEmitter<SessionEvents> {
83
+ declare class Session<State = DefaultSessionState> extends TypedEmitter<SessionEvents> {
78
84
  /**
79
85
  * The last event ID sent to the client.
80
86
  *
@@ -118,7 +124,7 @@ declare class Session<State extends Record<string, unknown> = DefaultSessionStat
118
124
  private keepAliveTimer?;
119
125
  private statusCode;
120
126
  private headers;
121
- constructor(req: Http1ServerRequest | Http2ServerRequest, res: Http1ServerResponse | Http2ServerResponse, options?: SessionOptions);
127
+ constructor(req: Http1ServerRequest | Http2ServerRequest, res: Http1ServerResponse | Http2ServerResponse, options?: SessionOptions<State>);
122
128
  private initialize;
123
129
  private onDisconnected;
124
130
  private keepAlive;
@@ -1,3 +1,3 @@
1
1
  import { Channel } from "./Channel";
2
- declare const createChannel: <State extends Record<string, unknown>, SessionState extends Record<string, unknown>>() => Channel<State, SessionState>;
2
+ declare const createChannel: <State, SessionState>(options?: import("./Channel").ChannelOptions<State> | undefined) => Channel<State, SessionState>;
3
3
  export { createChannel };
@@ -2,5 +2,5 @@ import { Session } from "./Session";
2
2
  /**
3
3
  * Create a new session and return the session instance once it has connected.
4
4
  */
5
- declare const createSession: <State extends Record<string, unknown>>(req: import("http").IncomingMessage | import("http2").Http2ServerRequest, res: import("http").ServerResponse<import("http").IncomingMessage> | import("http2").Http2ServerResponse, options?: import("./Session").SessionOptions | undefined) => Promise<Session<State>>;
5
+ declare const createSession: <State>(req: import("http").IncomingMessage | import("http2").Http2ServerRequest, res: import("http").ServerResponse<import("http").IncomingMessage> | import("http2").Http2ServerResponse, options?: import("./Session").SessionOptions<State> | undefined) => Promise<Session<State>>;
6
6
  export { createSession };
package/build/index.js CHANGED
@@ -1,2 +1,2 @@
1
- !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,(()=>(()=>{"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:()=>g,EventBuffer:()=>d,Session:()=>m,SseError:()=>v,createChannel:()=>y,createEventBuffer:()=>w,createSession:()=>b});const s=require("http"),i=e=>JSON.stringify(e),r=/(\r\n|\r|\n)/g,n=/\n+$/g,o=e=>{let t=e;return t=t.replace(r,"\n"),t=t.replace(n,""),t},h=require("crypto");let a;a=h.randomUUID?()=>(0,h.randomUUID)():()=>(0,h.randomBytes)(4).toString("hex");const c=e=>async(t,s={})=>{const{eventName:i="stream"}=s;return await new Promise(((s,r)=>{t.on("data",(t=>{let s;s=Buffer.isBuffer(t)?t.toString():t,e(s,i)})),t.once("end",(()=>s(!0))),t.once("close",(()=>s(!0))),t.once("error",(e=>r(e)))}))},l=e=>async(t,s={})=>{const{eventName:i="iteration"}=s;for await(const s of t)e(s,i)};class d{constructor(e={}){var t,s;this.buffer="",this.writeField=(e,t)=>{const s=this.sanitize(t);return this.buffer+=e+":"+s+"\n",this},this.data=e=>{const t=this.serialize(e);return this.writeField("data",t),this},this.id=(e="")=>(this.writeField("id",e),this),this.retry=e=>{const t=e.toString();return this.writeField("retry",t),this},this.comment=(e="")=>(this.writeField("",e),this),this.dispatch=()=>(this.buffer+="\n",this),this.push=(e,t="message",s=a())=>(this.event(t).id(s).data(e).dispatch(),this),this.stream=c(this.push),this.iterate=l(this.push),this.clear=()=>(this.buffer="",this),this.read=()=>this.buffer,this.serialize=null!==(t=e.serializer)&&void 0!==t?t:i,this.sanitize=null!==(s=e.sanitizer)&&void 0!==s?s:o}event(e){return this.writeField("event",e),this}}const u=require("events");var f=e.n(u);class p extends(f()){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)}}class v extends Error{constructor(e){super(e),this.message=`better-sse: ${e}`}}class m extends p{constructor(e,t,r={}){var n,h,u,f,p,m,b;super(),this.lastId="",this.isConnected=!1,this.state={},this.initialize=()=>{var e,t,i;const r=`http://${this.req.headers.host}${this.req.url}`,n=new URL(r).searchParams;if(this.trustClientEventId){const s=null!==(i=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!==i?i:"";this.lastId=s}const o={};this.res instanceof s.ServerResponse?(o["Content-Type"]="text/event-stream",o["Cache-Control"]="private, no-cache, no-store, no-transform, must-revalidate, max-age=0",o.Connection="keep-alive",o.Pragma="no-cache",o["X-Accel-Buffering"]="no"):(o["content-type"]="text/event-stream",o["cache-control"]="private, no-cache, no-store, no-transform, must-revalidate, max-age=0",o.pragma="no-cache",o["x-accel-buffering"]="no");for(const[e,t]of Object.entries(this.headers))o[e]=null!=t?t:"";this.res.writeHead(this.statusCode,o),n.has("padding")&&this.buffer.comment(" ".repeat(2049)).dispatch(),n.has("evs_preamble")&&this.buffer.comment(" ".repeat(2056)).dispatch(),null!==this.initialRetry&&this.buffer.retry(this.initialRetry).dispatch(),this.flush(),null!==this.keepAliveInterval&&(this.keepAliveTimer=setInterval(this.keepAlive,this.keepAliveInterval)),this.isConnected=!0,this.emit("connected")},this.onDisconnected=()=>{this.req.removeListener("close",this.onDisconnected),this.res.removeListener("close",this.onDisconnected),this.keepAliveTimer&&clearInterval(this.keepAliveTimer),this.isConnected=!1,this.emit("disconnected")},this.keepAlive=()=>{this.buffer.comment().dispatch(),this.flush()},this.data=e=>(this.buffer.data(e),this),this.id=(e="")=>(this.buffer.id(e),this.lastId=e,this),this.retry=e=>(this.buffer.retry(e),this),this.comment=e=>(this.buffer.comment(e),this),this.dispatch=()=>(this.buffer.dispatch(),this),this.flush=()=>(this.res.write(this.buffer.read()),this.buffer.clear(),this),this.push=(e,t="message",s=a())=>{if(!this.isConnected)throw new v("Cannot push data to a non-active session.");return this.buffer.push(e,t,s),this.flush(),this.lastId=s,this.emit("push",e,t,s),this},this.stream=c(this.push),this.iterate=l(this.push),this.batch=async e=>{if(e instanceof d)this.res.write(e.read());else{const t=new d({serializer:this.serialize,sanitizer:this.sanitize});await e(t),this.res.write(t.read())}},this.req=e,this.res=t;const g=null!==(n=r.serializer)&&void 0!==n?n:i,y=null!==(h=r.sanitizer)&&void 0!==h?h:o;this.serialize=g,this.sanitize=y,this.buffer=new d({serializer:g,sanitizer:y}),this.trustClientEventId=null===(u=r.trustClientEventId)||void 0===u||u,this.initialRetry=null===r.retry?null:null!==(f=r.retry)&&void 0!==f?f:2e3,this.keepAliveInterval=null===r.keepAlive?null:null!==(p=r.keepAlive)&&void 0!==p?p:1e4,this.statusCode=null!==(m=r.statusCode)&&void 0!==m?m:200,this.headers=null!==(b=r.headers)&&void 0!==b?b:{},this.req.once("close",this.onDisconnected),this.res.once("close",this.onDisconnected),setImmediate(this.initialize)}event(e){return this.buffer.event(e),this}}const b=(...e)=>new Promise((t=>{const s=new m(...e);s.once("connected",(()=>{t(s)}))}));class g extends p{constructor(){super(),this.state={},this.sessions=new Set,this.broadcast=(e,t="message",s={})=>{var i;const r=null!==(i=s.eventId)&&void 0!==i?i:a(),n=s.filter?this.activeSessions.filter(s.filter):this.sessions;for(const s of n)s.push(e,t,r);return this.emit("broadcast",e,t,r),this}}get activeSessions(){return Array.from(this.sessions)}get sessionCount(){return this.sessions.size}register(e){if(this.sessions.has(e))return this;if(!e.isConnected)throw new v("Cannot register a non-active session.");return e.once("disconnected",(()=>{this.emit("session-disconnected",e),this.deregister(e)})),this.sessions.add(e),this.emit("session-registered",e),this}deregister(e){return this.sessions.has(e)?(this.sessions.delete(e),this.emit("session-deregistered",e),this):this}}const y=(...e)=>new g(...e),w=(...e)=>new d(...e);return t})()));
1
+ !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,(()=>(()=>{"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:()=>g,EventBuffer:()=>d,Session:()=>m,SseError:()=>v,createChannel:()=>y,createEventBuffer:()=>w,createSession:()=>b});const s=require("http"),i=e=>JSON.stringify(e),r=/(\r\n|\r|\n)/g,n=/\n+$/g,o=e=>{let t=e;return t=t.replace(r,"\n"),t=t.replace(n,""),t},h=require("crypto");let a;a=h.randomUUID?()=>(0,h.randomUUID)():()=>(0,h.randomBytes)(4).toString("hex");const l=e=>async(t,s={})=>{const{eventName:i="stream"}=s;return await new Promise(((s,r)=>{t.on("data",(t=>{let s;s=Buffer.isBuffer(t)?t.toString():t,e(s,i)})),t.once("end",(()=>s(!0))),t.once("close",(()=>s(!0))),t.once("error",(e=>r(e)))}))},c=e=>async(t,s={})=>{const{eventName:i="iteration"}=s;for await(const s of t)e(s,i)};class d{constructor(e={}){var t,s;this.buffer="",this.writeField=(e,t)=>{const s=this.sanitize(t);return this.buffer+=e+":"+s+"\n",this},this.data=e=>{const t=this.serialize(e);return this.writeField("data",t),this},this.id=(e="")=>(this.writeField("id",e),this),this.retry=e=>{const t=e.toString();return this.writeField("retry",t),this},this.comment=(e="")=>(this.writeField("",e),this),this.dispatch=()=>(this.buffer+="\n",this),this.push=(e,t="message",s=a())=>(this.event(t).id(s).data(e).dispatch(),this),this.stream=l(this.push),this.iterate=c(this.push),this.clear=()=>(this.buffer="",this),this.read=()=>this.buffer,this.serialize=null!==(t=e.serializer)&&void 0!==t?t:i,this.sanitize=null!==(s=e.sanitizer)&&void 0!==s?s:o}event(e){return this.writeField("event",e),this}}const u=require("events");var f=e.n(u);class p extends(f()){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)}}class v extends Error{constructor(e){super(e),this.message=`better-sse: ${e}`}}class m extends p{constructor(e,t,r={}){var n,h,u,f,p,m,b,g;super(),this.lastId="",this.isConnected=!1,this.initialize=()=>{var e,t,i;const r=`http://${this.req.headers.host}${this.req.url}`,n=new URL(r).searchParams;if(this.trustClientEventId){const s=null!==(i=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!==i?i:"";this.lastId=s}const o={};this.res instanceof s.ServerResponse?(o["Content-Type"]="text/event-stream",o["Cache-Control"]="private, no-cache, no-store, no-transform, must-revalidate, max-age=0",o.Connection="keep-alive",o.Pragma="no-cache",o["X-Accel-Buffering"]="no"):(o["content-type"]="text/event-stream",o["cache-control"]="private, no-cache, no-store, no-transform, must-revalidate, max-age=0",o.pragma="no-cache",o["x-accel-buffering"]="no");for(const[e,t]of Object.entries(this.headers))o[e]=null!=t?t:"";this.res.writeHead(this.statusCode,o),n.has("padding")&&this.buffer.comment(" ".repeat(2049)).dispatch(),n.has("evs_preamble")&&this.buffer.comment(" ".repeat(2056)).dispatch(),null!==this.initialRetry&&this.buffer.retry(this.initialRetry).dispatch(),this.flush(),null!==this.keepAliveInterval&&(this.keepAliveTimer=setInterval(this.keepAlive,this.keepAliveInterval)),this.isConnected=!0,this.emit("connected")},this.onDisconnected=()=>{this.req.removeListener("close",this.onDisconnected),this.res.removeListener("close",this.onDisconnected),this.keepAliveTimer&&clearInterval(this.keepAliveTimer),this.isConnected=!1,this.emit("disconnected")},this.keepAlive=()=>{this.buffer.comment().dispatch(),this.flush()},this.data=e=>(this.buffer.data(e),this),this.id=(e="")=>(this.buffer.id(e),this.lastId=e,this),this.retry=e=>(this.buffer.retry(e),this),this.comment=e=>(this.buffer.comment(e),this),this.dispatch=()=>(this.buffer.dispatch(),this),this.flush=()=>(this.res.write(this.buffer.read()),this.buffer.clear(),this),this.push=(e,t="message",s=a())=>{if(!this.isConnected)throw new v("Cannot push data to a non-active session.");return this.buffer.push(e,t,s),this.flush(),this.lastId=s,this.emit("push",e,t,s),this},this.stream=l(this.push),this.iterate=c(this.push),this.batch=async e=>{if(e instanceof d)this.res.write(e.read());else{const t=new d({serializer:this.serialize,sanitizer:this.sanitize});await e(t),this.res.write(t.read())}},this.req=e,this.res=t;const y=null!==(n=r.serializer)&&void 0!==n?n:i,w=null!==(h=r.sanitizer)&&void 0!==h?h:o;this.serialize=y,this.sanitize=w,this.buffer=new d({serializer:y,sanitizer:w}),this.trustClientEventId=null===(u=r.trustClientEventId)||void 0===u||u,this.initialRetry=null===r.retry?null:null!==(f=r.retry)&&void 0!==f?f:2e3,this.keepAliveInterval=null===r.keepAlive?null:null!==(p=r.keepAlive)&&void 0!==p?p:1e4,this.statusCode=null!==(m=r.statusCode)&&void 0!==m?m:200,this.headers=null!==(b=r.headers)&&void 0!==b?b:{},this.state=null!==(g=r.state)&&void 0!==g?g:{},this.req.once("close",this.onDisconnected),this.res.once("close",this.onDisconnected),setImmediate(this.initialize)}event(e){return this.buffer.event(e),this}}const b=(...e)=>new Promise((t=>{const s=new m(...e);s.once("connected",(()=>{t(s)}))}));class g extends p{constructor(e={}){var t;super(),this.sessions=new Set,this.broadcast=(e,t="message",s={})=>{var i;const r=null!==(i=s.eventId)&&void 0!==i?i:a(),n=s.filter?this.activeSessions.filter(s.filter):this.sessions;for(const s of n)s.push(e,t,r);return this.emit("broadcast",e,t,r),this},this.state=null!==(t=e.state)&&void 0!==t?t:{}}get activeSessions(){return Array.from(this.sessions)}get sessionCount(){return this.sessions.size}register(e){if(this.sessions.has(e))return this;if(!e.isConnected)throw new v("Cannot register a non-active session.");return e.once("disconnected",(()=>{this.emit("session-disconnected",e),this.deregister(e)})),this.sessions.add(e),this.emit("session-registered",e),this}deregister(e){return this.sessions.has(e)?(this.sessions.delete(e),this.emit("session-deregistered",e),this):this}}const y=(...e)=>new g(...e),w=(...e)=>new d(...e);return t})()));
2
2
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "better-sse",
3
3
  "description": "Dead simple, dependency-less, spec-compliant server-sent events implementation for Node, written in TypeScript.",
4
- "version": "0.12.1",
4
+ "version": "0.13.0",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
7
7
  "license": "MIT",