@effect.healthcare/messenger 0.4.2 → 0.5.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/.claude/CLAUDE.md +4 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/messenger.d.ts +24 -14
- package/dist/messenger.js +55 -34
- package/dist/messenger.meta.d.ts +11 -5
- package/dist/messenger.meta.js +8 -2
- package/dist/messenger.validation.d.ts +3 -3
- package/dist/messenger.validation.js +4 -7
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/messenger.meta.ts +11 -4
- package/src/messenger.ts +54 -37
- package/src/messenger.validation.ts +6 -8
package/.claude/CLAUDE.md
CHANGED
|
@@ -78,11 +78,11 @@ When a new component's shape conflicts with a rule, classify the rule before rec
|
|
|
78
78
|
|
|
79
79
|
For advanced testing or debugging, the live web component can be served and driven through the `chrome-devtools` MCP. This gives access to the rendered component, its shadow DOM, the console, and DOM manipulation.
|
|
80
80
|
|
|
81
|
-
1. **Start the dev server** — `npm run serve`. The `preserve` hook builds first (`src/` → `dist/`), and `web-dev-server.config.mjs` is auto-loaded (`rootDir: "."`, `port:
|
|
82
|
-
2. **Navigate the controlled browser** — `mcp__chrome-devtools__navigate_page` to `http://localhost:
|
|
81
|
+
1. **Start the dev server** — `npm run serve`. The `preserve` hook builds first (`src/` → `dist/`), and `web-dev-server.config.mjs` is auto-loaded (`rootDir: "."`, `port: 3000`, `watch: true`). Run it in the background. The server serves the repo root because the demo loads `../importmap` and `./dist` via the import map. Port 3000 is the origin the backend CORS-whitelists, so do not change it.
|
|
82
|
+
2. **Navigate the controlled browser** — `mcp__chrome-devtools__navigate_page` to `http://localhost:3000/demo/index.html`. Always use the explicit `/demo/index.html` path; the demo uses relative asset paths and will not resolve at `/`.
|
|
83
83
|
3. **Verify** — `mcp__chrome-devtools__evaluate_script` to inspect the component (e.g. `customElements.get("app-messenger")` is defined; the base framework exposes the shadow root as `el.root`); `mcp__chrome-devtools__list_console_messages` to check for errors.
|
|
84
|
-
4. **Clean up when done** — navigate the page to `about:blank` (the MCP keeps one page alive, so the last page cannot be closed), then stop the dev server process on port
|
|
84
|
+
4. **Clean up when done** — navigate the page to `about:blank` (the MCP keeps one page alive, so the last page cannot be closed), then stop the dev server process on port 3000.
|
|
85
85
|
|
|
86
86
|
Notes:
|
|
87
87
|
- MCP tool schemas must be loaded via `ToolSearch` (`select:<name>`) before first use each session.
|
|
88
|
-
- If a stale server already holds port
|
|
88
|
+
- If a stale server already holds port 3000, the new one fails to bind — kill the straggler rather than letting it drift to another port.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { Tag, Session, Protocol, Message, Attributes, State, Connection, Operation, Event, Gesture, } from "./messenger.meta.js";
|
|
1
|
+
export { Tag, Session, Endpoint, Protocol, Message, Attributes, State, Connection, Operation, Event, Gesture, } from "./messenger.meta.js";
|
|
2
|
+
export { type Path } from "./messenger.meta.js";
|
|
2
3
|
export { Validate } from "./messenger.validation.js";
|
|
3
4
|
export { Messenger } from "./messenger.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Tag, Session, Protocol, Message, Attributes, State, Connection, Operation, Event, Gesture, } from "./messenger.meta.js";
|
|
1
|
+
export { Tag, Session, Endpoint, Protocol, Message, Attributes, State, Connection, Operation, Event, Gesture, } from "./messenger.meta.js";
|
|
2
2
|
export { Validate } from "./messenger.validation.js";
|
|
3
3
|
export { Messenger } from "./messenger.js";
|
package/dist/messenger.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Component } from "@scalable.software/component";
|
|
5
5
|
import { type Configuration } from "@scalable.software/component";
|
|
6
6
|
import { Attributes, Connection } from "./messenger.meta.js";
|
|
7
|
-
import { type
|
|
7
|
+
import { type Path } from "./messenger.meta.js";
|
|
8
8
|
/**
|
|
9
9
|
* Configuration for the headless component — no template or stylesheet.
|
|
10
10
|
* @category Configuration
|
|
@@ -31,11 +31,11 @@ export declare class Messenger extends Component {
|
|
|
31
31
|
*/
|
|
32
32
|
private _connection;
|
|
33
33
|
/**
|
|
34
|
-
* The negotiate
|
|
34
|
+
* The negotiate request path. When absent the messenger stays disconnected.
|
|
35
35
|
* @category State
|
|
36
36
|
* @hidden
|
|
37
37
|
*/
|
|
38
|
-
private
|
|
38
|
+
private _path;
|
|
39
39
|
/**
|
|
40
40
|
* The live Web PubSub socket. Held so it can be closed and replaced.
|
|
41
41
|
* @category State
|
|
@@ -47,7 +47,7 @@ export declare class Messenger extends Component {
|
|
|
47
47
|
* @category Event
|
|
48
48
|
* @hidden
|
|
49
49
|
*/
|
|
50
|
-
private
|
|
50
|
+
private _onpathchange;
|
|
51
51
|
private _onconnecting;
|
|
52
52
|
private _onconnected;
|
|
53
53
|
private _ondisconnected;
|
|
@@ -70,11 +70,11 @@ export declare class Messenger extends Component {
|
|
|
70
70
|
get connection(): Connection;
|
|
71
71
|
protected set connection(connection: Connection);
|
|
72
72
|
/**
|
|
73
|
-
* The negotiate
|
|
73
|
+
* The negotiate request path. When absent the messenger stays disconnected.
|
|
74
74
|
* @category State
|
|
75
75
|
*/
|
|
76
|
-
get
|
|
77
|
-
set
|
|
76
|
+
get path(): Path | null;
|
|
77
|
+
set path(path: Path | null);
|
|
78
78
|
/**
|
|
79
79
|
* The session token, read fresh from local storage on every access — never
|
|
80
80
|
* cached — because the License component renews and rewrites the key.
|
|
@@ -83,9 +83,18 @@ export declare class Messenger extends Component {
|
|
|
83
83
|
*/
|
|
84
84
|
private get _token();
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
86
|
+
* The endpoint host base URL, read fresh from local storage on every access —
|
|
87
|
+
* never cached — mirroring the session token. Composed with the `path` state
|
|
88
|
+
* to form the negotiate URL. When absent the messenger stays disconnected.
|
|
89
|
+
* @category State
|
|
90
|
+
* @hidden
|
|
91
|
+
*/
|
|
92
|
+
private get _host();
|
|
93
|
+
/**
|
|
94
|
+
* Negotiates a client access URL from the configured path, authenticating
|
|
87
95
|
* with the session token, then opens the live connection. No-op when the
|
|
88
|
-
*
|
|
96
|
+
* path or token is unavailable, or when already connecting or connected.
|
|
97
|
+
* When negotiate fails the connection returns to `disconnected`.
|
|
89
98
|
* @category Operation
|
|
90
99
|
*/
|
|
91
100
|
connect: () => Promise<void>;
|
|
@@ -103,11 +112,11 @@ export declare class Messenger extends Component {
|
|
|
103
112
|
*/
|
|
104
113
|
send: (message: object) => boolean;
|
|
105
114
|
/**
|
|
106
|
-
* Subscribes to the `
|
|
115
|
+
* Subscribes to the `onpathchange` event. Assigning replaces the previous
|
|
107
116
|
* handler; assigning `null` removes it.
|
|
108
117
|
* @category Event
|
|
109
118
|
*/
|
|
110
|
-
set
|
|
119
|
+
set onpathchange(handler: EventListener | null);
|
|
111
120
|
/**
|
|
112
121
|
* Subscribes to the `onconnecting` event. Assigning replaces the previous
|
|
113
122
|
* handler; assigning `null` removes it.
|
|
@@ -139,7 +148,7 @@ export declare class Messenger extends Component {
|
|
|
139
148
|
*/
|
|
140
149
|
protected _attributeHandlers: {
|
|
141
150
|
connection: (value: Connection) => false | void;
|
|
142
|
-
|
|
151
|
+
path: (value: Path) => string;
|
|
143
152
|
};
|
|
144
153
|
/**
|
|
145
154
|
* Cache element references to improve performance
|
|
@@ -160,13 +169,14 @@ export declare class Messenger extends Component {
|
|
|
160
169
|
*/
|
|
161
170
|
protected _addEventListeners: () => void;
|
|
162
171
|
/**
|
|
163
|
-
* Called by the disconnectedCallback prototypical method
|
|
172
|
+
* Called by the disconnectedCallback prototypical method. Closes the live
|
|
173
|
+
* connection so a removed element does not keep a socket open.
|
|
164
174
|
* @category Configuration
|
|
165
175
|
* @hidden
|
|
166
176
|
*/
|
|
167
177
|
protected _removeEventListeners: () => void;
|
|
168
178
|
/**
|
|
169
|
-
* Negotiates a short-lived Web PubSub client access URL from the
|
|
179
|
+
* Negotiates a short-lived Web PubSub client access URL from the path,
|
|
170
180
|
* authenticating the request with the session token.
|
|
171
181
|
* @category Operation
|
|
172
182
|
* @hidden
|
package/dist/messenger.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Base Component
|
|
5
5
|
import { Component } from "@scalable.software/component";
|
|
6
6
|
// Component Metadata
|
|
7
|
-
import { Tag, Attributes, Connection, Event, Gesture, Session, Protocol, Message, } from "./messenger.meta.js";
|
|
7
|
+
import { Tag, Attributes, Connection, Event, Gesture, Session, Endpoint, Protocol, Message, } from "./messenger.meta.js";
|
|
8
8
|
// Component Validation
|
|
9
9
|
import { Validate } from "./messenger.validation.js";
|
|
10
10
|
/**
|
|
@@ -39,11 +39,11 @@ export class Messenger extends Component {
|
|
|
39
39
|
*/
|
|
40
40
|
_connection = Connection.DISCONNECTED;
|
|
41
41
|
/**
|
|
42
|
-
* The negotiate
|
|
42
|
+
* The negotiate request path. When absent the messenger stays disconnected.
|
|
43
43
|
* @category State
|
|
44
44
|
* @hidden
|
|
45
45
|
*/
|
|
46
|
-
|
|
46
|
+
_path = null;
|
|
47
47
|
/**
|
|
48
48
|
* The live Web PubSub socket. Held so it can be closed and replaced.
|
|
49
49
|
* @category State
|
|
@@ -55,7 +55,7 @@ export class Messenger extends Component {
|
|
|
55
55
|
* @category Event
|
|
56
56
|
* @hidden
|
|
57
57
|
*/
|
|
58
|
-
|
|
58
|
+
_onpathchange = null;
|
|
59
59
|
_onconnecting = null;
|
|
60
60
|
_onconnected = null;
|
|
61
61
|
_ondisconnected = null;
|
|
@@ -97,27 +97,27 @@ export class Messenger extends Component {
|
|
|
97
97
|
this._dispatchEvent(Event.ON_DISCONNECTED, event);
|
|
98
98
|
}
|
|
99
99
|
/**
|
|
100
|
-
* The negotiate
|
|
100
|
+
* The negotiate request path. When absent the messenger stays disconnected.
|
|
101
101
|
* @category State
|
|
102
102
|
*/
|
|
103
|
-
get
|
|
104
|
-
return this.
|
|
103
|
+
get path() {
|
|
104
|
+
return this._path;
|
|
105
105
|
}
|
|
106
|
-
set
|
|
106
|
+
set path(path) {
|
|
107
107
|
// Normalize
|
|
108
|
-
|
|
108
|
+
path = path ?? null;
|
|
109
109
|
// Validate
|
|
110
|
-
|
|
110
|
+
path = path ? Validate.path(path) : null;
|
|
111
111
|
// Guard
|
|
112
|
-
if (this.
|
|
112
|
+
if (this._path === path)
|
|
113
113
|
return;
|
|
114
114
|
// Mutate
|
|
115
|
-
this.
|
|
116
|
-
|
|
117
|
-
? this.removeAttribute(Attributes.
|
|
118
|
-
: this.setAttribute(Attributes.
|
|
115
|
+
this._path = path;
|
|
116
|
+
path === null
|
|
117
|
+
? this.removeAttribute(Attributes.PATH)
|
|
118
|
+
: this.setAttribute(Attributes.PATH, path);
|
|
119
119
|
// Dispatch
|
|
120
|
-
this._dispatchEvent(Event.
|
|
120
|
+
this._dispatchEvent(Event.ON_PATH_CHANGE, { detail: { path } });
|
|
121
121
|
}
|
|
122
122
|
/**
|
|
123
123
|
* The session token, read fresh from local storage on every access — never
|
|
@@ -129,22 +129,40 @@ export class Messenger extends Component {
|
|
|
129
129
|
return localStorage.getItem(Session);
|
|
130
130
|
}
|
|
131
131
|
/**
|
|
132
|
-
*
|
|
132
|
+
* The endpoint host base URL, read fresh from local storage on every access —
|
|
133
|
+
* never cached — mirroring the session token. Composed with the `path` state
|
|
134
|
+
* to form the negotiate URL. When absent the messenger stays disconnected.
|
|
135
|
+
* @category State
|
|
136
|
+
* @hidden
|
|
137
|
+
*/
|
|
138
|
+
get _host() {
|
|
139
|
+
return localStorage.getItem(Endpoint);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Negotiates a client access URL from the configured path, authenticating
|
|
133
143
|
* with the session token, then opens the live connection. No-op when the
|
|
134
|
-
*
|
|
144
|
+
* path or token is unavailable, or when already connecting or connected.
|
|
145
|
+
* When negotiate fails the connection returns to `disconnected`.
|
|
135
146
|
* @category Operation
|
|
136
147
|
*/
|
|
137
148
|
connect = async () => {
|
|
138
149
|
const token = this._token;
|
|
139
150
|
// Gate + guard
|
|
140
|
-
if (!this.
|
|
151
|
+
if (!this._host || !this.path || !token)
|
|
141
152
|
return;
|
|
142
153
|
if (this._connection !== Connection.DISCONNECTED)
|
|
143
154
|
return;
|
|
144
|
-
// Establish
|
|
155
|
+
// Establish — on negotiate failure, recover to disconnected
|
|
145
156
|
this.connection = Connection.CONNECTING;
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
try {
|
|
158
|
+
const url = await this._negotiate(token);
|
|
159
|
+
if (this.connection !== Connection.CONNECTING)
|
|
160
|
+
return;
|
|
161
|
+
this._socket = this._open(url);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
this.connection = Connection.DISCONNECTED;
|
|
165
|
+
}
|
|
148
166
|
};
|
|
149
167
|
/**
|
|
150
168
|
* Closes the live connection.
|
|
@@ -176,16 +194,16 @@ export class Messenger extends Component {
|
|
|
176
194
|
return true;
|
|
177
195
|
};
|
|
178
196
|
/**
|
|
179
|
-
* Subscribes to the `
|
|
197
|
+
* Subscribes to the `onpathchange` event. Assigning replaces the previous
|
|
180
198
|
* handler; assigning `null` removes it.
|
|
181
199
|
* @category Event
|
|
182
200
|
*/
|
|
183
|
-
set
|
|
184
|
-
this.
|
|
185
|
-
this.removeEventListener(Event.
|
|
186
|
-
this.
|
|
187
|
-
this.
|
|
188
|
-
this.addEventListener(Event.
|
|
201
|
+
set onpathchange(handler) {
|
|
202
|
+
this._onpathchange &&
|
|
203
|
+
this.removeEventListener(Event.ON_PATH_CHANGE, this._onpathchange);
|
|
204
|
+
this._onpathchange = handler;
|
|
205
|
+
this._onpathchange &&
|
|
206
|
+
this.addEventListener(Event.ON_PATH_CHANGE, this._onpathchange);
|
|
189
207
|
}
|
|
190
208
|
/**
|
|
191
209
|
* Subscribes to the `onconnecting` event. Assigning replaces the previous
|
|
@@ -243,7 +261,7 @@ export class Messenger extends Component {
|
|
|
243
261
|
_attributeHandlers = {
|
|
244
262
|
[Attributes.CONNECTION]: (value) => value !== this._connection &&
|
|
245
263
|
this.setAttribute(Attributes.CONNECTION, this._connection),
|
|
246
|
-
[Attributes.
|
|
264
|
+
[Attributes.PATH]: (value) => (this.path = value),
|
|
247
265
|
};
|
|
248
266
|
/**
|
|
249
267
|
* Cache element references to improve performance
|
|
@@ -267,22 +285,25 @@ export class Messenger extends Component {
|
|
|
267
285
|
*/
|
|
268
286
|
_addEventListeners = () => { };
|
|
269
287
|
/**
|
|
270
|
-
* Called by the disconnectedCallback prototypical method
|
|
288
|
+
* Called by the disconnectedCallback prototypical method. Closes the live
|
|
289
|
+
* connection so a removed element does not keep a socket open.
|
|
271
290
|
* @category Configuration
|
|
272
291
|
* @hidden
|
|
273
292
|
*/
|
|
274
|
-
_removeEventListeners = () =>
|
|
293
|
+
_removeEventListeners = () => this.disconnect();
|
|
275
294
|
/**
|
|
276
|
-
* Negotiates a short-lived Web PubSub client access URL from the
|
|
295
|
+
* Negotiates a short-lived Web PubSub client access URL from the path,
|
|
277
296
|
* authenticating the request with the session token.
|
|
278
297
|
* @category Operation
|
|
279
298
|
* @hidden
|
|
280
299
|
*/
|
|
281
300
|
_negotiate = async (token) => {
|
|
282
|
-
const response = await fetch(this.
|
|
301
|
+
const response = await fetch(`${this._host}${this.path}`, {
|
|
283
302
|
headers: { Authorization: `Bearer ${token}` },
|
|
284
303
|
});
|
|
285
304
|
const { url } = await response.json();
|
|
305
|
+
if (!url)
|
|
306
|
+
throw new Error("Invalid negotiate response: missing url");
|
|
286
307
|
return url;
|
|
287
308
|
};
|
|
288
309
|
/**
|
package/dist/messenger.meta.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ export declare const Tag: "app-messenger";
|
|
|
11
11
|
* @category Configuration
|
|
12
12
|
*/
|
|
13
13
|
export declare const Session: "app-license.session";
|
|
14
|
+
/**
|
|
15
|
+
* Local storage key under which the endpoint host base URL is cached by the
|
|
16
|
+
* Configurator component. Composed with the `path` state to form the negotiate URL.
|
|
17
|
+
* @category Configuration
|
|
18
|
+
*/
|
|
19
|
+
export declare const Endpoint: "data";
|
|
14
20
|
/**
|
|
15
21
|
* The Azure Web PubSub subprotocol required for structured group messaging.
|
|
16
22
|
* @category Configuration
|
|
@@ -36,7 +42,7 @@ export type Message = (typeof Message)[keyof typeof Message];
|
|
|
36
42
|
*/
|
|
37
43
|
export declare const Attributes: {
|
|
38
44
|
readonly CONNECTION: "connection";
|
|
39
|
-
readonly
|
|
45
|
+
readonly PATH: "path";
|
|
40
46
|
};
|
|
41
47
|
/**
|
|
42
48
|
* HTML Attributes available to set
|
|
@@ -50,7 +56,7 @@ export type Attributes = typeof Attributes;
|
|
|
50
56
|
*/
|
|
51
57
|
export declare const State: {
|
|
52
58
|
readonly CONNECTION: "connection";
|
|
53
|
-
readonly
|
|
59
|
+
readonly PATH: "path";
|
|
54
60
|
};
|
|
55
61
|
/**
|
|
56
62
|
* HTML Attributes available to set
|
|
@@ -73,10 +79,10 @@ export declare const Connection: {
|
|
|
73
79
|
*/
|
|
74
80
|
export type Connection = (typeof Connection)[keyof typeof Connection];
|
|
75
81
|
/**
|
|
76
|
-
* The negotiate
|
|
82
|
+
* The negotiate request path value domain
|
|
77
83
|
* @category Metadata
|
|
78
84
|
*/
|
|
79
|
-
export type
|
|
85
|
+
export type Path = string;
|
|
80
86
|
/**
|
|
81
87
|
* @category Metadata
|
|
82
88
|
* @enum
|
|
@@ -95,7 +101,7 @@ export type Operation = (typeof Operation)[keyof typeof Operation];
|
|
|
95
101
|
* @enum
|
|
96
102
|
*/
|
|
97
103
|
export declare const Event: {
|
|
98
|
-
readonly
|
|
104
|
+
readonly ON_PATH_CHANGE: "onpathchange";
|
|
99
105
|
readonly ON_CONNECTING: "onconnecting";
|
|
100
106
|
readonly ON_CONNECTED: "onconnected";
|
|
101
107
|
readonly ON_DISCONNECTED: "ondisconnected";
|
package/dist/messenger.meta.js
CHANGED
|
@@ -11,6 +11,12 @@ export const Tag = "app-messenger";
|
|
|
11
11
|
* @category Configuration
|
|
12
12
|
*/
|
|
13
13
|
export const Session = "app-license.session";
|
|
14
|
+
/**
|
|
15
|
+
* Local storage key under which the endpoint host base URL is cached by the
|
|
16
|
+
* Configurator component. Composed with the `path` state to form the negotiate URL.
|
|
17
|
+
* @category Configuration
|
|
18
|
+
*/
|
|
19
|
+
export const Endpoint = "data";
|
|
14
20
|
/**
|
|
15
21
|
* The Azure Web PubSub subprotocol required for structured group messaging.
|
|
16
22
|
* @category Configuration
|
|
@@ -31,7 +37,7 @@ export const Message = {
|
|
|
31
37
|
*/
|
|
32
38
|
export const Attributes = {
|
|
33
39
|
CONNECTION: "connection",
|
|
34
|
-
|
|
40
|
+
PATH: "path",
|
|
35
41
|
};
|
|
36
42
|
/**
|
|
37
43
|
* HTML Attributes available to set
|
|
@@ -65,7 +71,7 @@ export const Operation = {
|
|
|
65
71
|
* @enum
|
|
66
72
|
*/
|
|
67
73
|
export const Event = {
|
|
68
|
-
|
|
74
|
+
ON_PATH_CHANGE: "onpathchange",
|
|
69
75
|
ON_CONNECTING: "onconnecting",
|
|
70
76
|
ON_CONNECTED: "onconnected",
|
|
71
77
|
ON_DISCONNECTED: "ondisconnected",
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module Component
|
|
3
3
|
*/
|
|
4
|
-
import { type
|
|
4
|
+
import { type Path } from "./messenger.meta.js";
|
|
5
5
|
/**
|
|
6
6
|
* Runtime enforcement of declared value domains.
|
|
7
7
|
* @category Validation
|
|
8
8
|
*/
|
|
9
9
|
export declare class Validate {
|
|
10
10
|
/**
|
|
11
|
-
* Validates the negotiate
|
|
11
|
+
* Validates the negotiate request path value domain.
|
|
12
12
|
* @category Validation
|
|
13
13
|
*/
|
|
14
|
-
static
|
|
14
|
+
static path: (value: string) => Path;
|
|
15
15
|
}
|
|
@@ -7,15 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export class Validate {
|
|
9
9
|
/**
|
|
10
|
-
* Validates the negotiate
|
|
10
|
+
* Validates the negotiate request path value domain.
|
|
11
11
|
* @category Validation
|
|
12
12
|
*/
|
|
13
|
-
static
|
|
14
|
-
|
|
15
|
-
new
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
throw new Error(`Invalid endpoint value: ${value}`);
|
|
13
|
+
static path = (value) => {
|
|
14
|
+
if (!value.startsWith("/")) {
|
|
15
|
+
throw new Error(`Invalid path value: ${value}`);
|
|
19
16
|
}
|
|
20
17
|
return value;
|
|
21
18
|
};
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export {
|
|
2
2
|
Tag,
|
|
3
3
|
Session,
|
|
4
|
+
Endpoint,
|
|
4
5
|
Protocol,
|
|
5
6
|
Message,
|
|
6
7
|
Attributes,
|
|
@@ -11,6 +12,8 @@ export {
|
|
|
11
12
|
Gesture,
|
|
12
13
|
} from "./messenger.meta.js";
|
|
13
14
|
|
|
15
|
+
export { type Path } from "./messenger.meta.js";
|
|
16
|
+
|
|
14
17
|
export { Validate } from "./messenger.validation.js";
|
|
15
18
|
|
|
16
19
|
export { Messenger } from "./messenger.js";
|
package/src/messenger.meta.ts
CHANGED
|
@@ -14,6 +14,13 @@ export const Tag = "app-messenger" as const;
|
|
|
14
14
|
*/
|
|
15
15
|
export const Session = "app-license.session" as const;
|
|
16
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
|
+
|
|
17
24
|
/**
|
|
18
25
|
* The Azure Web PubSub subprotocol required for structured group messaging.
|
|
19
26
|
* @category Configuration
|
|
@@ -42,7 +49,7 @@ export type Message = (typeof Message)[keyof typeof Message];
|
|
|
42
49
|
*/
|
|
43
50
|
export const Attributes = {
|
|
44
51
|
CONNECTION: "connection",
|
|
45
|
-
|
|
52
|
+
PATH: "path",
|
|
46
53
|
} as const;
|
|
47
54
|
/**
|
|
48
55
|
* HTML Attributes available to set
|
|
@@ -83,10 +90,10 @@ export const Connection = {
|
|
|
83
90
|
export type Connection = (typeof Connection)[keyof typeof Connection];
|
|
84
91
|
|
|
85
92
|
/**
|
|
86
|
-
* The negotiate
|
|
93
|
+
* The negotiate request path value domain
|
|
87
94
|
* @category Metadata
|
|
88
95
|
*/
|
|
89
|
-
export type
|
|
96
|
+
export type Path = string;
|
|
90
97
|
|
|
91
98
|
/**
|
|
92
99
|
* @category Metadata
|
|
@@ -108,7 +115,7 @@ export type Operation = (typeof Operation)[keyof typeof Operation];
|
|
|
108
115
|
* @enum
|
|
109
116
|
*/
|
|
110
117
|
export const Event = {
|
|
111
|
-
|
|
118
|
+
ON_PATH_CHANGE: "onpathchange",
|
|
112
119
|
ON_CONNECTING: "onconnecting",
|
|
113
120
|
ON_CONNECTED: "onconnected",
|
|
114
121
|
ON_DISCONNECTED: "ondisconnected",
|
package/src/messenger.ts
CHANGED
|
@@ -14,11 +14,12 @@ import {
|
|
|
14
14
|
Event,
|
|
15
15
|
Gesture,
|
|
16
16
|
Session,
|
|
17
|
+
Endpoint,
|
|
17
18
|
Protocol,
|
|
18
19
|
Message,
|
|
19
20
|
} from "./messenger.meta.js";
|
|
20
21
|
|
|
21
|
-
import { type
|
|
22
|
+
import { type Path } from "./messenger.meta.js";
|
|
22
23
|
|
|
23
24
|
// Component Validation
|
|
24
25
|
import { Validate } from "./messenger.validation.js";
|
|
@@ -59,11 +60,11 @@ export class Messenger extends Component {
|
|
|
59
60
|
private _connection: Connection = Connection.DISCONNECTED;
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
|
-
* The negotiate
|
|
63
|
+
* The negotiate request path. When absent the messenger stays disconnected.
|
|
63
64
|
* @category State
|
|
64
65
|
* @hidden
|
|
65
66
|
*/
|
|
66
|
-
private
|
|
67
|
+
private _path: Path | null = null;
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
70
|
* The live Web PubSub socket. Held so it can be closed and replaced.
|
|
@@ -77,7 +78,7 @@ export class Messenger extends Component {
|
|
|
77
78
|
* @category Event
|
|
78
79
|
* @hidden
|
|
79
80
|
*/
|
|
80
|
-
private
|
|
81
|
+
private _onpathchange: EventListener | null = null;
|
|
81
82
|
private _onconnecting: EventListener | null = null;
|
|
82
83
|
private _onconnected: EventListener | null = null;
|
|
83
84
|
private _ondisconnected: EventListener | null = null;
|
|
@@ -125,31 +126,31 @@ export class Messenger extends Component {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
/**
|
|
128
|
-
* The negotiate
|
|
129
|
+
* The negotiate request path. When absent the messenger stays disconnected.
|
|
129
130
|
* @category State
|
|
130
131
|
*/
|
|
131
|
-
public get
|
|
132
|
-
return this.
|
|
132
|
+
public get path(): Path | null {
|
|
133
|
+
return this._path;
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
public set
|
|
136
|
+
public set path(path: Path | null) {
|
|
136
137
|
// Normalize
|
|
137
|
-
|
|
138
|
+
path = path ?? null;
|
|
138
139
|
|
|
139
140
|
// Validate
|
|
140
|
-
|
|
141
|
+
path = path ? Validate.path(path) : null;
|
|
141
142
|
|
|
142
143
|
// Guard
|
|
143
|
-
if (this.
|
|
144
|
+
if (this._path === path) return;
|
|
144
145
|
|
|
145
146
|
// Mutate
|
|
146
|
-
this.
|
|
147
|
-
|
|
148
|
-
? this.removeAttribute(Attributes.
|
|
149
|
-
: this.setAttribute(Attributes.
|
|
147
|
+
this._path = path;
|
|
148
|
+
path === null
|
|
149
|
+
? this.removeAttribute(Attributes.PATH)
|
|
150
|
+
: this.setAttribute(Attributes.PATH, path);
|
|
150
151
|
|
|
151
152
|
// Dispatch
|
|
152
|
-
this._dispatchEvent(Event.
|
|
153
|
+
this._dispatchEvent(Event.ON_PATH_CHANGE, { detail: { path } });
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
/**
|
|
@@ -163,22 +164,39 @@ export class Messenger extends Component {
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
/**
|
|
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
|
|
167
179
|
* with the session token, then opens the live connection. No-op when the
|
|
168
|
-
*
|
|
180
|
+
* path or token is unavailable, or when already connecting or connected.
|
|
181
|
+
* When negotiate fails the connection returns to `disconnected`.
|
|
169
182
|
* @category Operation
|
|
170
183
|
*/
|
|
171
184
|
public connect = async (): Promise<void> => {
|
|
172
185
|
const token = this._token;
|
|
173
186
|
|
|
174
187
|
// Gate + guard
|
|
175
|
-
if (!this.
|
|
188
|
+
if (!this._host || !this.path || !token) return;
|
|
176
189
|
if (this._connection !== Connection.DISCONNECTED) return;
|
|
177
190
|
|
|
178
|
-
// Establish
|
|
191
|
+
// Establish — on negotiate failure, recover to disconnected
|
|
179
192
|
this.connection = Connection.CONNECTING;
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
}
|
|
182
200
|
};
|
|
183
201
|
|
|
184
202
|
/**
|
|
@@ -216,21 +234,18 @@ export class Messenger extends Component {
|
|
|
216
234
|
};
|
|
217
235
|
|
|
218
236
|
/**
|
|
219
|
-
* Subscribes to the `
|
|
237
|
+
* Subscribes to the `onpathchange` event. Assigning replaces the previous
|
|
220
238
|
* handler; assigning `null` removes it.
|
|
221
239
|
* @category Event
|
|
222
240
|
*/
|
|
223
|
-
public set
|
|
224
|
-
this.
|
|
225
|
-
this.removeEventListener(
|
|
226
|
-
Event.ON_ENDPOINT_CHANGE,
|
|
227
|
-
this._onendpointchange,
|
|
228
|
-
);
|
|
241
|
+
public set onpathchange(handler: EventListener | null) {
|
|
242
|
+
this._onpathchange &&
|
|
243
|
+
this.removeEventListener(Event.ON_PATH_CHANGE, this._onpathchange);
|
|
229
244
|
|
|
230
|
-
this.
|
|
245
|
+
this._onpathchange = handler;
|
|
231
246
|
|
|
232
|
-
this.
|
|
233
|
-
this.addEventListener(Event.
|
|
247
|
+
this._onpathchange &&
|
|
248
|
+
this.addEventListener(Event.ON_PATH_CHANGE, this._onpathchange);
|
|
234
249
|
}
|
|
235
250
|
|
|
236
251
|
/**
|
|
@@ -308,7 +323,7 @@ export class Messenger extends Component {
|
|
|
308
323
|
[Attributes.CONNECTION]: (value: Connection) =>
|
|
309
324
|
value !== this._connection &&
|
|
310
325
|
this.setAttribute(Attributes.CONNECTION, this._connection),
|
|
311
|
-
[Attributes.
|
|
326
|
+
[Attributes.PATH]: (value: Path) => (this.path = value),
|
|
312
327
|
};
|
|
313
328
|
|
|
314
329
|
/**
|
|
@@ -336,23 +351,25 @@ export class Messenger extends Component {
|
|
|
336
351
|
protected _addEventListeners = () => {};
|
|
337
352
|
|
|
338
353
|
/**
|
|
339
|
-
* Called by the disconnectedCallback prototypical method
|
|
354
|
+
* Called by the disconnectedCallback prototypical method. Closes the live
|
|
355
|
+
* connection so a removed element does not keep a socket open.
|
|
340
356
|
* @category Configuration
|
|
341
357
|
* @hidden
|
|
342
358
|
*/
|
|
343
|
-
protected _removeEventListeners = () =>
|
|
359
|
+
protected _removeEventListeners = () => this.disconnect();
|
|
344
360
|
|
|
345
361
|
/**
|
|
346
|
-
* Negotiates a short-lived Web PubSub client access URL from the
|
|
362
|
+
* Negotiates a short-lived Web PubSub client access URL from the path,
|
|
347
363
|
* authenticating the request with the session token.
|
|
348
364
|
* @category Operation
|
|
349
365
|
* @hidden
|
|
350
366
|
*/
|
|
351
367
|
private _negotiate = async (token: string): Promise<string> => {
|
|
352
|
-
const response = await fetch(this.
|
|
368
|
+
const response = await fetch(`${this._host}${this.path}`, {
|
|
353
369
|
headers: { Authorization: `Bearer ${token}` },
|
|
354
370
|
});
|
|
355
371
|
const { url } = await response.json();
|
|
372
|
+
if (!url) throw new Error("Invalid negotiate response: missing url");
|
|
356
373
|
return url;
|
|
357
374
|
};
|
|
358
375
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
// Component Metadata
|
|
6
|
-
import { type
|
|
6
|
+
import { type Path } from "./messenger.meta.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Runtime enforcement of declared value domains.
|
|
@@ -11,15 +11,13 @@ import { type Endpoint } from "./messenger.meta.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export class Validate {
|
|
13
13
|
/**
|
|
14
|
-
* Validates the negotiate
|
|
14
|
+
* Validates the negotiate request path value domain.
|
|
15
15
|
* @category Validation
|
|
16
16
|
*/
|
|
17
|
-
public static
|
|
18
|
-
|
|
19
|
-
new
|
|
20
|
-
} catch {
|
|
21
|
-
throw new Error(`Invalid endpoint value: ${value}`);
|
|
17
|
+
public static path = (value: string) => {
|
|
18
|
+
if (!value.startsWith("/")) {
|
|
19
|
+
throw new Error(`Invalid path value: ${value}`);
|
|
22
20
|
}
|
|
23
|
-
return value as
|
|
21
|
+
return value as Path;
|
|
24
22
|
};
|
|
25
23
|
}
|