@effect.healthcare/messenger 0.5.0 → 0.6.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,139 +0,0 @@
1
- /**
2
- * @module Component
3
- */
4
-
5
- /**
6
- * @category Configuration
7
- */
8
- export const Tag = "app-messenger" as const;
9
-
10
- /**
11
- * Local storage key under which the session token is stored (and renewed) by
12
- * the License component. Externalized so the contract lives in one place.
13
- * @category Configuration
14
- */
15
- export const Session = "app-license.session" as const;
16
-
17
- /**
18
- * Local storage key under which the endpoint host base URL is cached by the
19
- * Configurator component. Composed with the `path` state to form the negotiate URL.
20
- * @category Configuration
21
- */
22
- export const Endpoint = "data" as const;
23
-
24
- /**
25
- * The Azure Web PubSub subprotocol required for structured group messaging.
26
- * @category Configuration
27
- */
28
- export const Protocol = "json.webpubsub.azure.v1" as const;
29
-
30
- /**
31
- * Inbound message types the messenger reacts to (the `data.type` of a frame).
32
- * @category Metadata
33
- * @enum
34
- */
35
- export const Message = {
36
- MASTER_DATA_CHANGED: "masterData.changed",
37
- } as const;
38
-
39
- /**
40
- * Inbound message types the messenger reacts to.
41
- * @category Metadata
42
- */
43
- export type Message = (typeof Message)[keyof typeof Message];
44
-
45
- /**
46
- * HTML Attributes available to set
47
- * @category Metadata
48
- * @enum
49
- */
50
- export const Attributes = {
51
- CONNECTION: "connection",
52
- PATH: "path",
53
- } as const;
54
- /**
55
- * HTML Attributes available to set
56
- * @category Metadata
57
- */
58
- export type Attributes = typeof Attributes;
59
-
60
- /**
61
- * HTML Attributes available to set
62
- * @category Metadata
63
- * @enum
64
- */
65
- export const State = {
66
- ...Attributes,
67
- } as const;
68
-
69
- /**
70
- * HTML Attributes available to set
71
- * @category Metadata
72
- */
73
- export type State = (typeof State)[keyof typeof State];
74
-
75
- /**
76
- * Live connection status value domain
77
- * @category Metadata
78
- * @enum
79
- */
80
- export const Connection = {
81
- DISCONNECTED: "disconnected",
82
- CONNECTING: "connecting",
83
- CONNECTED: "connected",
84
- } as const;
85
-
86
- /**
87
- * Live connection status value domain
88
- * @category Metadata
89
- */
90
- export type Connection = (typeof Connection)[keyof typeof Connection];
91
-
92
- /**
93
- * The negotiate request path value domain
94
- * @category Metadata
95
- */
96
- export type Path = string;
97
-
98
- /**
99
- * @category Metadata
100
- * @enum
101
- */
102
- export const Operation = {
103
- CONNECT: "connect",
104
- DISCONNECT: "disconnect",
105
- SEND: "send",
106
- } as const;
107
-
108
- /**
109
- * @category Metadata
110
- */
111
- export type Operation = (typeof Operation)[keyof typeof Operation];
112
-
113
- /**
114
- * @category Metadata
115
- * @enum
116
- */
117
- export const Event = {
118
- ON_PATH_CHANGE: "onpathchange",
119
- ON_CONNECTING: "onconnecting",
120
- ON_CONNECTED: "onconnected",
121
- ON_DISCONNECTED: "ondisconnected",
122
- ON_MASTERDATACHANGE: "onmasterdatachange",
123
- } as const;
124
- /**
125
- * @category Metadata
126
- */
127
- export type Event = (typeof Event)[keyof typeof Event];
128
-
129
- /**
130
- * @category Metadata
131
- * @enum
132
- */
133
- export const Gesture = {
134
- MESSAGE: "message",
135
- } as const;
136
- /**
137
- * @category Metadata
138
- */
139
- export type Gesture = (typeof Gesture)[keyof typeof Gesture];
package/src/messenger.ts DELETED
@@ -1,403 +0,0 @@
1
- /**
2
- * @module Component
3
- */
4
-
5
- // Base Component
6
- import { Component } from "@scalable.software/component";
7
- import { type Configuration } from "@scalable.software/component";
8
-
9
- // Component Metadata
10
- import {
11
- Tag,
12
- Attributes,
13
- Connection,
14
- Event,
15
- Gesture,
16
- Session,
17
- Endpoint,
18
- Protocol,
19
- Message,
20
- } from "./messenger.meta.js";
21
-
22
- import { type Path } from "./messenger.meta.js";
23
-
24
- // Component Validation
25
- import { Validate } from "./messenger.validation.js";
26
-
27
- /**
28
- * Configuration for the headless component — no template or stylesheet.
29
- * @category Configuration
30
- */
31
- export const configuration: Configuration = {
32
- url: import.meta.url,
33
- } as const;
34
-
35
- /**
36
- * @category Component
37
- */
38
- export class Messenger extends Component {
39
- /**
40
- * The tag name of the component
41
- * @category Configuration
42
- */
43
- public static get Tag() {
44
- return Tag;
45
- }
46
-
47
- /**
48
- * Only attributes defined the Attributes object will be observed in DOM
49
- * @category State
50
- */
51
- public static get Attributes(): Attributes {
52
- return Attributes;
53
- }
54
-
55
- /**
56
- * The live connection status of the messenger. Internally owned.
57
- * @category State
58
- * @hidden
59
- */
60
- private _connection: Connection = Connection.DISCONNECTED;
61
-
62
- /**
63
- * The negotiate request path. When absent the messenger stays disconnected.
64
- * @category State
65
- * @hidden
66
- */
67
- private _path: Path | null = null;
68
-
69
- /**
70
- * The live Web PubSub socket. Held so it can be closed and replaced.
71
- * @category State
72
- * @hidden
73
- */
74
- private _socket: WebSocket | null = null;
75
-
76
- /**
77
- * Event subscription handler slots.
78
- * @category Event
79
- * @hidden
80
- */
81
- private _onpathchange: EventListener | null = null;
82
- private _onconnecting: EventListener | null = null;
83
- private _onconnected: EventListener | null = null;
84
- private _ondisconnected: EventListener | null = null;
85
- private _onmasterdatachange: EventListener | null = null;
86
-
87
- /**
88
- * Cache element references to improve performance
89
- * @category State
90
- * @hidden
91
- */
92
- protected elements: {} = {};
93
-
94
- /**
95
- * @hidden
96
- */
97
- constructor() {
98
- super(configuration);
99
- }
100
-
101
- /**
102
- * The live connection status of the messenger. Internally owned — readable
103
- * externally, but only mutable from within (protected setter).
104
- * @category State
105
- */
106
- public get connection(): Connection {
107
- return this._connection;
108
- }
109
-
110
- protected set connection(connection: Connection) {
111
- // Guard
112
- if (this._connection === connection) return;
113
-
114
- // Mutate
115
- this._connection = connection;
116
- this.setAttribute(Attributes.CONNECTION, connection);
117
-
118
- // Dispatch
119
- const event = { detail: { connection } };
120
- connection === Connection.CONNECTING &&
121
- this._dispatchEvent(Event.ON_CONNECTING, event);
122
- connection === Connection.CONNECTED &&
123
- this._dispatchEvent(Event.ON_CONNECTED, event);
124
- connection === Connection.DISCONNECTED &&
125
- this._dispatchEvent(Event.ON_DISCONNECTED, event);
126
- }
127
-
128
- /**
129
- * The negotiate request path. When absent the messenger stays disconnected.
130
- * @category State
131
- */
132
- public get path(): Path | null {
133
- return this._path;
134
- }
135
-
136
- public set path(path: Path | null) {
137
- // Normalize
138
- path = path ?? null;
139
-
140
- // Validate
141
- path = path ? Validate.path(path) : null;
142
-
143
- // Guard
144
- if (this._path === path) return;
145
-
146
- // Mutate
147
- this._path = path;
148
- path === null
149
- ? this.removeAttribute(Attributes.PATH)
150
- : this.setAttribute(Attributes.PATH, path);
151
-
152
- // Dispatch
153
- this._dispatchEvent(Event.ON_PATH_CHANGE, { detail: { path } });
154
- }
155
-
156
- /**
157
- * The session token, read fresh from local storage on every access — never
158
- * cached — because the License component renews and rewrites the key.
159
- * @category State
160
- * @hidden
161
- */
162
- private get _token(): string | null {
163
- return localStorage.getItem(Session);
164
- }
165
-
166
- /**
167
- * The endpoint host base URL, read fresh from local storage on every access —
168
- * never cached — mirroring the session token. Composed with the `path` state
169
- * to form the negotiate URL. When absent the messenger stays disconnected.
170
- * @category State
171
- * @hidden
172
- */
173
- private get _host(): string | null {
174
- return localStorage.getItem(Endpoint);
175
- }
176
-
177
- /**
178
- * Negotiates a client access URL from the configured path, authenticating
179
- * with the session token, then opens the live connection. No-op when the
180
- * path or token is unavailable, or when already connecting or connected.
181
- * When negotiate fails the connection returns to `disconnected`.
182
- * @category Operation
183
- */
184
- public connect = async (): Promise<void> => {
185
- const token = this._token;
186
-
187
- // Gate + guard
188
- if (!this._host || !this.path || !token) return;
189
- if (this._connection !== Connection.DISCONNECTED) return;
190
-
191
- // Establish — on negotiate failure, recover to disconnected
192
- this.connection = Connection.CONNECTING;
193
- try {
194
- const url = await this._negotiate(token);
195
- if (this.connection !== Connection.CONNECTING) return;
196
- this._socket = this._open(url);
197
- } catch {
198
- this.connection = Connection.DISCONNECTED;
199
- }
200
- };
201
-
202
- /**
203
- * Closes the live connection.
204
- * @category Operation
205
- */
206
- public disconnect = (): void => {
207
- this._socket?.close();
208
- this._socket = null;
209
- this.connection = Connection.DISCONNECTED;
210
- };
211
-
212
- /**
213
- * Sends a JSON message to the server over the open Web PubSub connection by
214
- * wrapping it in an `event` frame — the client-to-server mirror of the inbound
215
- * `masterData.changed` notification. No-op returning `false` when there is no
216
- * open connection; returns `true` when a frame is sent.
217
- * @category Operation
218
- */
219
- public send = (message: object): boolean => {
220
- // Gate
221
- if (!this._socket || this._connection !== Connection.CONNECTED)
222
- return false;
223
-
224
- // Send — wrap in an Azure Web PubSub `event` frame, message rides as `data`
225
- this._socket.send(
226
- JSON.stringify({
227
- type: "event",
228
- event: "message",
229
- dataType: "json",
230
- data: message,
231
- }),
232
- );
233
- return true;
234
- };
235
-
236
- /**
237
- * Subscribes to the `onpathchange` event. Assigning replaces the previous
238
- * handler; assigning `null` removes it.
239
- * @category Event
240
- */
241
- public set onpathchange(handler: EventListener | null) {
242
- this._onpathchange &&
243
- this.removeEventListener(Event.ON_PATH_CHANGE, this._onpathchange);
244
-
245
- this._onpathchange = handler;
246
-
247
- this._onpathchange &&
248
- this.addEventListener(Event.ON_PATH_CHANGE, this._onpathchange);
249
- }
250
-
251
- /**
252
- * Subscribes to the `onconnecting` event. Assigning replaces the previous
253
- * handler; assigning `null` removes it.
254
- * @category Event
255
- */
256
- public set onconnecting(handler: EventListener | null) {
257
- this._onconnecting &&
258
- this.removeEventListener(Event.ON_CONNECTING, this._onconnecting);
259
-
260
- this._onconnecting = handler;
261
-
262
- this._onconnecting &&
263
- this.addEventListener(Event.ON_CONNECTING, this._onconnecting);
264
- }
265
-
266
- /**
267
- * Subscribes to the `onconnected` event. Assigning replaces the previous
268
- * handler; assigning `null` removes it.
269
- * @category Event
270
- */
271
- public set onconnected(handler: EventListener | null) {
272
- this._onconnected &&
273
- this.removeEventListener(Event.ON_CONNECTED, this._onconnected);
274
-
275
- this._onconnected = handler;
276
-
277
- this._onconnected &&
278
- this.addEventListener(Event.ON_CONNECTED, this._onconnected);
279
- }
280
-
281
- /**
282
- * Subscribes to the `ondisconnected` event. Assigning replaces the previous
283
- * handler; assigning `null` removes it.
284
- * @category Event
285
- */
286
- public set ondisconnected(handler: EventListener | null) {
287
- this._ondisconnected &&
288
- this.removeEventListener(Event.ON_DISCONNECTED, this._ondisconnected);
289
-
290
- this._ondisconnected = handler;
291
-
292
- this._ondisconnected &&
293
- this.addEventListener(Event.ON_DISCONNECTED, this._ondisconnected);
294
- }
295
-
296
- /**
297
- * Subscribes to the `onmasterdatachange` event. Assigning replaces the
298
- * previous handler; assigning `null` removes it.
299
- * @category Event
300
- */
301
- public set onmasterdatachange(handler: EventListener | null) {
302
- this._onmasterdatachange &&
303
- this.removeEventListener(
304
- Event.ON_MASTERDATACHANGE,
305
- this._onmasterdatachange,
306
- );
307
-
308
- this._onmasterdatachange = handler;
309
-
310
- this._onmasterdatachange &&
311
- this.addEventListener(
312
- Event.ON_MASTERDATACHANGE,
313
- this._onmasterdatachange,
314
- );
315
- }
316
-
317
- /**
318
- * List operations to perform for selected attributes being observed in the DOM.
319
- * @category Configuration
320
- * @hidden
321
- */
322
- protected _attributeHandlers = {
323
- [Attributes.CONNECTION]: (value: Connection) =>
324
- value !== this._connection &&
325
- this.setAttribute(Attributes.CONNECTION, this._connection),
326
- [Attributes.PATH]: (value: Path) => (this.path = value),
327
- };
328
-
329
- /**
330
- * Cache element references to improve performance
331
- * @category Configuration
332
- * @hidden
333
- */
334
- protected _cache = () => {};
335
-
336
- /**
337
- * Initialize component attributes with default values
338
- * @category Configuration
339
- * @hidden
340
- */
341
- protected _initialize = () => {
342
- !this.hasAttribute(Attributes.CONNECTION) &&
343
- this.setAttribute(Attributes.CONNECTION, this._connection);
344
- };
345
-
346
- /**
347
- * Called by the connectedCallback prototypical method
348
- * @category Configuration
349
- * @hidden
350
- */
351
- protected _addEventListeners = () => {};
352
-
353
- /**
354
- * Called by the disconnectedCallback prototypical method. Closes the live
355
- * connection so a removed element does not keep a socket open.
356
- * @category Configuration
357
- * @hidden
358
- */
359
- protected _removeEventListeners = () => this.disconnect();
360
-
361
- /**
362
- * Negotiates a short-lived Web PubSub client access URL from the path,
363
- * authenticating the request with the session token.
364
- * @category Operation
365
- * @hidden
366
- */
367
- private _negotiate = async (token: string): Promise<string> => {
368
- const response = await fetch(`${this._host}${this.path}`, {
369
- headers: { Authorization: `Bearer ${token}` },
370
- });
371
- const { url } = await response.json();
372
- if (!url) throw new Error("Invalid negotiate response: missing url");
373
- return url;
374
- };
375
-
376
- /**
377
- * Opens a Web PubSub WebSocket with the required subprotocol and wires the
378
- * connection lifecycle handlers.
379
- * @category Operation
380
- * @hidden
381
- */
382
- private _open = (url: string): WebSocket => {
383
- const socket = new WebSocket(url, Protocol);
384
- socket.onopen = () => (this.connection = Connection.CONNECTED);
385
- socket.onclose = () => (this.connection = Connection.DISCONNECTED);
386
- socket.addEventListener(Gesture.MESSAGE, this._handleMessage);
387
- return socket;
388
- };
389
-
390
- /**
391
- * Handles an inbound Web PubSub message (the `message` gesture). Dispatches
392
- * `onmasterdatachange` when the payload's `data.type` is `masterData.changed`.
393
- * @category Gesture
394
- * @hidden
395
- */
396
- private _handleMessage = (event: MessageEvent) => {
397
- const message = JSON.parse(event.data);
398
- message.data?.type === Message.MASTER_DATA_CHANGED &&
399
- this._dispatchEvent(Event.ON_MASTERDATACHANGE, {
400
- detail: { datetime: new Date().toISOString() },
401
- });
402
- };
403
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * @module Component
3
- */
4
-
5
- // Component Metadata
6
- import { type Path } from "./messenger.meta.js";
7
-
8
- /**
9
- * Runtime enforcement of declared value domains.
10
- * @category Validation
11
- */
12
- export class Validate {
13
- /**
14
- * Validates the negotiate request path value domain.
15
- * @category Validation
16
- */
17
- public static path = (value: string) => {
18
- if (!value.startsWith("/")) {
19
- throw new Error(`Invalid path value: ${value}`);
20
- }
21
- return value as Path;
22
- };
23
- }
@@ -1,8 +0,0 @@
1
- export default {
2
- rootDir: ".", // serve repo root: demo pulls in ../importmap and ./dist via the import map
3
- port: 3000,
4
- watch: true, // live-reload on file changes
5
- // open: "/demo/index.html", // left off: the browser is driven via the chrome-devtools MCP
6
- // Note: the demo uses relative asset paths, so it must be opened at /demo/index.html
7
- // (not at "/"). appIndex/history-fallback is intentionally not used here.
8
- };