@vaadin/hilla-frontend 24.7.0-alpha9 → 24.7.0-beta2

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/FluxConnection.js CHANGED
@@ -1,262 +1,281 @@
1
1
  import atmosphere from "atmosphere.js";
2
2
  import { getCsrfTokenHeadersForEndpointRequest } from "./CsrfUtils.js";
3
- import {
4
- isClientMessage
5
- } from "./FluxMessages.js";
6
- var State = /* @__PURE__ */ ((State2) => {
7
- State2["ACTIVE"] = "active";
8
- State2["INACTIVE"] = "inactive";
9
- State2["RECONNECTING"] = "reconnecting";
10
- return State2;
11
- })(State || {});
12
- var ActionOnLostSubscription = /* @__PURE__ */ ((ActionOnLostSubscription2) => {
13
- ActionOnLostSubscription2["RESUBSCRIBE"] = "resubscribe";
14
- ActionOnLostSubscription2["REMOVE"] = "remove";
15
- return ActionOnLostSubscription2;
16
- })(ActionOnLostSubscription || {});
17
- var FluxSubscriptionState = /* @__PURE__ */ ((FluxSubscriptionState2) => {
18
- FluxSubscriptionState2["CONNECTING"] = "connecting";
19
- FluxSubscriptionState2["CONNECTED"] = "connected";
20
- FluxSubscriptionState2["CLOSED"] = "closed";
21
- return FluxSubscriptionState2;
22
- })(FluxSubscriptionState || {});
23
- class FluxConnection extends EventTarget {
24
- state = "inactive" /* INACTIVE */;
25
- wasClosed = false;
26
- #endpointInfos = /* @__PURE__ */ new Map();
27
- #nextId = 0;
28
- #onCompleteCallbacks = /* @__PURE__ */ new Map();
29
- #onErrorCallbacks = /* @__PURE__ */ new Map();
30
- #onNextCallbacks = /* @__PURE__ */ new Map();
31
- #onStateChangeCallbacks = /* @__PURE__ */ new Map();
32
- #statusOfSubscriptions = /* @__PURE__ */ new Map();
33
- #pendingMessages = [];
34
- #socket;
35
- constructor(connectPrefix, atmosphereOptions) {
36
- super();
37
- this.#connectWebsocket(connectPrefix.replace(/connect$/u, ""), atmosphereOptions ?? {});
38
- }
39
- #resubscribeIfWasClosed() {
40
- if (this.wasClosed) {
41
- this.wasClosed = false;
42
- const toBeRemoved = [];
43
- this.#endpointInfos.forEach((endpointInfo, id) => {
44
- if (endpointInfo.reconnect?.() === "resubscribe" /* RESUBSCRIBE */) {
45
- this.#setSubscriptionConnState(id, "connecting" /* CONNECTING */);
46
- this.#send({
47
- "@type": "subscribe",
48
- endpointName: endpointInfo.endpointName,
49
- id,
50
- methodName: endpointInfo.methodName,
51
- params: endpointInfo.params
52
- });
53
- } else {
54
- toBeRemoved.push(id);
55
- }
56
- });
57
- toBeRemoved.forEach((id) => this.#removeSubscription(id));
58
- }
59
- }
60
- /**
61
- * Subscribes to the flux returned by the given endpoint name + method name using the given parameters.
62
- *
63
- * @param endpointName - the endpoint to connect to
64
- * @param methodName - the method in the endpoint to connect to
65
- * @param parameters - the parameters to use
66
- * @returns a subscription
67
- */
68
- subscribe(endpointName, methodName, parameters) {
69
- const id = this.#nextId.toString();
70
- this.#nextId += 1;
71
- const params = parameters ?? [];
72
- const msg = { "@type": "subscribe", endpointName, id, methodName, params };
73
- this.#send(msg);
74
- this.#endpointInfos.set(id, { endpointName, methodName, params });
75
- this.#setSubscriptionConnState(id, "connecting" /* CONNECTING */);
76
- const hillaSubscription = {
77
- cancel: () => {
78
- if (!this.#endpointInfos.has(id)) {
79
- return;
80
- }
81
- const closeMessage = { "@type": "unsubscribe", id };
82
- this.#send(closeMessage);
83
- this.#removeSubscription(id);
84
- },
85
- context(context) {
86
- context.addController({
87
- hostDisconnected() {
88
- hillaSubscription.cancel();
89
- }
90
- });
91
- return hillaSubscription;
92
- },
93
- onComplete: (callback) => {
94
- this.#onCompleteCallbacks.set(id, callback);
95
- return hillaSubscription;
96
- },
97
- onError: (callback) => {
98
- this.#onErrorCallbacks.set(id, callback);
99
- return hillaSubscription;
100
- },
101
- onNext: (callback) => {
102
- this.#onNextCallbacks.set(id, callback);
103
- return hillaSubscription;
104
- },
105
- onSubscriptionLost: (callback) => {
106
- if (this.#endpointInfos.has(id)) {
107
- this.#endpointInfos.get(id).reconnect = callback;
108
- } else {
109
- console.warn(`"onReconnect" value not set for subscription "${id}" because it was already canceled`);
110
- }
111
- return hillaSubscription;
112
- },
113
- onConnectionStateChange: (callback) => {
114
- this.#onStateChangeCallbacks.set(id, callback);
115
- callback(
116
- new CustomEvent("subscription-state-change", { detail: { state: this.#statusOfSubscriptions.get(id) } })
117
- );
118
- return hillaSubscription;
119
- }
120
- };
121
- return hillaSubscription;
122
- }
123
- #connectWebsocket(prefix, atmosphereOptions) {
124
- const extraHeaders = globalThis.document ? getCsrfTokenHeadersForEndpointRequest(globalThis.document) : {};
125
- const pushUrl = "HILLA/push";
126
- const url = prefix.length === 0 ? pushUrl : (prefix.endsWith("/") ? prefix : `${prefix}/`) + pushUrl;
127
- this.#socket = atmosphere.subscribe?.({
128
- contentType: "application/json; charset=UTF-8",
129
- enableProtocol: true,
130
- transport: "websocket",
131
- fallbackTransport: "websocket",
132
- headers: extraHeaders,
133
- maxReconnectOnClose: 1e7,
134
- reconnectInterval: 5e3,
135
- timeout: -1,
136
- trackMessageLength: true,
137
- url,
138
- onClose: () => {
139
- this.wasClosed = true;
140
- if (this.state !== "inactive" /* INACTIVE */) {
141
- this.state = "inactive" /* INACTIVE */;
142
- this.dispatchEvent(new CustomEvent("state-changed", { detail: { active: false } }));
143
- }
144
- },
145
- onError: (response) => {
146
- console.error("error in push communication", response);
147
- },
148
- onMessage: (response) => {
149
- if (response.responseBody) {
150
- this.#handleMessage(JSON.parse(response.responseBody));
151
- }
152
- },
153
- onMessagePublished: (response) => {
154
- if (response?.responseBody) {
155
- this.#handleMessage(JSON.parse(response.responseBody));
156
- }
157
- },
158
- onOpen: () => {
159
- if (this.state !== "active" /* ACTIVE */) {
160
- this.#resubscribeIfWasClosed();
161
- this.state = "active" /* ACTIVE */;
162
- this.dispatchEvent(new CustomEvent("state-changed", { detail: { active: true } }));
163
- this.#sendPendingMessages();
164
- }
165
- },
166
- onReopen: () => {
167
- if (this.state !== "active" /* ACTIVE */) {
168
- this.#resubscribeIfWasClosed();
169
- this.state = "active" /* ACTIVE */;
170
- this.dispatchEvent(new CustomEvent("state-changed", { detail: { active: true } }));
171
- this.#sendPendingMessages();
172
- }
173
- },
174
- onReconnect: () => {
175
- if (this.state !== "reconnecting" /* RECONNECTING */) {
176
- this.state = "reconnecting" /* RECONNECTING */;
177
- this.#endpointInfos.forEach((_, id) => {
178
- this.#setSubscriptionConnState(id, "connecting" /* CONNECTING */);
179
- });
180
- }
181
- },
182
- onFailureToReconnect: () => {
183
- if (this.state !== "inactive" /* INACTIVE */) {
184
- this.state = "inactive" /* INACTIVE */;
185
- this.dispatchEvent(new CustomEvent("state-changed", { detail: { active: false } }));
186
- this.#endpointInfos.forEach((_, id) => this.#setSubscriptionConnState(id, "closed" /* CLOSED */));
187
- }
188
- },
189
- ...atmosphereOptions
190
- });
191
- }
192
- #setSubscriptionConnState(id, state) {
193
- const currentState = this.#statusOfSubscriptions.get(id);
194
- if (!currentState) {
195
- this.#statusOfSubscriptions.set(id, state);
196
- this.#onStateChangeCallbacks.get(id)?.(
197
- new CustomEvent("subscription-state-change", { detail: { state: this.#statusOfSubscriptions.get(id) } })
198
- );
199
- } else if (currentState !== state) {
200
- this.#statusOfSubscriptions.set(id, state);
201
- this.#onStateChangeCallbacks.get(id)?.(
202
- new CustomEvent("subscription-state-change", { detail: { state: this.#statusOfSubscriptions.get(id) } })
203
- );
204
- }
205
- }
206
- #handleMessage(message) {
207
- if (isClientMessage(message)) {
208
- const { id } = message;
209
- const endpointInfo = this.#endpointInfos.get(id);
210
- if (message["@type"] === "update") {
211
- const callback = this.#onNextCallbacks.get(id);
212
- if (callback) {
213
- callback(message.item);
214
- }
215
- this.#setSubscriptionConnState(id, "connected" /* CONNECTED */);
216
- } else if (message["@type"] === "complete") {
217
- this.#onCompleteCallbacks.get(id)?.();
218
- this.#removeSubscription(id);
219
- } else {
220
- const callback = this.#onErrorCallbacks.get(id);
221
- if (callback) {
222
- callback(message.message);
223
- }
224
- this.#removeSubscription(id);
225
- if (!callback) {
226
- throw new Error(
227
- endpointInfo ? `Error in ${endpointInfo.endpointName}.${endpointInfo.methodName}(${JSON.stringify(endpointInfo.params)}): ${message.message}` : `Error in unknown subscription: ${message.message}`
228
- );
229
- }
230
- }
231
- } else {
232
- throw new Error(`Unknown message from server: ${String(message)}`);
233
- }
234
- }
235
- #removeSubscription(id) {
236
- this.#setSubscriptionConnState(id, "closed" /* CLOSED */);
237
- this.#statusOfSubscriptions.delete(id);
238
- this.#onStateChangeCallbacks.delete(id);
239
- this.#onNextCallbacks.delete(id);
240
- this.#onCompleteCallbacks.delete(id);
241
- this.#onErrorCallbacks.delete(id);
242
- this.#endpointInfos.delete(id);
243
- }
244
- #send(message) {
245
- if (this.state === "inactive" /* INACTIVE */) {
246
- this.#pendingMessages.push(message);
247
- } else {
248
- this.#socket?.push?.(JSON.stringify(message));
249
- }
250
- }
251
- #sendPendingMessages() {
252
- this.#pendingMessages.forEach((msg) => this.#send(msg));
253
- this.#pendingMessages = [];
254
- }
3
+ import { isClientMessage } from "./FluxMessages.js";
4
+ export let State = function(State) {
5
+ State["ACTIVE"] = "active";
6
+ State["INACTIVE"] = "inactive";
7
+ State["RECONNECTING"] = "reconnecting";
8
+ return State;
9
+ }({});
10
+ /**
11
+ * Possible options for dealing with lost subscriptions after a websocket is reopened.
12
+ */
13
+ export let ActionOnLostSubscription = function(ActionOnLostSubscription) {
14
+ /**
15
+ * The subscription should be resubscribed using the same server method and parameters.
16
+ */
17
+ ActionOnLostSubscription["RESUBSCRIBE"] = "resubscribe";
18
+ /**
19
+ * The subscription should be removed.
20
+ */
21
+ ActionOnLostSubscription["REMOVE"] = "remove";
22
+ return ActionOnLostSubscription;
23
+ }({});
24
+ /**
25
+ * Possible states of a flux subscription.
26
+ */
27
+ export let FluxSubscriptionState = function(FluxSubscriptionState) {
28
+ /**
29
+ * The subscription is not connected and is trying to connect.
30
+ */
31
+ FluxSubscriptionState["CONNECTING"] = "connecting";
32
+ /**
33
+ * The subscription is connected and receiving updates.
34
+ */
35
+ FluxSubscriptionState["CONNECTED"] = "connected";
36
+ /**
37
+ * The subscription is closed and is not trying to reconnect.
38
+ */
39
+ FluxSubscriptionState["CLOSED"] = "closed";
40
+ return FluxSubscriptionState;
41
+ }({});
42
+ /**
43
+ * A representation of the underlying persistent network connection used for subscribing to Flux type endpoint methods.
44
+ */
45
+ export class FluxConnection extends EventTarget {
46
+ state = State.INACTIVE;
47
+ wasClosed = false;
48
+ #endpointInfos = new Map();
49
+ #nextId = 0;
50
+ #onCompleteCallbacks = new Map();
51
+ #onErrorCallbacks = new Map();
52
+ #onNextCallbacks = new Map();
53
+ #onStateChangeCallbacks = new Map();
54
+ #statusOfSubscriptions = new Map();
55
+ #pendingMessages = [];
56
+ #socket;
57
+ constructor(connectPrefix, atmosphereOptions) {
58
+ super();
59
+ this.#connectWebsocket(connectPrefix.replace(/connect$/u, ""), atmosphereOptions ?? {});
60
+ }
61
+ #resubscribeIfWasClosed() {
62
+ if (this.wasClosed) {
63
+ this.wasClosed = false;
64
+ const toBeRemoved = [];
65
+ this.#endpointInfos.forEach((endpointInfo, id) => {
66
+ if (endpointInfo.reconnect?.() === ActionOnLostSubscription.RESUBSCRIBE) {
67
+ this.#setSubscriptionConnState(id, FluxSubscriptionState.CONNECTING);
68
+ this.#send({
69
+ "@type": "subscribe",
70
+ endpointName: endpointInfo.endpointName,
71
+ id,
72
+ methodName: endpointInfo.methodName,
73
+ params: endpointInfo.params
74
+ });
75
+ } else {
76
+ toBeRemoved.push(id);
77
+ }
78
+ });
79
+ toBeRemoved.forEach((id) => this.#removeSubscription(id));
80
+ }
81
+ }
82
+ /**
83
+ * Subscribes to the flux returned by the given endpoint name + method name using the given parameters.
84
+ *
85
+ * @param endpointName - the endpoint to connect to
86
+ * @param methodName - the method in the endpoint to connect to
87
+ * @param parameters - the parameters to use
88
+ * @returns a subscription
89
+ */
90
+ subscribe(endpointName, methodName, parameters) {
91
+ const id = this.#nextId.toString();
92
+ this.#nextId += 1;
93
+ const params = parameters ?? [];
94
+ const msg = {
95
+ "@type": "subscribe",
96
+ endpointName,
97
+ id,
98
+ methodName,
99
+ params
100
+ };
101
+ this.#send(msg);
102
+ this.#endpointInfos.set(id, {
103
+ endpointName,
104
+ methodName,
105
+ params
106
+ });
107
+ this.#setSubscriptionConnState(id, FluxSubscriptionState.CONNECTING);
108
+ const hillaSubscription = {
109
+ cancel: () => {
110
+ if (!this.#endpointInfos.has(id)) {
111
+ return;
112
+ }
113
+ const closeMessage = {
114
+ "@type": "unsubscribe",
115
+ id
116
+ };
117
+ this.#send(closeMessage);
118
+ this.#removeSubscription(id);
119
+ },
120
+ context(context) {
121
+ context.addController({ hostDisconnected() {
122
+ hillaSubscription.cancel();
123
+ } });
124
+ return hillaSubscription;
125
+ },
126
+ onComplete: (callback) => {
127
+ this.#onCompleteCallbacks.set(id, callback);
128
+ return hillaSubscription;
129
+ },
130
+ onError: (callback) => {
131
+ this.#onErrorCallbacks.set(id, callback);
132
+ return hillaSubscription;
133
+ },
134
+ onNext: (callback) => {
135
+ this.#onNextCallbacks.set(id, callback);
136
+ return hillaSubscription;
137
+ },
138
+ onSubscriptionLost: (callback) => {
139
+ if (this.#endpointInfos.has(id)) {
140
+ this.#endpointInfos.get(id).reconnect = callback;
141
+ } else {
142
+ console.warn(`"onReconnect" value not set for subscription "${id}" because it was already canceled`);
143
+ }
144
+ return hillaSubscription;
145
+ },
146
+ onConnectionStateChange: (callback) => {
147
+ this.#onStateChangeCallbacks.set(id, callback);
148
+ callback(new CustomEvent("subscription-state-change", { detail: { state: this.#statusOfSubscriptions.get(id) } }));
149
+ return hillaSubscription;
150
+ }
151
+ };
152
+ return hillaSubscription;
153
+ }
154
+ #connectWebsocket(prefix, atmosphereOptions) {
155
+ const extraHeaders = globalThis.document ? getCsrfTokenHeadersForEndpointRequest(globalThis.document) : {};
156
+ const pushUrl = "HILLA/push";
157
+ const url = prefix.length === 0 ? pushUrl : (prefix.endsWith("/") ? prefix : `${prefix}/`) + pushUrl;
158
+ this.#socket = atmosphere.subscribe?.({
159
+ contentType: "application/json; charset=UTF-8",
160
+ enableProtocol: true,
161
+ transport: "websocket",
162
+ fallbackTransport: "websocket",
163
+ headers: extraHeaders,
164
+ maxReconnectOnClose: 1e7,
165
+ reconnectInterval: 5e3,
166
+ timeout: -1,
167
+ trackMessageLength: true,
168
+ url,
169
+ onClose: () => {
170
+ this.wasClosed = true;
171
+ if (this.state !== State.INACTIVE) {
172
+ this.state = State.INACTIVE;
173
+ this.dispatchEvent(new CustomEvent("state-changed", { detail: { active: false } }));
174
+ }
175
+ },
176
+ onError: (response) => {
177
+ console.error("error in push communication", response);
178
+ },
179
+ onMessage: (response) => {
180
+ if (response.responseBody) {
181
+ this.#handleMessage(JSON.parse(response.responseBody));
182
+ }
183
+ },
184
+ onMessagePublished: (response) => {
185
+ if (response?.responseBody) {
186
+ this.#handleMessage(JSON.parse(response.responseBody));
187
+ }
188
+ },
189
+ onOpen: () => {
190
+ if (this.state !== State.ACTIVE) {
191
+ this.#resubscribeIfWasClosed();
192
+ this.state = State.ACTIVE;
193
+ this.dispatchEvent(new CustomEvent("state-changed", { detail: { active: true } }));
194
+ this.#sendPendingMessages();
195
+ }
196
+ },
197
+ onReopen: () => {
198
+ if (this.state !== State.ACTIVE) {
199
+ this.#resubscribeIfWasClosed();
200
+ this.state = State.ACTIVE;
201
+ this.dispatchEvent(new CustomEvent("state-changed", { detail: { active: true } }));
202
+ this.#sendPendingMessages();
203
+ }
204
+ },
205
+ onReconnect: () => {
206
+ if (this.state !== State.RECONNECTING) {
207
+ this.state = State.RECONNECTING;
208
+ this.#endpointInfos.forEach((_, id) => {
209
+ this.#setSubscriptionConnState(id, FluxSubscriptionState.CONNECTING);
210
+ });
211
+ }
212
+ },
213
+ onFailureToReconnect: () => {
214
+ if (this.state !== State.INACTIVE) {
215
+ this.state = State.INACTIVE;
216
+ this.dispatchEvent(new CustomEvent("state-changed", { detail: { active: false } }));
217
+ this.#endpointInfos.forEach((_, id) => this.#setSubscriptionConnState(id, FluxSubscriptionState.CLOSED));
218
+ }
219
+ },
220
+ ...atmosphereOptions
221
+ });
222
+ }
223
+ #setSubscriptionConnState(id, state) {
224
+ const currentState = this.#statusOfSubscriptions.get(id);
225
+ if (!currentState) {
226
+ this.#statusOfSubscriptions.set(id, state);
227
+ this.#onStateChangeCallbacks.get(id)?.(new CustomEvent("subscription-state-change", { detail: { state: this.#statusOfSubscriptions.get(id) } }));
228
+ } else if (currentState !== state) {
229
+ this.#statusOfSubscriptions.set(id, state);
230
+ this.#onStateChangeCallbacks.get(id)?.(new CustomEvent("subscription-state-change", { detail: { state: this.#statusOfSubscriptions.get(id) } }));
231
+ }
232
+ }
233
+ #handleMessage(message) {
234
+ if (isClientMessage(message)) {
235
+ const { id } = message;
236
+ const endpointInfo = this.#endpointInfos.get(id);
237
+ if (message["@type"] === "update") {
238
+ const callback = this.#onNextCallbacks.get(id);
239
+ if (callback) {
240
+ callback(message.item);
241
+ }
242
+ this.#setSubscriptionConnState(id, FluxSubscriptionState.CONNECTED);
243
+ } else if (message["@type"] === "complete") {
244
+ this.#onCompleteCallbacks.get(id)?.();
245
+ this.#removeSubscription(id);
246
+ } else {
247
+ const callback = this.#onErrorCallbacks.get(id);
248
+ if (callback) {
249
+ callback(message.message);
250
+ }
251
+ this.#removeSubscription(id);
252
+ if (!callback) {
253
+ throw new Error(endpointInfo ? `Error in ${endpointInfo.endpointName}.${endpointInfo.methodName}(${JSON.stringify(endpointInfo.params)}): ${message.message}` : `Error in unknown subscription: ${message.message}`);
254
+ }
255
+ }
256
+ } else {
257
+ throw new Error(`Unknown message from server: ${String(message)}`);
258
+ }
259
+ }
260
+ #removeSubscription(id) {
261
+ this.#setSubscriptionConnState(id, FluxSubscriptionState.CLOSED);
262
+ this.#statusOfSubscriptions.delete(id);
263
+ this.#onStateChangeCallbacks.delete(id);
264
+ this.#onNextCallbacks.delete(id);
265
+ this.#onCompleteCallbacks.delete(id);
266
+ this.#onErrorCallbacks.delete(id);
267
+ this.#endpointInfos.delete(id);
268
+ }
269
+ #send(message) {
270
+ if (this.state === State.INACTIVE) {
271
+ this.#pendingMessages.push(message);
272
+ } else {
273
+ this.#socket?.push?.(JSON.stringify(message));
274
+ }
275
+ }
276
+ #sendPendingMessages() {
277
+ this.#pendingMessages.forEach((msg) => this.#send(msg));
278
+ this.#pendingMessages = [];
279
+ }
255
280
  }
256
- export {
257
- ActionOnLostSubscription,
258
- FluxConnection,
259
- FluxSubscriptionState,
260
- State
261
- };
262
- //# sourceMappingURL=FluxConnection.js.map
281
+ //# sourceMappingURL=./FluxConnection.js.map