redweb 0.13.5 → 0.14.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.
- package/CHANGELOG.md +8 -1
- package/README.md +20 -32
- package/docs/APPLICATION.md +98 -0
- package/docs/CLI.md +1 -1
- package/docs/COVERAGE_SCOPE_AUDIT.md +7 -0
- package/docs/DEFINE_APP_VERIFICATION.md +101 -0
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/LIVE_HTML.md +2 -2
- package/docs/MIGRATION.md +1 -1
- package/docs/MULTIPLAYER_OPERATIONS.md +6 -0
- package/docs/RELEASE_TRUST.md +4 -4
- package/docs/RUNTIME_DIAGNOSTICS.md +1 -1
- package/docs/SOCKET_CONTRACTS.md +2 -2
- package/docs/STARTER_LIFECYCLE_VERIFICATION.md +9 -0
- package/docs/generated.json +325 -271
- package/docs/guides/http-websocket.md +4 -4
- package/docs/guides/jsx-without-react.md +1 -1
- package/docs/reference.json +40 -1
- package/docs/releases/0.14.0.json +2208 -0
- package/docs/topics.json +1 -0
- package/examples/live-html/cards.js +87 -86
- package/examples/live-html/cards.ts +3 -2
- package/examples/live-html/chatroom.js +210 -207
- package/examples/live-html/chatroom.tsx +6 -2
- package/examples/live-html/components.js +103 -102
- package/examples/live-html/components.ts +3 -2
- package/examples/live-html/counter.js +74 -73
- package/examples/live-html/counter.ts +3 -2
- package/examples/live-html/jsx-page.js +2 -1
- package/examples/live-html/jsx-page.tsx +3 -2
- package/index.d.ts +53 -5
- package/index.js +3 -0
- package/package.json +3 -3
- package/recipes/chat/app.test.cjs +3 -1
- package/recipes/chat/app.tsx +4 -7
- package/recipes/dashboard/app.test.cjs +10 -10
- package/recipes/dashboard/app.tsx +29 -29
- package/recipes/http-ws/README.md +1 -1
- package/recipes/http-ws/app.test.cjs +8 -10
- package/recipes/http-ws/app.tsx +9 -20
- package/recipes/realtime/app.tsx +3 -6
- package/recipes/shared/README.md +3 -1
- package/recipes/shared/lifecycle.test.cjs +81 -0
- package/recipes/shared/network.cjs +10 -5
- package/recipes/site/app.tsx +3 -6
- package/recipes/socket/app.tsx +3 -10
- package/src/Application.js +239 -0
- package/src/StartupCleanup.js +24 -0
- package/src/cli/SourceInspector.js +5 -0
- package/src/cli/templates.js +3 -4
- package/src/htmx/LiveHtmlServer.js +14 -5
- package/src/htmx/PageManager.js +3 -1
- package/src/ws/BaseSocketServer.js +13 -13
- package/src/ws/SocketRoute.js +6 -2
- package/recipes/shared/run-app.test.cjs +0 -158
- package/recipes/shared/run-app.ts +0 -50
|
@@ -1,207 +1,210 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
3
|
-
var useValue = arguments.length > 2;
|
|
4
|
-
for (var i = 0; i < initializers.length; i++) {
|
|
5
|
-
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
6
|
-
}
|
|
7
|
-
return useValue ? value : void 0;
|
|
8
|
-
};
|
|
9
|
-
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
10
|
-
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
11
|
-
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
12
|
-
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
13
|
-
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
14
|
-
var _, done = false;
|
|
15
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
16
|
-
var context = {};
|
|
17
|
-
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
18
|
-
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
19
|
-
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
20
|
-
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
21
|
-
if (kind === "accessor") {
|
|
22
|
-
if (result === void 0) continue;
|
|
23
|
-
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
24
|
-
if (_ = accept(result.get)) descriptor.get = _;
|
|
25
|
-
if (_ = accept(result.set)) descriptor.set = _;
|
|
26
|
-
if (_ = accept(result.init)) initializers.unshift(_);
|
|
27
|
-
}
|
|
28
|
-
else if (_ = accept(result)) {
|
|
29
|
-
if (kind === "field") initializers.unshift(_);
|
|
30
|
-
else descriptor[key] = _;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
34
|
-
done = true;
|
|
35
|
-
};
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.ChatroomComponent = exports.chatInputs = void 0;
|
|
38
|
-
exports.createChatroomPage = createChatroomPage;
|
|
39
|
-
const jsx_runtime_1 = require('../../jsx-runtime');
|
|
40
|
-
const redweb_1 = require('../..');
|
|
41
|
-
const zod_1 = require("zod");
|
|
42
|
-
const MAX_VISIBLE_MEMBERS = 100;
|
|
43
|
-
const visibleText = (maximum) => zod_1.z.string()
|
|
44
|
-
.transform(value => value.normalize('NFKC').trim())
|
|
45
|
-
.pipe(zod_1.z.string().min(1).max(maximum).regex(/^[^\p{Cc}\p{Cf}]+$/u));
|
|
46
|
-
exports.chatInputs = {
|
|
47
|
-
join: zod_1.z.object({ name: visibleText(40) }).strict(),
|
|
48
|
-
send: zod_1.z.object({ message: visibleText(500) }).strict(),
|
|
49
|
-
};
|
|
50
|
-
class ChatRoom {
|
|
51
|
-
history = [];
|
|
52
|
-
nextMessageId = 0;
|
|
53
|
-
participants = new Set();
|
|
54
|
-
online = new Set();
|
|
55
|
-
join(participant) {
|
|
56
|
-
const name = participant.displayName.toLocaleLowerCase();
|
|
57
|
-
if ([...this.participants].some(member => member !== participant && member.displayName.toLocaleLowerCase() === name))
|
|
58
|
-
return false;
|
|
59
|
-
this.participants.add(participant);
|
|
60
|
-
this.online.add(participant);
|
|
61
|
-
participant.updateMessages(this.history);
|
|
62
|
-
this.publishPresence();
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
disconnect(participant) {
|
|
66
|
-
if (this.online.delete(participant))
|
|
67
|
-
this.publishPresence();
|
|
68
|
-
}
|
|
69
|
-
leave(participant) {
|
|
70
|
-
this.online.delete(participant);
|
|
71
|
-
if (this.participants.delete(participant))
|
|
72
|
-
this.publishPresence();
|
|
73
|
-
}
|
|
74
|
-
send(participant, text) {
|
|
75
|
-
if (!this.online.has(participant))
|
|
76
|
-
return false;
|
|
77
|
-
this.history = [...this.history, { id: ++this.nextMessageId, sender: participant.displayName, text }].slice(-100);
|
|
78
|
-
for (const member of this.participants)
|
|
79
|
-
member.updateMessages(this.history);
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
publishPresence() {
|
|
83
|
-
const members = [...this.online].map(participant => participant.displayName);
|
|
84
|
-
for (const participant of this.participants)
|
|
85
|
-
participant.updatePresence(members);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
let ChatroomComponent = (() => {
|
|
89
|
-
let _classDecorators = [(0, redweb_1.component)()];
|
|
90
|
-
let _classDescriptor;
|
|
91
|
-
let _classExtraInitializers = [];
|
|
92
|
-
let _classThis;
|
|
93
|
-
let _instanceExtraInitializers = [];
|
|
94
|
-
let _displayName_decorators;
|
|
95
|
-
let _displayName_initializers = [];
|
|
96
|
-
let _displayName_extraInitializers = [];
|
|
97
|
-
let _feedback_decorators;
|
|
98
|
-
let _feedback_initializers = [];
|
|
99
|
-
let _feedback_extraInitializers = [];
|
|
100
|
-
let _messages_decorators;
|
|
101
|
-
let _messages_initializers = [];
|
|
102
|
-
let _messages_extraInitializers = [];
|
|
103
|
-
let _members_decorators;
|
|
104
|
-
let _members_initializers = [];
|
|
105
|
-
let _members_extraInitializers = [];
|
|
106
|
-
let _join_decorators;
|
|
107
|
-
let _send_decorators;
|
|
108
|
-
let _leave_decorators;
|
|
109
|
-
var ChatroomComponent = class {
|
|
110
|
-
static { _classThis = this; }
|
|
111
|
-
static {
|
|
112
|
-
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
113
|
-
_displayName_decorators = [(0, redweb_1.state)()];
|
|
114
|
-
_feedback_decorators = [(0, redweb_1.state)()];
|
|
115
|
-
_messages_decorators = [(0, redweb_1.state)()];
|
|
116
|
-
_members_decorators = [(0, redweb_1.state)()];
|
|
117
|
-
_join_decorators = [(0, redweb_1.action)({ input: exports.chatInputs.join })];
|
|
118
|
-
_send_decorators = [(0, redweb_1.action)({ input: exports.chatInputs.send })];
|
|
119
|
-
_leave_decorators = [(0, redweb_1.action)()];
|
|
120
|
-
__esDecorate(this, null, _join_decorators, { kind: "method", name: "join", static: false, private: false, access: { has: obj => "join" in obj, get: obj => obj.join }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
121
|
-
__esDecorate(this, null, _send_decorators, { kind: "method", name: "send", static: false, private: false, access: { has: obj => "send" in obj, get: obj => obj.send }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
122
|
-
__esDecorate(this, null, _leave_decorators, { kind: "method", name: "leave", static: false, private: false, access: { has: obj => "leave" in obj, get: obj => obj.leave }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
123
|
-
__esDecorate(null, null, _displayName_decorators, { kind: "field", name: "displayName", static: false, private: false, access: { has: obj => "displayName" in obj, get: obj => obj.displayName, set: (obj, value) => { obj.displayName = value; } }, metadata: _metadata }, _displayName_initializers, _displayName_extraInitializers);
|
|
124
|
-
__esDecorate(null, null, _feedback_decorators, { kind: "field", name: "feedback", static: false, private: false, access: { has: obj => "feedback" in obj, get: obj => obj.feedback, set: (obj, value) => { obj.feedback = value; } }, metadata: _metadata }, _feedback_initializers, _feedback_extraInitializers);
|
|
125
|
-
__esDecorate(null, null, _messages_decorators, { kind: "field", name: "messages", static: false, private: false, access: { has: obj => "messages" in obj, get: obj => obj.messages, set: (obj, value) => { obj.messages = value; } }, metadata: _metadata }, _messages_initializers, _messages_extraInitializers);
|
|
126
|
-
__esDecorate(null, null, _members_decorators, { kind: "field", name: "members", static: false, private: false, access: { has: obj => "members" in obj, get: obj => obj.members, set: (obj, value) => { obj.members = value; } }, metadata: _metadata }, _members_initializers, _members_extraInitializers);
|
|
127
|
-
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
128
|
-
ChatroomComponent = _classThis = _classDescriptor.value;
|
|
129
|
-
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
130
|
-
__runInitializers(_classThis, _classExtraInitializers);
|
|
131
|
-
}
|
|
132
|
-
room = __runInitializers(this, _instanceExtraInitializers);
|
|
133
|
-
displayName = __runInitializers(this, _displayName_initializers, '');
|
|
134
|
-
feedback = (__runInitializers(this, _displayName_extraInitializers), __runInitializers(this, _feedback_initializers, ''));
|
|
135
|
-
messages = (__runInitializers(this, _feedback_extraInitializers), __runInitializers(this, _messages_initializers, []));
|
|
136
|
-
members = (__runInitializers(this, _messages_extraInitializers), __runInitializers(this, _members_initializers, []));
|
|
137
|
-
constructor(room) {
|
|
138
|
-
__runInitializers(this, _members_extraInitializers);
|
|
139
|
-
this.room = room;
|
|
140
|
-
}
|
|
141
|
-
connected() { if (this.displayName)
|
|
142
|
-
this.room.join(this); }
|
|
143
|
-
disconnected() { this.room.disconnect(this); }
|
|
144
|
-
disposed() { this.room.leave(this); }
|
|
145
|
-
join({ name }) {
|
|
146
|
-
if (this.displayName)
|
|
147
|
-
return false;
|
|
148
|
-
this.displayName = name;
|
|
149
|
-
if (!this.room.join(this)) {
|
|
150
|
-
this.displayName = '';
|
|
151
|
-
this.feedback = 'That display name is already in use.';
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
this.feedback = '';
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
send({ message }) {
|
|
158
|
-
return this.room.send(this, message);
|
|
159
|
-
}
|
|
160
|
-
leave() {
|
|
161
|
-
this.room.leave(this);
|
|
162
|
-
this.displayName = '';
|
|
163
|
-
this.feedback = '';
|
|
164
|
-
this.messages = [];
|
|
165
|
-
this.members = [];
|
|
166
|
-
}
|
|
167
|
-
updateMessages(messages) { this.messages = messages; }
|
|
168
|
-
updatePresence(members) { this.members = members; }
|
|
169
|
-
render() {
|
|
170
|
-
return (0, jsx_runtime_1.jsx)("section", { class: "chatroom", children: this.displayName ? this.roomScreen() : this.joinScreen() });
|
|
171
|
-
}
|
|
172
|
-
joinScreen() {
|
|
173
|
-
return ((0, jsx_runtime_1.jsxs)("section", { class: "join-panel", children: [(0, jsx_runtime_1.jsx)("p", { class: "eyebrow", children: "Live room" }), (0, jsx_runtime_1.jsx)("h1", { children: "Join the chatroom" }), (0, jsx_runtime_1.jsx)("p", { children: "Choose a name once, then chat in realtime with everyone currently in the room." }), this.feedback && (0, jsx_runtime_1.jsx)("p", { class: "form-error", role: "alert", children: this.feedback }), (0, jsx_runtime_1.jsxs)("form", { "rw-submit": "join", class: "join-form", children: [(0, jsx_runtime_1.jsx)("label", { for: "display-name", children: "Display name" }), (0, jsx_runtime_1.jsxs)("div", { class: "input-row", children: [(0, jsx_runtime_1.jsx)("input", { id: "display-name", name: "name", maxlength: "40", autocomplete: "nickname", required: true, autofocus: true }), (0, jsx_runtime_1.jsx)("button", { type: "submit", children: "Join room" })] })] })] }));
|
|
174
|
-
}
|
|
175
|
-
roomScreen() {
|
|
176
|
-
const remaining = this.members.length - MAX_VISIBLE_MEMBERS;
|
|
177
|
-
return ((0, jsx_runtime_1.jsxs)("div", { class: "room-layout", children: [(0, jsx_runtime_1.jsxs)("section", { class: "conversation", children: [(0, jsx_runtime_1.jsxs)("header", { class: "room-header", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { class: "eyebrow", children: "Connected as" }), (0, jsx_runtime_1.jsx)("h1", { children: this.displayName })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", class: "quiet-button", "rw-click": "leave", children: "Leave" })] }), (0, jsx_runtime_1.jsx)("ol", { class: "message-list", "aria-live": "polite", children: this.messages.length ? this.messages.map(entry => ((0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: entry.sender }), (0, jsx_runtime_1.jsx)("p", { children: entry.text })] }, entry.id))) : (0, jsx_runtime_1.jsx)("li", { class: "empty-message", children: "No messages yet. Say hello." }) }), (0, jsx_runtime_1.jsxs)("form", { "rw-submit": "send", class: "composer", children: [(0, jsx_runtime_1.jsx)("label", { class: "sr-only", for: "chat-message", children: "Message" }), (0, jsx_runtime_1.jsx)("input", { id: "chat-message", name: "message", maxlength: "500", autocomplete: "off", placeholder: "Message the room\u2026", required: true, autofocus: true }), (0, jsx_runtime_1.jsx)("button", { type: "submit", children: "Send" })] })] }), (0, jsx_runtime_1.jsxs)("aside", { class: "presence", "aria-label": "People in the room", children: [(0, jsx_runtime_1.jsxs)("p", { class: "eyebrow", children: ["Online \u00B7 ", this.members.length] }), (0, jsx_runtime_1.jsxs)("ul", { children: [this.members.slice(0, MAX_VISIBLE_MEMBERS).map(member => (0, jsx_runtime_1.jsx)("li", { children: member }, member)), remaining > 0 && (0, jsx_runtime_1.jsxs)("li", { class: "more-members", children: ["+", remaining, " more"] })] })] })] }));
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
return ChatroomComponent = _classThis;
|
|
181
|
-
})();
|
|
182
|
-
exports.ChatroomComponent = ChatroomComponent;
|
|
183
|
-
function createChatroomPage() {
|
|
184
|
-
const room = new ChatRoom();
|
|
185
|
-
let ChatroomPage = (() => {
|
|
186
|
-
let _classDecorators = [(0, redweb_1.page)('/', { css: 'chatroom.css' })];
|
|
187
|
-
let _classDescriptor;
|
|
188
|
-
let _classExtraInitializers = [];
|
|
189
|
-
let _classThis;
|
|
190
|
-
var ChatroomPage = class {
|
|
191
|
-
static { _classThis = this; }
|
|
192
|
-
static {
|
|
193
|
-
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
194
|
-
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
195
|
-
ChatroomPage = _classThis = _classDescriptor.value;
|
|
196
|
-
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
197
|
-
__runInitializers(_classThis, _classExtraInitializers);
|
|
198
|
-
}
|
|
199
|
-
chat = new ChatroomComponent(room);
|
|
200
|
-
render() { return (0, jsx_runtime_1.jsx)("main", { children: this.chat }); }
|
|
201
|
-
};
|
|
202
|
-
return ChatroomPage = _classThis;
|
|
203
|
-
})();
|
|
204
|
-
return ChatroomPage;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
3
|
+
var useValue = arguments.length > 2;
|
|
4
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
5
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
6
|
+
}
|
|
7
|
+
return useValue ? value : void 0;
|
|
8
|
+
};
|
|
9
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
10
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
11
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
12
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
13
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
14
|
+
var _, done = false;
|
|
15
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
16
|
+
var context = {};
|
|
17
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
18
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
19
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
20
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
21
|
+
if (kind === "accessor") {
|
|
22
|
+
if (result === void 0) continue;
|
|
23
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
24
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
25
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
26
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
27
|
+
}
|
|
28
|
+
else if (_ = accept(result)) {
|
|
29
|
+
if (kind === "field") initializers.unshift(_);
|
|
30
|
+
else descriptor[key] = _;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
34
|
+
done = true;
|
|
35
|
+
};
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.ChatroomPage = exports.ChatroomComponent = exports.chatInputs = void 0;
|
|
38
|
+
exports.createChatroomPage = createChatroomPage;
|
|
39
|
+
const jsx_runtime_1 = require('../../jsx-runtime');
|
|
40
|
+
const redweb_1 = require('../..');
|
|
41
|
+
const zod_1 = require("zod");
|
|
42
|
+
const MAX_VISIBLE_MEMBERS = 100;
|
|
43
|
+
const visibleText = (maximum) => zod_1.z.string()
|
|
44
|
+
.transform(value => value.normalize('NFKC').trim())
|
|
45
|
+
.pipe(zod_1.z.string().min(1).max(maximum).regex(/^[^\p{Cc}\p{Cf}]+$/u));
|
|
46
|
+
exports.chatInputs = {
|
|
47
|
+
join: zod_1.z.object({ name: visibleText(40) }).strict(),
|
|
48
|
+
send: zod_1.z.object({ message: visibleText(500) }).strict(),
|
|
49
|
+
};
|
|
50
|
+
class ChatRoom {
|
|
51
|
+
history = [];
|
|
52
|
+
nextMessageId = 0;
|
|
53
|
+
participants = new Set();
|
|
54
|
+
online = new Set();
|
|
55
|
+
join(participant) {
|
|
56
|
+
const name = participant.displayName.toLocaleLowerCase();
|
|
57
|
+
if ([...this.participants].some(member => member !== participant && member.displayName.toLocaleLowerCase() === name))
|
|
58
|
+
return false;
|
|
59
|
+
this.participants.add(participant);
|
|
60
|
+
this.online.add(participant);
|
|
61
|
+
participant.updateMessages(this.history);
|
|
62
|
+
this.publishPresence();
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
disconnect(participant) {
|
|
66
|
+
if (this.online.delete(participant))
|
|
67
|
+
this.publishPresence();
|
|
68
|
+
}
|
|
69
|
+
leave(participant) {
|
|
70
|
+
this.online.delete(participant);
|
|
71
|
+
if (this.participants.delete(participant))
|
|
72
|
+
this.publishPresence();
|
|
73
|
+
}
|
|
74
|
+
send(participant, text) {
|
|
75
|
+
if (!this.online.has(participant))
|
|
76
|
+
return false;
|
|
77
|
+
this.history = [...this.history, { id: ++this.nextMessageId, sender: participant.displayName, text }].slice(-100);
|
|
78
|
+
for (const member of this.participants)
|
|
79
|
+
member.updateMessages(this.history);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
publishPresence() {
|
|
83
|
+
const members = [...this.online].map(participant => participant.displayName);
|
|
84
|
+
for (const participant of this.participants)
|
|
85
|
+
participant.updatePresence(members);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
let ChatroomComponent = (() => {
|
|
89
|
+
let _classDecorators = [(0, redweb_1.component)()];
|
|
90
|
+
let _classDescriptor;
|
|
91
|
+
let _classExtraInitializers = [];
|
|
92
|
+
let _classThis;
|
|
93
|
+
let _instanceExtraInitializers = [];
|
|
94
|
+
let _displayName_decorators;
|
|
95
|
+
let _displayName_initializers = [];
|
|
96
|
+
let _displayName_extraInitializers = [];
|
|
97
|
+
let _feedback_decorators;
|
|
98
|
+
let _feedback_initializers = [];
|
|
99
|
+
let _feedback_extraInitializers = [];
|
|
100
|
+
let _messages_decorators;
|
|
101
|
+
let _messages_initializers = [];
|
|
102
|
+
let _messages_extraInitializers = [];
|
|
103
|
+
let _members_decorators;
|
|
104
|
+
let _members_initializers = [];
|
|
105
|
+
let _members_extraInitializers = [];
|
|
106
|
+
let _join_decorators;
|
|
107
|
+
let _send_decorators;
|
|
108
|
+
let _leave_decorators;
|
|
109
|
+
var ChatroomComponent = class {
|
|
110
|
+
static { _classThis = this; }
|
|
111
|
+
static {
|
|
112
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
113
|
+
_displayName_decorators = [(0, redweb_1.state)()];
|
|
114
|
+
_feedback_decorators = [(0, redweb_1.state)()];
|
|
115
|
+
_messages_decorators = [(0, redweb_1.state)()];
|
|
116
|
+
_members_decorators = [(0, redweb_1.state)()];
|
|
117
|
+
_join_decorators = [(0, redweb_1.action)({ input: exports.chatInputs.join })];
|
|
118
|
+
_send_decorators = [(0, redweb_1.action)({ input: exports.chatInputs.send })];
|
|
119
|
+
_leave_decorators = [(0, redweb_1.action)()];
|
|
120
|
+
__esDecorate(this, null, _join_decorators, { kind: "method", name: "join", static: false, private: false, access: { has: obj => "join" in obj, get: obj => obj.join }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
121
|
+
__esDecorate(this, null, _send_decorators, { kind: "method", name: "send", static: false, private: false, access: { has: obj => "send" in obj, get: obj => obj.send }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
122
|
+
__esDecorate(this, null, _leave_decorators, { kind: "method", name: "leave", static: false, private: false, access: { has: obj => "leave" in obj, get: obj => obj.leave }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
123
|
+
__esDecorate(null, null, _displayName_decorators, { kind: "field", name: "displayName", static: false, private: false, access: { has: obj => "displayName" in obj, get: obj => obj.displayName, set: (obj, value) => { obj.displayName = value; } }, metadata: _metadata }, _displayName_initializers, _displayName_extraInitializers);
|
|
124
|
+
__esDecorate(null, null, _feedback_decorators, { kind: "field", name: "feedback", static: false, private: false, access: { has: obj => "feedback" in obj, get: obj => obj.feedback, set: (obj, value) => { obj.feedback = value; } }, metadata: _metadata }, _feedback_initializers, _feedback_extraInitializers);
|
|
125
|
+
__esDecorate(null, null, _messages_decorators, { kind: "field", name: "messages", static: false, private: false, access: { has: obj => "messages" in obj, get: obj => obj.messages, set: (obj, value) => { obj.messages = value; } }, metadata: _metadata }, _messages_initializers, _messages_extraInitializers);
|
|
126
|
+
__esDecorate(null, null, _members_decorators, { kind: "field", name: "members", static: false, private: false, access: { has: obj => "members" in obj, get: obj => obj.members, set: (obj, value) => { obj.members = value; } }, metadata: _metadata }, _members_initializers, _members_extraInitializers);
|
|
127
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
128
|
+
ChatroomComponent = _classThis = _classDescriptor.value;
|
|
129
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
130
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
131
|
+
}
|
|
132
|
+
room = __runInitializers(this, _instanceExtraInitializers);
|
|
133
|
+
displayName = __runInitializers(this, _displayName_initializers, '');
|
|
134
|
+
feedback = (__runInitializers(this, _displayName_extraInitializers), __runInitializers(this, _feedback_initializers, ''));
|
|
135
|
+
messages = (__runInitializers(this, _feedback_extraInitializers), __runInitializers(this, _messages_initializers, []));
|
|
136
|
+
members = (__runInitializers(this, _messages_extraInitializers), __runInitializers(this, _members_initializers, []));
|
|
137
|
+
constructor(room) {
|
|
138
|
+
__runInitializers(this, _members_extraInitializers);
|
|
139
|
+
this.room = room;
|
|
140
|
+
}
|
|
141
|
+
connected() { if (this.displayName)
|
|
142
|
+
this.room.join(this); }
|
|
143
|
+
disconnected() { this.room.disconnect(this); }
|
|
144
|
+
disposed() { this.room.leave(this); }
|
|
145
|
+
join({ name }) {
|
|
146
|
+
if (this.displayName)
|
|
147
|
+
return false;
|
|
148
|
+
this.displayName = name;
|
|
149
|
+
if (!this.room.join(this)) {
|
|
150
|
+
this.displayName = '';
|
|
151
|
+
this.feedback = 'That display name is already in use.';
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
this.feedback = '';
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
send({ message }) {
|
|
158
|
+
return this.room.send(this, message);
|
|
159
|
+
}
|
|
160
|
+
leave() {
|
|
161
|
+
this.room.leave(this);
|
|
162
|
+
this.displayName = '';
|
|
163
|
+
this.feedback = '';
|
|
164
|
+
this.messages = [];
|
|
165
|
+
this.members = [];
|
|
166
|
+
}
|
|
167
|
+
updateMessages(messages) { this.messages = messages; }
|
|
168
|
+
updatePresence(members) { this.members = members; }
|
|
169
|
+
render() {
|
|
170
|
+
return (0, jsx_runtime_1.jsx)("section", { class: "chatroom", children: this.displayName ? this.roomScreen() : this.joinScreen() });
|
|
171
|
+
}
|
|
172
|
+
joinScreen() {
|
|
173
|
+
return ((0, jsx_runtime_1.jsxs)("section", { class: "join-panel", children: [(0, jsx_runtime_1.jsx)("p", { class: "eyebrow", children: "Live room" }), (0, jsx_runtime_1.jsx)("h1", { children: "Join the chatroom" }), (0, jsx_runtime_1.jsx)("p", { children: "Choose a name once, then chat in realtime with everyone currently in the room." }), this.feedback && (0, jsx_runtime_1.jsx)("p", { class: "form-error", role: "alert", children: this.feedback }), (0, jsx_runtime_1.jsxs)("form", { "rw-submit": "join", class: "join-form", children: [(0, jsx_runtime_1.jsx)("label", { for: "display-name", children: "Display name" }), (0, jsx_runtime_1.jsxs)("div", { class: "input-row", children: [(0, jsx_runtime_1.jsx)("input", { id: "display-name", name: "name", maxlength: "40", autocomplete: "nickname", required: true, autofocus: true }), (0, jsx_runtime_1.jsx)("button", { type: "submit", children: "Join room" })] })] })] }));
|
|
174
|
+
}
|
|
175
|
+
roomScreen() {
|
|
176
|
+
const remaining = this.members.length - MAX_VISIBLE_MEMBERS;
|
|
177
|
+
return ((0, jsx_runtime_1.jsxs)("div", { class: "room-layout", children: [(0, jsx_runtime_1.jsxs)("section", { class: "conversation", children: [(0, jsx_runtime_1.jsxs)("header", { class: "room-header", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { class: "eyebrow", children: "Connected as" }), (0, jsx_runtime_1.jsx)("h1", { children: this.displayName })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", class: "quiet-button", "rw-click": "leave", children: "Leave" })] }), (0, jsx_runtime_1.jsx)("ol", { class: "message-list", "aria-live": "polite", children: this.messages.length ? this.messages.map(entry => ((0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: entry.sender }), (0, jsx_runtime_1.jsx)("p", { children: entry.text })] }, entry.id))) : (0, jsx_runtime_1.jsx)("li", { class: "empty-message", children: "No messages yet. Say hello." }) }), (0, jsx_runtime_1.jsxs)("form", { "rw-submit": "send", class: "composer", children: [(0, jsx_runtime_1.jsx)("label", { class: "sr-only", for: "chat-message", children: "Message" }), (0, jsx_runtime_1.jsx)("input", { id: "chat-message", name: "message", maxlength: "500", autocomplete: "off", placeholder: "Message the room\u2026", required: true, autofocus: true }), (0, jsx_runtime_1.jsx)("button", { type: "submit", children: "Send" })] })] }), (0, jsx_runtime_1.jsxs)("aside", { class: "presence", "aria-label": "People in the room", children: [(0, jsx_runtime_1.jsxs)("p", { class: "eyebrow", children: ["Online \u00B7 ", this.members.length] }), (0, jsx_runtime_1.jsxs)("ul", { children: [this.members.slice(0, MAX_VISIBLE_MEMBERS).map(member => (0, jsx_runtime_1.jsx)("li", { children: member }, member)), remaining > 0 && (0, jsx_runtime_1.jsxs)("li", { class: "more-members", children: ["+", remaining, " more"] })] })] })] }));
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
return ChatroomComponent = _classThis;
|
|
181
|
+
})();
|
|
182
|
+
exports.ChatroomComponent = ChatroomComponent;
|
|
183
|
+
function createChatroomPage() {
|
|
184
|
+
const room = new ChatRoom();
|
|
185
|
+
let ChatroomPage = (() => {
|
|
186
|
+
let _classDecorators = [(0, redweb_1.page)('/', { css: 'chatroom.css' })];
|
|
187
|
+
let _classDescriptor;
|
|
188
|
+
let _classExtraInitializers = [];
|
|
189
|
+
let _classThis;
|
|
190
|
+
var ChatroomPage = class {
|
|
191
|
+
static { _classThis = this; }
|
|
192
|
+
static {
|
|
193
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
194
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
195
|
+
ChatroomPage = _classThis = _classDescriptor.value;
|
|
196
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
197
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
198
|
+
}
|
|
199
|
+
chat = new ChatroomComponent(room);
|
|
200
|
+
render() { return (0, jsx_runtime_1.jsx)("main", { children: this.chat }); }
|
|
201
|
+
};
|
|
202
|
+
return ChatroomPage = _classThis;
|
|
203
|
+
})();
|
|
204
|
+
return ChatroomPage;
|
|
205
|
+
}
|
|
206
|
+
/** Default room; the factory remains available when independent rooms are needed. */
|
|
207
|
+
exports.ChatroomPage = createChatroomPage();
|
|
208
|
+
if (require.main === module)
|
|
209
|
+
void (0, redweb_1.defineApp)({ pages: [exports.ChatroomPage], port: 8080 }).run()
|
|
210
|
+
.catch(error => { console.error(error); process.exitCode = 1; });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { action, component,
|
|
1
|
+
import { action, component, defineApp, page, state, type ActionInput } from 'redweb';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
const MAX_VISIBLE_MEMBERS = 100;
|
|
@@ -164,4 +164,8 @@ export function createChatroomPage() {
|
|
|
164
164
|
return ChatroomPage;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
/** Default room; the factory remains available when independent rooms are needed. */
|
|
168
|
+
export const ChatroomPage = createChatroomPage();
|
|
169
|
+
|
|
170
|
+
if (require.main === module) void defineApp({ pages: [ChatroomPage], port: 8080 }).run()
|
|
171
|
+
.catch(error => { console.error(error); process.exitCode = 1; });
|