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