jsgui3-server 0.0.147 → 0.0.149
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/.github/workflows/control-scan-manifest-check.yml +31 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +44 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +45 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +42 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +43 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +41 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +44 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +39 -0
- package/README.md +85 -3
- package/admin-ui/client.js +213 -0
- package/admin-ui/server.js +104 -0
- package/client/controls/auto-observable.js +207 -0
- package/dev-status.svg +139 -0
- package/docs/api-reference.md +301 -43
- package/docs/books/admin-ui/01-introduction.md +32 -0
- package/docs/books/admin-ui/02-architecture.md +92 -0
- package/docs/books/admin-ui/03-controls.md +194 -0
- package/docs/books/admin-ui/04-implementation-plan.md +62 -0
- package/docs/books/admin-ui/README.md +26 -0
- package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
- package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
- package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
- package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
- package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
- package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
- package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
- package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
- package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
- package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
- package/docs/bundling-system-deep-dive.md +9 -4
- package/docs/comprehensive-documentation.md +49 -18
- package/docs/configuration-reference.md +152 -27
- package/docs/core/README.md +19 -0
- package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
- package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
- package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
- package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
- package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
- package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
- package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
- package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
- package/docs/publishers-guide.md +59 -4
- package/docs/resources-guide.md +184 -35
- package/docs/simple-server-api-design.md +72 -17
- package/docs/system-architecture.md +18 -14
- package/examples/controls/15) window, observable SSE/server.js +6 -1
- package/examples/controls/19) window, auto observable ui/client.js +125 -0
- package/examples/controls/19) window, auto observable ui/server.js +73 -0
- package/examples/controls/20) window, task manager app/README.md +133 -0
- package/examples/controls/20) window, task manager app/client.js +797 -0
- package/examples/controls/20) window, task manager app/server.js +178 -0
- package/examples/controls/6) window, color_palette/client.js +165 -68
- package/examples/controls/9) window, date picker/client.js +362 -76
- package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
- package/examples/jsgui3-html/06) theming/client.js +22 -1
- package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
- package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
- package/lab/experiments/capture-color-controls.js +196 -0
- package/lab/results/screenshots/color-controls/full_page.png +0 -0
- package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
- package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
- package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
- package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
- package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
- package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
- package/lab/screenshot-utils.js +248 -0
- package/module.js +11 -4
- package/package.json +14 -4
- package/publishers/Publishers.js +4 -3
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
- package/publishers/http-observable-publisher.js +8 -0
- package/publishers/http-sse-publisher.js +341 -0
- package/publishers/http-webpage-publisher.js +13 -3
- package/publishers/http-webpageorsite-publisher.js +18 -0
- package/resources/process-resource.js +950 -0
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +164 -46
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
- package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
- package/resources/remote-process-resource.js +355 -0
- package/resources/server-resource-pool.js +354 -41
- package/serve-factory.js +441 -259
- package/server.js +161 -16
- package/tests/README.md +66 -4
- package/tests/admin-ui-render.test.js +24 -0
- package/tests/assigners.test.js +56 -40
- package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
- package/tests/configuration-validation.test.js +21 -18
- package/tests/content-analysis.test.js +7 -6
- package/tests/control-optimizer-cache-behavior.test.js +52 -0
- package/tests/control-scan-manifest-regression.test.js +144 -0
- package/tests/end-to-end.test.js +15 -14
- package/tests/error-handling.test.js +222 -179
- package/tests/fixtures/bundling-default-button-client.js +37 -0
- package/tests/fixtures/bundling-default-window-client.js +34 -0
- package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
- package/tests/fixtures/resource-monitor-client.js +319 -0
- package/tests/helpers/puppeteer-e2e-harness.js +317 -0
- package/tests/http-sse-publisher.test.js +136 -0
- package/tests/performance.test.js +69 -65
- package/tests/process-resource.test.js +138 -0
- package/tests/publishers.test.js +7 -7
- package/tests/remote-process-resource.test.js +160 -0
- package/tests/sass-controls.e2e.test.js +7 -1
- package/tests/serve-resources.test.js +270 -0
- package/tests/serve.test.js +120 -50
- package/tests/server-resource-pool.test.js +106 -0
- package/tests/small-controls-bundle-size.test.js +252 -0
- package/tests/test-runner.js +13 -1
- package/tests/window-examples.puppeteer.test.js +204 -1
- package/tests/window-resource-integration.puppeteer.test.js +585 -0
- package/tests/temp_invalid.js +0 -7
- package/tests/temp_invalid_utf8.js +0 -1
- package/tests/temp_malformed.js +0 -10
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
const HTTP_Publisher = require('./http-publisher');
|
|
2
|
+
|
|
3
|
+
const default_keepalive_interval_ms = 15000;
|
|
4
|
+
const default_event_history_size = 200;
|
|
5
|
+
|
|
6
|
+
const to_data_string = (data_value) => {
|
|
7
|
+
if (data_value === undefined) {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
if (typeof data_value === 'string') {
|
|
11
|
+
return data_value;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
return JSON.stringify(data_value);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
return JSON.stringify({
|
|
17
|
+
error: 'non_serializable',
|
|
18
|
+
message: error instanceof Error ? error.message : String(error)
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
class HTTP_SSE_Publisher extends HTTP_Publisher {
|
|
24
|
+
constructor(spec = {}) {
|
|
25
|
+
super(spec);
|
|
26
|
+
|
|
27
|
+
this.name = spec.name || this.name || 'events';
|
|
28
|
+
this.keepalive_interval_ms = Number.isFinite(spec.keepaliveIntervalMs)
|
|
29
|
+
? Number(spec.keepaliveIntervalMs)
|
|
30
|
+
: default_keepalive_interval_ms;
|
|
31
|
+
this.max_clients = Number.isFinite(spec.maxClients)
|
|
32
|
+
? Number(spec.maxClients)
|
|
33
|
+
: Number.POSITIVE_INFINITY;
|
|
34
|
+
this.event_history_size = Number.isInteger(spec.eventHistorySize)
|
|
35
|
+
? spec.eventHistorySize
|
|
36
|
+
: default_event_history_size;
|
|
37
|
+
|
|
38
|
+
this.clients = new Map();
|
|
39
|
+
this.event_history = [];
|
|
40
|
+
this.next_event_id = 0;
|
|
41
|
+
|
|
42
|
+
this._keepalive_timer = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get client_count() {
|
|
46
|
+
return this.clients.size;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_generate_client_id() {
|
|
50
|
+
return `sse_${Date.now()}_${Math.random().toString(16).slice(2, 10)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_parse_connection_url(req) {
|
|
54
|
+
const host_header = req.headers.host || 'localhost';
|
|
55
|
+
return new URL(req.url || '/', `http://${host_header}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_extract_last_event_id(req, parsed_url) {
|
|
59
|
+
const query_value = parsed_url.searchParams.get('lastEventId')
|
|
60
|
+
|| parsed_url.searchParams.get('last_event_id');
|
|
61
|
+
const header_value = req.headers['last-event-id'];
|
|
62
|
+
const raw_value = query_value || header_value;
|
|
63
|
+
|
|
64
|
+
if (raw_value === undefined || raw_value === null || raw_value === '') {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const parsed_value = Number.parseInt(String(raw_value), 10);
|
|
69
|
+
if (!Number.isFinite(parsed_value)) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return parsed_value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_ensure_keepalive_timer() {
|
|
76
|
+
if (this._keepalive_timer || !Number.isFinite(this.keepalive_interval_ms) || this.keepalive_interval_ms <= 0) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this._keepalive_timer = setInterval(() => {
|
|
81
|
+
this._broadcast_comment('keepalive');
|
|
82
|
+
}, this.keepalive_interval_ms);
|
|
83
|
+
this._keepalive_timer.unref?.();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_maybe_stop_keepalive_timer() {
|
|
87
|
+
if (this._keepalive_timer && this.clients.size === 0) {
|
|
88
|
+
clearInterval(this._keepalive_timer);
|
|
89
|
+
this._keepalive_timer = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_write_to_client(client_id, payload_text) {
|
|
94
|
+
const client_entry = this.clients.get(client_id);
|
|
95
|
+
if (!client_entry) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const { res } = client_entry;
|
|
100
|
+
if (!res || res.destroyed || res.writableEnded) {
|
|
101
|
+
this._disconnect_client(client_id, 'write_failed');
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
res.write(payload_text);
|
|
107
|
+
return true;
|
|
108
|
+
} catch {
|
|
109
|
+
this._disconnect_client(client_id, 'write_failed');
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_format_event_payload(event_name, data_value, event_id) {
|
|
115
|
+
const payload_lines = [];
|
|
116
|
+
if (event_id !== undefined && event_id !== null) {
|
|
117
|
+
payload_lines.push(`id: ${event_id}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (event_name) {
|
|
121
|
+
payload_lines.push(`event: ${event_name}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const data_text = to_data_string(data_value);
|
|
125
|
+
const data_lines = data_text.split(/\r?\n/);
|
|
126
|
+
if (data_lines.length === 0) {
|
|
127
|
+
payload_lines.push('data:');
|
|
128
|
+
} else {
|
|
129
|
+
for (const line of data_lines) {
|
|
130
|
+
payload_lines.push(`data: ${line}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
payload_lines.push('');
|
|
135
|
+
payload_lines.push('');
|
|
136
|
+
return payload_lines.join('\n');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
_record_event(event_record) {
|
|
140
|
+
this.event_history.push(event_record);
|
|
141
|
+
while (this.event_history.length > this.event_history_size) {
|
|
142
|
+
this.event_history.shift();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_broadcast_comment(comment_text) {
|
|
147
|
+
const payload = `:${comment_text}\n\n`;
|
|
148
|
+
const client_ids = Array.from(this.clients.keys());
|
|
149
|
+
for (const client_id of client_ids) {
|
|
150
|
+
this._write_to_client(client_id, payload);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_send_event_to_clients(client_ids, event_name, data_value) {
|
|
155
|
+
const event_id = ++this.next_event_id;
|
|
156
|
+
const payload_text = this._format_event_payload(event_name, data_value, event_id);
|
|
157
|
+
|
|
158
|
+
const delivered_client_ids = [];
|
|
159
|
+
for (const client_id of client_ids) {
|
|
160
|
+
const did_write = this._write_to_client(client_id, payload_text);
|
|
161
|
+
if (did_write) {
|
|
162
|
+
const client_entry = this.clients.get(client_id);
|
|
163
|
+
if (client_entry) {
|
|
164
|
+
client_entry.last_event_id = event_id;
|
|
165
|
+
}
|
|
166
|
+
delivered_client_ids.push(client_id);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this._record_event({
|
|
171
|
+
id: event_id,
|
|
172
|
+
event: event_name,
|
|
173
|
+
data: data_value,
|
|
174
|
+
timestamp: Date.now()
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
eventId: event_id,
|
|
179
|
+
deliveredClientIds: delivered_client_ids
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
_replay_events_since(client_id, last_event_id) {
|
|
184
|
+
if (last_event_id === null) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const replay_candidates = this.event_history.filter((entry) => entry.id > last_event_id);
|
|
189
|
+
for (const replay_event of replay_candidates) {
|
|
190
|
+
const payload_text = this._format_event_payload(replay_event.event, replay_event.data, replay_event.id);
|
|
191
|
+
const did_write = this._write_to_client(client_id, payload_text);
|
|
192
|
+
if (!did_write) {
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
_disconnect_client(client_id, reason = 'disconnect') {
|
|
199
|
+
const client_entry = this.clients.get(client_id);
|
|
200
|
+
if (!client_entry) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.clients.delete(client_id);
|
|
205
|
+
const { req, res } = client_entry;
|
|
206
|
+
|
|
207
|
+
if (req) {
|
|
208
|
+
req.removeAllListeners('close');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
if (res && !res.writableEnded) {
|
|
213
|
+
res.end();
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
// Ignore disconnect write errors.
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.raise('client_disconnected', {
|
|
220
|
+
clientId: client_id,
|
|
221
|
+
reason,
|
|
222
|
+
timestamp: Date.now(),
|
|
223
|
+
connectedClients: this.client_count
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
this._maybe_stop_keepalive_timer();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
handle_http(req, res) {
|
|
230
|
+
const request_method = String(req.method || 'GET').toUpperCase();
|
|
231
|
+
if (request_method !== 'GET') {
|
|
232
|
+
res.writeHead(405, {
|
|
233
|
+
'Content-Type': 'application/json'
|
|
234
|
+
});
|
|
235
|
+
res.end(JSON.stringify({
|
|
236
|
+
ok: false,
|
|
237
|
+
error: 'Method Not Allowed'
|
|
238
|
+
}));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (this.client_count >= this.max_clients) {
|
|
243
|
+
res.writeHead(503, {
|
|
244
|
+
'Content-Type': 'application/json'
|
|
245
|
+
});
|
|
246
|
+
res.end(JSON.stringify({
|
|
247
|
+
ok: false,
|
|
248
|
+
error: 'SSE client limit reached'
|
|
249
|
+
}));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const parsed_url = this._parse_connection_url(req);
|
|
254
|
+
const requested_client_id = parsed_url.searchParams.get('clientId') || parsed_url.searchParams.get('client_id');
|
|
255
|
+
const client_id = requested_client_id || this._generate_client_id();
|
|
256
|
+
const last_event_id = this._extract_last_event_id(req, parsed_url);
|
|
257
|
+
|
|
258
|
+
if (this.clients.has(client_id)) {
|
|
259
|
+
this._disconnect_client(client_id, 'replaced');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
res.writeHead(200, {
|
|
263
|
+
'Content-Type': 'text/event-stream',
|
|
264
|
+
'Cache-Control': 'no-cache',
|
|
265
|
+
'Connection': 'keep-alive',
|
|
266
|
+
'Transfer-Encoding': 'chunked',
|
|
267
|
+
'X-Accel-Buffering': 'no'
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
res.write(`retry: 3000\n`);
|
|
271
|
+
res.write(':connected\n\n');
|
|
272
|
+
|
|
273
|
+
this.clients.set(client_id, {
|
|
274
|
+
client_id,
|
|
275
|
+
req,
|
|
276
|
+
res,
|
|
277
|
+
connected_at: Date.now(),
|
|
278
|
+
last_event_id
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
this._ensure_keepalive_timer();
|
|
282
|
+
|
|
283
|
+
this.raise('client_connected', {
|
|
284
|
+
clientId: client_id,
|
|
285
|
+
lastEventId: last_event_id,
|
|
286
|
+
timestamp: Date.now(),
|
|
287
|
+
connectedClients: this.client_count
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
this._replay_events_since(client_id, last_event_id);
|
|
291
|
+
|
|
292
|
+
const disconnect_handler = () => {
|
|
293
|
+
this._disconnect_client(client_id, 'closed');
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
req.on('close', disconnect_handler);
|
|
297
|
+
res.on('close', disconnect_handler);
|
|
298
|
+
res.on('finish', disconnect_handler);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
broadcast(event_name, data_value) {
|
|
302
|
+
const target_client_ids = Array.from(this.clients.keys());
|
|
303
|
+
return this._send_event_to_clients(target_client_ids, event_name, data_value);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
send(client_id, event_name, data_value) {
|
|
307
|
+
if (!this.clients.has(client_id)) {
|
|
308
|
+
return {
|
|
309
|
+
eventId: null,
|
|
310
|
+
deliveredClientIds: []
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return this._send_event_to_clients([client_id], event_name, data_value);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
stop(callback) {
|
|
318
|
+
const stop_promise = new Promise((resolve) => {
|
|
319
|
+
if (this._keepalive_timer) {
|
|
320
|
+
clearInterval(this._keepalive_timer);
|
|
321
|
+
this._keepalive_timer = null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const connected_client_ids = Array.from(this.clients.keys());
|
|
325
|
+
for (const client_id of connected_client_ids) {
|
|
326
|
+
this._disconnect_client(client_id, 'stopped');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
resolve(true);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (typeof callback === 'function') {
|
|
333
|
+
stop_promise.then(() => callback(null, true), callback);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return stop_promise;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
module.exports = HTTP_SSE_Publisher;
|
|
@@ -72,9 +72,19 @@ class HTTP_Webpage_Publisher extends HTTP_Webpageorsite_Publisher {
|
|
|
72
72
|
bundler_config: this.bundler_config
|
|
73
73
|
});
|
|
74
74
|
(async () => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
try {
|
|
76
|
+
const res_get_ready = await this.get_ready();
|
|
77
|
+
this.raise('ready', res_get_ready);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.error('HTTP_Webpage_Publisher error: Failed to get ready (bundling or preparation failed).');
|
|
80
|
+
console.error(e);
|
|
81
|
+
// Can't just ignore it if it means the server won't handle requests correctly,
|
|
82
|
+
// but at least it won't crash the whole process.
|
|
83
|
+
// We might want to emit an error event.
|
|
84
|
+
this.raise('error', e);
|
|
85
|
+
// Also raise 'ready' so the pool/server can continue starting up other things
|
|
86
|
+
this.raise('ready', {});
|
|
87
|
+
}
|
|
78
88
|
})();
|
|
79
89
|
|
|
80
90
|
}
|
|
@@ -162,6 +162,24 @@ class HTTP_Webpageorsite_Publisher extends HTTP_Publisher {
|
|
|
162
162
|
|
|
163
163
|
console.log('[HTTP_Webpageorsite_Publisher] get_ready called, src_path_client_js:', src_path_client_js);
|
|
164
164
|
|
|
165
|
+
// Defensive programming: Handle missing client JS path
|
|
166
|
+
if (!src_path_client_js) {
|
|
167
|
+
console.warn('[HTTP_Webpageorsite_Publisher] No src_path_client_js provided. Returning empty bundle.');
|
|
168
|
+
const bundle = new Bundle();
|
|
169
|
+
// Could add empty CSS/JS placeholders if needed to prevent errors downstream
|
|
170
|
+
bundle.push({
|
|
171
|
+
type: 'CSS',
|
|
172
|
+
extension: 'css',
|
|
173
|
+
text: '/* No client CSS - Server Start Mode */'
|
|
174
|
+
});
|
|
175
|
+
bundle.push({
|
|
176
|
+
type: 'JavaScript',
|
|
177
|
+
extension: 'js',
|
|
178
|
+
text: '/* No client JS - Server Start Mode */'
|
|
179
|
+
});
|
|
180
|
+
return bundle;
|
|
181
|
+
}
|
|
182
|
+
|
|
165
183
|
// Skip bundling if no client.js path is provided
|
|
166
184
|
// This allows Server.serve() to work with SSR-only controls
|
|
167
185
|
if (!src_path_client_js) {
|