@gjsify/eventsource 0.3.13 → 0.3.14

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.
package/lib/esm/index.js CHANGED
@@ -1,232 +1,235 @@
1
1
  import "@gjsify/web-streams/register";
2
- import {
3
- Event as DomEvent,
4
- EventTarget as DomEventTarget,
5
- MessageEvent as DomMessageEvent
6
- } from "@gjsify/dom-events";
2
+ import { Event, EventTarget, MessageEvent } from "@gjsify/dom-events";
3
+
4
+ //#region src/index.ts
7
5
  const CONNECTING = 0;
8
6
  const OPEN = 1;
9
7
  const CLOSED = 2;
10
- const _Event = typeof globalThis.Event === "function" ? globalThis.Event : DomEvent;
11
- const _EventTarget = typeof globalThis.EventTarget === "function" ? globalThis.EventTarget : DomEventTarget;
12
- const _MessageEvent = typeof globalThis.MessageEvent === "function" ? globalThis.MessageEvent : DomMessageEvent;
13
- class TextLineStream extends TransformStream {
14
- #buf = "";
15
- constructor() {
16
- super({
17
- transform: (chunk, controller) => this.#handle(chunk, controller),
18
- flush: (controller) => {
19
- if (this.#buf.length > 0) {
20
- controller.enqueue(this.#buf);
21
- }
22
- }
23
- });
24
- }
25
- #handle(chunk, controller) {
26
- chunk = this.#buf + chunk;
27
- for (; ; ) {
28
- const lfIndex = chunk.indexOf("\n");
29
- if (lfIndex !== -1) {
30
- let crOrLfIndex = lfIndex;
31
- if (chunk[lfIndex - 1] === "\r") {
32
- crOrLfIndex--;
33
- }
34
- controller.enqueue(chunk.slice(0, crOrLfIndex));
35
- chunk = chunk.slice(lfIndex + 1);
36
- continue;
37
- }
38
- const crIndex = chunk.indexOf("\r");
39
- if (crIndex !== -1 && crIndex !== chunk.length - 1) {
40
- controller.enqueue(chunk.slice(0, crIndex));
41
- chunk = chunk.slice(crIndex + 1);
42
- continue;
43
- }
44
- break;
45
- }
46
- this.#buf = chunk;
47
- }
48
- }
49
- class EventSource extends _EventTarget {
50
- static CONNECTING = CONNECTING;
51
- static OPEN = OPEN;
52
- static CLOSED = CLOSED;
53
- CONNECTING = CONNECTING;
54
- OPEN = OPEN;
55
- CLOSED = CLOSED;
56
- #abortController = new AbortController();
57
- #reconnectionTimerId;
58
- #reconnectionTime = 5e3;
59
- #lastEventId = "";
60
- #readyState = CONNECTING;
61
- #url;
62
- #withCredentials;
63
- // Event handler attributes
64
- onopen = null;
65
- onmessage = null;
66
- onerror = null;
67
- constructor(url, eventSourceInitDict) {
68
- super();
69
- const urlStr = String(url);
70
- try {
71
- this.#url = new URL(urlStr).href;
72
- } catch {
73
- throw new DOMException(`Failed to construct 'EventSource': ${urlStr} is not a valid URL`, "SyntaxError");
74
- }
75
- this.#withCredentials = eventSourceInitDict?.withCredentials ?? false;
76
- this.#loop();
77
- }
78
- get readyState() {
79
- return this.#readyState;
80
- }
81
- get url() {
82
- return this.#url;
83
- }
84
- get withCredentials() {
85
- return this.#withCredentials;
86
- }
87
- close() {
88
- this.#abortController.abort();
89
- this.#readyState = CLOSED;
90
- if (this.#reconnectionTimerId !== void 0) {
91
- clearTimeout(this.#reconnectionTimerId);
92
- this.#reconnectionTimerId = void 0;
93
- }
94
- }
95
- dispatchEvent(event) {
96
- const type = event.type;
97
- if (type === "open" && this.onopen) {
98
- this.onopen.call(this, event);
99
- } else if (type === "message" && this.onmessage) {
100
- this.onmessage.call(this, event);
101
- } else if (type === "error" && this.onerror) {
102
- this.onerror.call(this, event);
103
- }
104
- return super.dispatchEvent(event);
105
- }
106
- async #loop() {
107
- const headers = {
108
- "Accept": "text/event-stream",
109
- "Cache-Control": "no-cache"
110
- };
111
- if (this.#lastEventId) {
112
- headers["Last-Event-ID"] = this.#lastEventId;
113
- }
114
- let res;
115
- try {
116
- res = await fetch(this.#url, {
117
- headers,
118
- signal: this.#abortController.signal
119
- });
120
- } catch {
121
- this.#reestablishConnection();
122
- return;
123
- }
124
- const contentType = res.headers.get("content-type") || "";
125
- if (res.status !== 200 || !contentType.toLowerCase().includes("text/event-stream")) {
126
- this.#failConnection();
127
- return;
128
- }
129
- if (this.#readyState === CLOSED) {
130
- return;
131
- }
132
- this.#readyState = OPEN;
133
- this.dispatchEvent(new _Event("open"));
134
- let data = "";
135
- let eventType = "";
136
- let lastEventId = this.#lastEventId;
137
- try {
138
- const body = res.body;
139
- if (!body) {
140
- this.#reestablishConnection();
141
- return;
142
- }
143
- const lineStream = body.pipeThrough(new TextDecoderStream()).pipeThrough(new TextLineStream());
144
- const reader = lineStream.getReader();
145
- while (true) {
146
- const { value: line, done } = await reader.read();
147
- if (done) break;
148
- if (line === "") {
149
- this.#lastEventId = lastEventId;
150
- if (data === "") {
151
- eventType = "";
152
- continue;
153
- }
154
- if (data.endsWith("\n")) {
155
- data = data.slice(0, -1);
156
- }
157
- const event = new _MessageEvent(eventType || "message", {
158
- data,
159
- origin: this.#url,
160
- lastEventId: this.#lastEventId
161
- });
162
- data = "";
163
- eventType = "";
164
- if (this.#readyState !== CLOSED) {
165
- this.dispatchEvent(event);
166
- }
167
- } else if (line.startsWith(":")) {
168
- continue;
169
- } else {
170
- let field = line;
171
- let value = "";
172
- const colonIndex = line.indexOf(":");
173
- if (colonIndex !== -1) {
174
- field = line.slice(0, colonIndex);
175
- value = line.slice(colonIndex + 1);
176
- if (value.startsWith(" ")) {
177
- value = value.slice(1);
178
- }
179
- }
180
- switch (field) {
181
- case "event":
182
- eventType = value;
183
- break;
184
- case "data":
185
- data += value + "\n";
186
- break;
187
- case "id":
188
- if (!value.includes("\0")) {
189
- lastEventId = value;
190
- }
191
- break;
192
- case "retry": {
193
- const ms = Number(value);
194
- if (!Number.isNaN(ms) && Number.isFinite(ms)) {
195
- this.#reconnectionTime = ms;
196
- }
197
- break;
198
- }
199
- }
200
- }
201
- }
202
- } catch {
203
- }
204
- this.#reestablishConnection();
205
- }
206
- #reestablishConnection() {
207
- if (this.#readyState === CLOSED) {
208
- return;
209
- }
210
- this.#readyState = CONNECTING;
211
- this.dispatchEvent(new _Event("error"));
212
- this.#reconnectionTimerId = setTimeout(() => {
213
- if (this.#readyState !== CONNECTING) {
214
- return;
215
- }
216
- this.#abortController = new AbortController();
217
- this.#loop();
218
- }, this.#reconnectionTime);
219
- }
220
- #failConnection() {
221
- if (this.#readyState !== CLOSED) {
222
- this.#readyState = CLOSED;
223
- this.dispatchEvent(new _Event("error"));
224
- }
225
- }
226
- }
227
- var index_default = EventSource;
228
- export {
229
- EventSource,
230
- TextLineStream,
231
- index_default as default
8
+ const _Event = typeof globalThis.Event === "function" ? globalThis.Event : Event;
9
+ const _EventTarget = typeof globalThis.EventTarget === "function" ? globalThis.EventTarget : EventTarget;
10
+ const _MessageEvent = typeof globalThis.MessageEvent === "function" ? globalThis.MessageEvent : MessageEvent;
11
+ /**
12
+ * TextLineStream splits a string stream into individual lines.
13
+ * Handles \n, \r\n, and standalone \r line endings.
14
+ */
15
+ var TextLineStream = class extends TransformStream {
16
+ #buf = "";
17
+ constructor() {
18
+ super({
19
+ transform: (chunk, controller) => this.#handle(chunk, controller),
20
+ flush: (controller) => {
21
+ if (this.#buf.length > 0) {
22
+ controller.enqueue(this.#buf);
23
+ }
24
+ }
25
+ });
26
+ }
27
+ #handle(chunk, controller) {
28
+ chunk = this.#buf + chunk;
29
+ for (;;) {
30
+ const lfIndex = chunk.indexOf("\n");
31
+ if (lfIndex !== -1) {
32
+ let crOrLfIndex = lfIndex;
33
+ if (chunk[lfIndex - 1] === "\r") {
34
+ crOrLfIndex--;
35
+ }
36
+ controller.enqueue(chunk.slice(0, crOrLfIndex));
37
+ chunk = chunk.slice(lfIndex + 1);
38
+ continue;
39
+ }
40
+ const crIndex = chunk.indexOf("\r");
41
+ if (crIndex !== -1 && crIndex !== chunk.length - 1) {
42
+ controller.enqueue(chunk.slice(0, crIndex));
43
+ chunk = chunk.slice(crIndex + 1);
44
+ continue;
45
+ }
46
+ break;
47
+ }
48
+ this.#buf = chunk;
49
+ }
232
50
  };
51
+ /**
52
+ * EventSource — W3C Server-Sent Events API.
53
+ *
54
+ * Connects to an SSE endpoint via fetch, pipes the response through
55
+ * TextDecoderStream and TextLineStream, then parses SSE fields.
56
+ */
57
+ var EventSource = class extends _EventTarget {
58
+ static CONNECTING = CONNECTING;
59
+ static OPEN = OPEN;
60
+ static CLOSED = CLOSED;
61
+ CONNECTING = CONNECTING;
62
+ OPEN = OPEN;
63
+ CLOSED = CLOSED;
64
+ #abortController = new AbortController();
65
+ #reconnectionTimerId;
66
+ #reconnectionTime = 5e3;
67
+ #lastEventId = "";
68
+ #readyState = CONNECTING;
69
+ #url;
70
+ #withCredentials;
71
+ onopen = null;
72
+ onmessage = null;
73
+ onerror = null;
74
+ constructor(url, eventSourceInitDict) {
75
+ super();
76
+ const urlStr = String(url);
77
+ try {
78
+ this.#url = new URL(urlStr).href;
79
+ } catch {
80
+ throw new DOMException(`Failed to construct 'EventSource': ${urlStr} is not a valid URL`, "SyntaxError");
81
+ }
82
+ this.#withCredentials = eventSourceInitDict?.withCredentials ?? false;
83
+ this.#loop();
84
+ }
85
+ get readyState() {
86
+ return this.#readyState;
87
+ }
88
+ get url() {
89
+ return this.#url;
90
+ }
91
+ get withCredentials() {
92
+ return this.#withCredentials;
93
+ }
94
+ close() {
95
+ this.#abortController.abort();
96
+ this.#readyState = CLOSED;
97
+ if (this.#reconnectionTimerId !== undefined) {
98
+ clearTimeout(this.#reconnectionTimerId);
99
+ this.#reconnectionTimerId = undefined;
100
+ }
101
+ }
102
+ dispatchEvent(event) {
103
+ const type = event.type;
104
+ if (type === "open" && this.onopen) {
105
+ this.onopen.call(this, event);
106
+ } else if (type === "message" && this.onmessage) {
107
+ this.onmessage.call(this, event);
108
+ } else if (type === "error" && this.onerror) {
109
+ this.onerror.call(this, event);
110
+ }
111
+ return super.dispatchEvent(event);
112
+ }
113
+ async #loop() {
114
+ const headers = {
115
+ "Accept": "text/event-stream",
116
+ "Cache-Control": "no-cache"
117
+ };
118
+ if (this.#lastEventId) {
119
+ headers["Last-Event-ID"] = this.#lastEventId;
120
+ }
121
+ let res;
122
+ try {
123
+ res = await fetch(this.#url, {
124
+ headers,
125
+ signal: this.#abortController.signal
126
+ });
127
+ } catch {
128
+ this.#reestablishConnection();
129
+ return;
130
+ }
131
+ const contentType = res.headers.get("content-type") || "";
132
+ if (res.status !== 200 || !contentType.toLowerCase().includes("text/event-stream")) {
133
+ this.#failConnection();
134
+ return;
135
+ }
136
+ if (this.#readyState === CLOSED) {
137
+ return;
138
+ }
139
+ this.#readyState = OPEN;
140
+ this.dispatchEvent(new _Event("open"));
141
+ let data = "";
142
+ let eventType = "";
143
+ let lastEventId = this.#lastEventId;
144
+ try {
145
+ const body = res.body;
146
+ if (!body) {
147
+ this.#reestablishConnection();
148
+ return;
149
+ }
150
+ const lineStream = body.pipeThrough(new TextDecoderStream()).pipeThrough(new TextLineStream());
151
+ const reader = lineStream.getReader();
152
+ while (true) {
153
+ const { value: line, done } = await reader.read();
154
+ if (done) break;
155
+ if (line === "") {
156
+ this.#lastEventId = lastEventId;
157
+ if (data === "") {
158
+ eventType = "";
159
+ continue;
160
+ }
161
+ if (data.endsWith("\n")) {
162
+ data = data.slice(0, -1);
163
+ }
164
+ const event = new _MessageEvent(eventType || "message", {
165
+ data,
166
+ origin: this.#url,
167
+ lastEventId: this.#lastEventId
168
+ });
169
+ data = "";
170
+ eventType = "";
171
+ if (this.#readyState !== CLOSED) {
172
+ this.dispatchEvent(event);
173
+ }
174
+ } else if (line.startsWith(":")) {
175
+ continue;
176
+ } else {
177
+ let field = line;
178
+ let value = "";
179
+ const colonIndex = line.indexOf(":");
180
+ if (colonIndex !== -1) {
181
+ field = line.slice(0, colonIndex);
182
+ value = line.slice(colonIndex + 1);
183
+ if (value.startsWith(" ")) {
184
+ value = value.slice(1);
185
+ }
186
+ }
187
+ switch (field) {
188
+ case "event":
189
+ eventType = value;
190
+ break;
191
+ case "data":
192
+ data += value + "\n";
193
+ break;
194
+ case "id":
195
+ if (!value.includes("\0")) {
196
+ lastEventId = value;
197
+ }
198
+ break;
199
+ case "retry": {
200
+ const ms = Number(value);
201
+ if (!Number.isNaN(ms) && Number.isFinite(ms)) {
202
+ this.#reconnectionTime = ms;
203
+ }
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ }
209
+ } catch {}
210
+ this.#reestablishConnection();
211
+ }
212
+ #reestablishConnection() {
213
+ if (this.#readyState === CLOSED) {
214
+ return;
215
+ }
216
+ this.#readyState = CONNECTING;
217
+ this.dispatchEvent(new _Event("error"));
218
+ this.#reconnectionTimerId = setTimeout(() => {
219
+ if (this.#readyState !== CONNECTING) {
220
+ return;
221
+ }
222
+ this.#abortController = new AbortController();
223
+ this.#loop();
224
+ }, this.#reconnectionTime);
225
+ }
226
+ #failConnection() {
227
+ if (this.#readyState !== CLOSED) {
228
+ this.#readyState = CLOSED;
229
+ this.dispatchEvent(new _Event("error"));
230
+ }
231
+ }
232
+ };
233
+
234
+ //#endregion
235
+ export { EventSource, EventSource as default, TextLineStream };
@@ -1,18 +1,18 @@
1
- import {
2
- Event as DomEvent,
3
- EventTarget as DomEventTarget,
4
- MessageEvent as DomMessageEvent
5
- } from "@gjsify/dom-events";
6
- import EventSource from "./index.js";
1
+ import { EventSource } from "./index.js";
2
+ import { Event, EventTarget, MessageEvent } from "@gjsify/dom-events";
3
+
4
+ //#region src/register.ts
7
5
  if (typeof globalThis.Event === "undefined") {
8
- globalThis.Event = DomEvent;
6
+ globalThis.Event = Event;
9
7
  }
10
8
  if (typeof globalThis.EventTarget === "undefined") {
11
- globalThis.EventTarget = DomEventTarget;
9
+ globalThis.EventTarget = EventTarget;
12
10
  }
13
11
  if (typeof globalThis.MessageEvent === "undefined") {
14
- globalThis.MessageEvent = DomMessageEvent;
12
+ globalThis.MessageEvent = MessageEvent;
15
13
  }
16
14
  if (typeof globalThis.EventSource === "undefined") {
17
- globalThis.EventSource = EventSource;
15
+ globalThis.EventSource = EventSource;
18
16
  }
17
+
18
+ //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/eventsource",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "W3C EventSource (Server-Sent Events) for GJS",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -39,12 +39,12 @@
39
39
  "server-sent-events"
40
40
  ],
41
41
  "dependencies": {
42
- "@gjsify/dom-events": "^0.3.13",
43
- "@gjsify/web-streams": "^0.3.13"
42
+ "@gjsify/dom-events": "^0.3.14",
43
+ "@gjsify/web-streams": "^0.3.14"
44
44
  },
45
45
  "devDependencies": {
46
- "@gjsify/cli": "^0.3.13",
47
- "@gjsify/unit": "^0.3.13",
46
+ "@gjsify/cli": "^0.3.14",
47
+ "@gjsify/unit": "^0.3.14",
48
48
  "@types/node": "^25.6.0",
49
49
  "typescript": "^6.0.3"
50
50
  }