@lowdefy/client 6.0.0 → 7.0.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/dist/Client.js +54 -17
- package/dist/Context.js +30 -12
- package/dist/HtmlOverlay.js +127 -0
- package/dist/IconProvider.js +24 -0
- package/dist/MountEvents.js +3 -3
- package/dist/adapters/createRouter.js +9 -3
- package/dist/adapters/url.js +1 -7
- package/dist/auth/createAuthMethods.js +1034 -41
- package/dist/block/Block.js +6 -3
- package/dist/block/CategorySwitch.js +6 -0
- package/dist/block/Container.js +3 -0
- package/dist/block/InputContainer.js +8 -2
- package/dist/block/List.js +3 -0
- package/dist/block/LoadingBlock.js +6 -12
- package/dist/block/LoadingContainer.js +6 -1
- package/dist/block/LoadingList.js +6 -1
- package/dist/block/loadingBlockMethods.js +26 -0
- package/dist/claimCallerReload.js +38 -0
- package/dist/createAntdIconConfig.js +91 -0
- package/dist/createCallAPI.js +6 -2
- package/dist/createCallRequest.js +6 -2
- package/dist/createHandleError.js +21 -11
- package/dist/createIcon.js +138 -36
- package/dist/createPageLifecycleManager.js +172 -0
- package/dist/formatIconTitle.js +26 -0
- package/dist/getActiveLocale.js +27 -0
- package/dist/getContextKey.js +29 -0
- package/dist/getMessageIcon.js +43 -0
- package/dist/hasOtherRoles.js +30 -0
- package/dist/index.js +1 -0
- package/dist/initLowdefyContext.js +103 -4
- package/dist/isOtherCaller.js +29 -0
- package/dist/recordCallerStamp.js +31 -0
- package/dist/renderIconNodes.js +46 -0
- package/dist/request.js +74 -6
- package/dist/setLowdefyUser.js +32 -0
- package/dist/setupLink.js +19 -13
- package/dist/shouldReloadForAuthError.js +39 -0
- package/dist/shouldReloadForBuild.js +44 -0
- package/dist/shouldReloadForCaller.js +50 -0
- package/dist/storage/clearUserStorage.js +36 -0
- package/dist/storage/clearUserStorageOnOwnerChange.js +41 -0
- package/dist/storage/createStorage.js +48 -0
- package/dist/storage/listUserStorageKeys.js +36 -0
- package/dist/storage/readStorageItem.js +31 -0
- package/dist/storage/userStorageKey.js +24 -0
- package/dist/storage/userStoragePrefix.js +25 -0
- package/dist/useDarkMode.js +17 -4
- package/dist/useLocale.js +18 -5
- package/dist/websocket/createWebSocketClient.js +239 -100
- package/package.json +11 -10
|
@@ -12,31 +12,53 @@
|
|
|
12
12
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
|
-
*/
|
|
15
|
+
*/ import { decodeServerError } from '@lowdefy/engine';
|
|
16
|
+
import { ServiceError } from '@lowdefy/errors';
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
const ACK_TIMEOUT_MS = 10 * 1000;
|
|
16
19
|
const IDLE_CLOSE_GRACE_MS = 5 * 1000;
|
|
17
20
|
const RECONNECT_BASE_MS = 500;
|
|
18
21
|
const RECONNECT_CAP_MS = 15 * 1000;
|
|
22
|
+
// A connection that stays open this long resets the reconnect backoff. A
|
|
23
|
+
// server that accepts connections and drops them straight away keeps backing
|
|
24
|
+
// off instead of being retried every half second.
|
|
25
|
+
const STABLE_CONNECTION_MS = 10 * 1000;
|
|
26
|
+
// The server closes with this code when the socket's caller signed out or
|
|
27
|
+
// changed (user, organization or roles). The client reconnects straight away,
|
|
28
|
+
// which authenticates afresh, and resubscribes as the new caller.
|
|
29
|
+
const CALLER_CHANGED_CODE = 4401;
|
|
30
|
+
// A connection closed for a caller change again within this long of opening
|
|
31
|
+
// reconnects with the normal backoff, so a server that keeps closing for that
|
|
32
|
+
// reason does not get a tight reconnect loop.
|
|
33
|
+
const CALLER_CHANGE_REPEAT_MS = 30 * 1000;
|
|
19
34
|
// One websocket per browser tab, shared by all channels, created lazily on
|
|
20
35
|
// the first subscribe or publish. Connections are ephemeral in production
|
|
21
36
|
// (serverless platforms close them at function max duration), so reconnect
|
|
22
37
|
// with backoff and resubscribe is the steady state, not an edge case.
|
|
23
38
|
function createWebSocketClient(lowdefy) {
|
|
24
39
|
const { window } = lowdefy._internal.globals;
|
|
25
|
-
// websocketId → { payload, handlers }
|
|
40
|
+
// websocketId → { websocketId, payload, handlers, requestId, ackTimer, pending }
|
|
41
|
+
// requestId names the last subscribe frame sent, which the server echoes in
|
|
42
|
+
// its reply. ackTimer runs while that frame waits for its ack. pending holds
|
|
43
|
+
// the subscribe promise's { resolve, reject } until the first ack, then is
|
|
44
|
+
// null.
|
|
26
45
|
const subscriptions = new Map();
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
//
|
|
46
|
+
// requestId → { frame, sent, resolve, reject, timer } for publishes waiting
|
|
47
|
+
// for their ack. A publish made while disconnected is sent when the
|
|
48
|
+
// connection opens, unless it timed out first.
|
|
30
49
|
const pendingPublishes = new Map();
|
|
31
|
-
// Frames queued while the socket is (re)connecting.
|
|
32
|
-
const sendQueue = [];
|
|
33
50
|
let socket = null;
|
|
34
51
|
let openPromise = null;
|
|
35
52
|
let reconnectAttempt = 0;
|
|
36
53
|
let reconnectTimer = null;
|
|
37
54
|
let idleTimer = null;
|
|
38
|
-
let
|
|
55
|
+
let stableTimer = null;
|
|
56
|
+
// When the current connection opened, and whether it was opened by the
|
|
57
|
+
// reconnect that follows a caller change.
|
|
58
|
+
let openedAt = null;
|
|
59
|
+
let reconnectingAfterCallerChange = false;
|
|
39
60
|
let publishCounter = 0;
|
|
61
|
+
let subscribeCounter = 0;
|
|
40
62
|
function url() {
|
|
41
63
|
const { location } = window;
|
|
42
64
|
const scheme = location.protocol === 'https:' ? 'wss' : 'ws';
|
|
@@ -48,114 +70,207 @@ function createWebSocketClient(lowdefy) {
|
|
|
48
70
|
idleTimer = null;
|
|
49
71
|
}
|
|
50
72
|
}
|
|
73
|
+
function isIdle() {
|
|
74
|
+
return subscriptions.size === 0 && pendingPublishes.size === 0;
|
|
75
|
+
}
|
|
51
76
|
function scheduleIdleClose() {
|
|
52
77
|
clearIdleTimer();
|
|
53
|
-
if (
|
|
78
|
+
if (!isIdle()) {
|
|
54
79
|
return;
|
|
55
80
|
}
|
|
56
81
|
// Grace period so rapid page navigation doesn't thrash connections.
|
|
57
82
|
idleTimer = setTimeout(()=>{
|
|
58
|
-
if (
|
|
59
|
-
|
|
83
|
+
if (isIdle() && socket) {
|
|
84
|
+
// The close event arrives after the closing handshake, when a new
|
|
85
|
+
// socket may already be connecting or open. This socket is finished
|
|
86
|
+
// with, so its events must not reach the client state that now
|
|
87
|
+
// belongs to the new socket.
|
|
88
|
+
socket.onclose = null;
|
|
89
|
+
socket.onmessage = null;
|
|
90
|
+
clearTimeout(stableTimer);
|
|
91
|
+
reconnectingAfterCallerChange = false;
|
|
60
92
|
socket.close();
|
|
61
93
|
socket = null;
|
|
62
94
|
openPromise = null;
|
|
63
95
|
}
|
|
64
96
|
}, IDLE_CLOSE_GRACE_MS);
|
|
65
97
|
}
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
98
|
+
function isSocketOpen() {
|
|
99
|
+
return socket !== null && socket.readyState === window.WebSocket.OPEN;
|
|
100
|
+
}
|
|
101
|
+
function sendPublish(pending) {
|
|
102
|
+
pending.sent = true;
|
|
103
|
+
socket.send(JSON.stringify(pending.frame));
|
|
104
|
+
}
|
|
105
|
+
function hasUnsentPublishes() {
|
|
106
|
+
return [
|
|
107
|
+
...pendingPublishes.values()
|
|
108
|
+
].some((pending)=>!pending.sent);
|
|
109
|
+
}
|
|
110
|
+
function sendUnsentPublishes() {
|
|
111
|
+
pendingPublishes.forEach((pending)=>{
|
|
112
|
+
if (!pending.sent) {
|
|
113
|
+
sendPublish(pending);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function clearAckTimer(subscription) {
|
|
118
|
+
clearTimeout(subscription.ackTimer);
|
|
119
|
+
subscription.ackTimer = null;
|
|
120
|
+
}
|
|
121
|
+
function handleAckTimeout(subscription) {
|
|
122
|
+
subscription.ackTimer = null;
|
|
123
|
+
const error = new ServiceError(`Subscribe to "${subscription.websocketId}" timed out.`, {
|
|
124
|
+
service: 'WebSocket'
|
|
125
|
+
});
|
|
126
|
+
if (subscription.pending) {
|
|
127
|
+
subscriptions.delete(subscription.websocketId);
|
|
128
|
+
// The server handles the frame late, or not yet, and would open the feed
|
|
129
|
+
// for a caller that has given up on it.
|
|
130
|
+
if (isSocketOpen()) {
|
|
131
|
+
socket.send(JSON.stringify({
|
|
132
|
+
type: 'unsubscribe',
|
|
133
|
+
websocketId: subscription.websocketId
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
scheduleIdleClose();
|
|
137
|
+
subscription.pending.reject(error);
|
|
70
138
|
return;
|
|
71
139
|
}
|
|
72
|
-
|
|
73
|
-
|
|
140
|
+
subscription.handlers.onError(error.message);
|
|
141
|
+
}
|
|
142
|
+
// Every path that removes a subscription from the map or answers its
|
|
143
|
+
// subscribe clears this timer, so a timer that fires always belongs to the
|
|
144
|
+
// subscription currently in the map.
|
|
145
|
+
function startAckTimer(subscription) {
|
|
146
|
+
clearAckTimer(subscription);
|
|
147
|
+
subscription.ackTimer = setTimeout(()=>handleAckTimeout(subscription), ACK_TIMEOUT_MS);
|
|
148
|
+
}
|
|
149
|
+
function sendSubscribe(subscription) {
|
|
150
|
+
const { payload, websocketId } = subscription;
|
|
151
|
+
subscribeCounter += 1;
|
|
152
|
+
subscription.requestId = `s${subscribeCounter}`;
|
|
153
|
+
startAckTimer(subscription);
|
|
154
|
+
socket.send(JSON.stringify({
|
|
155
|
+
type: 'subscribe',
|
|
156
|
+
websocketId,
|
|
157
|
+
payload,
|
|
158
|
+
requestId: subscription.requestId
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
// A reply to an earlier subscribe frame for the same feed, one that a newer
|
|
162
|
+
// subscribe or a resubscribe replaced, does not answer this subscription. A
|
|
163
|
+
// server that does not echo requestId answers by websocketId alone.
|
|
164
|
+
function answersSubscription(subscription, requestId) {
|
|
165
|
+
return type.isNone(requestId) || requestId === subscription.requestId;
|
|
166
|
+
}
|
|
167
|
+
function settlePublish(requestId) {
|
|
168
|
+
const pending = pendingPublishes.get(requestId);
|
|
169
|
+
clearTimeout(pending.timer);
|
|
170
|
+
pendingPublishes.delete(requestId);
|
|
171
|
+
scheduleIdleClose();
|
|
172
|
+
return pending;
|
|
74
173
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
174
|
+
// A caller that unsubscribed, or whose subscribe was replaced by a newer one
|
|
175
|
+
// to the same feed, no longer wants this subscription, so its subscribe
|
|
176
|
+
// promise settles without an error.
|
|
177
|
+
function releaseSubscription(subscription) {
|
|
178
|
+
clearAckTimer(subscription);
|
|
179
|
+
if (subscription.pending) {
|
|
180
|
+
subscription.pending.resolve();
|
|
181
|
+
subscription.pending = null;
|
|
78
182
|
}
|
|
79
183
|
}
|
|
80
184
|
function resubscribeAll() {
|
|
81
|
-
subscriptions.forEach(
|
|
82
|
-
socket.send(JSON.stringify({
|
|
83
|
-
type: 'subscribe',
|
|
84
|
-
websocketId,
|
|
85
|
-
payload
|
|
86
|
-
}));
|
|
87
|
-
});
|
|
185
|
+
subscriptions.forEach(sendSubscribe);
|
|
88
186
|
}
|
|
89
187
|
function handleFrame(frame) {
|
|
90
|
-
const { message, payload, requestId, websocketId } = frame;
|
|
188
|
+
const { error: errorPayload, message, payload, requestId, websocketId } = frame;
|
|
91
189
|
const subscription = subscriptions.get(websocketId);
|
|
92
190
|
switch(frame.type){
|
|
93
191
|
case 'message':
|
|
94
192
|
subscription?.handlers.onMessage(payload);
|
|
95
193
|
return;
|
|
96
194
|
case 'subscribed':
|
|
97
|
-
{
|
|
98
|
-
const pending = pendingSubscribes.get(websocketId);
|
|
99
|
-
if (pending) {
|
|
100
|
-
clearTimeout(pending.timer);
|
|
101
|
-
pendingSubscribes.delete(websocketId);
|
|
102
|
-
pending.resolve();
|
|
103
|
-
}
|
|
104
|
-
subscription?.handlers.onConnected();
|
|
195
|
+
if (!subscription || !answersSubscription(subscription, requestId)) {
|
|
105
196
|
return;
|
|
106
197
|
}
|
|
198
|
+
clearAckTimer(subscription);
|
|
199
|
+
if (subscription.pending) {
|
|
200
|
+
subscription.pending.resolve();
|
|
201
|
+
subscription.pending = null;
|
|
202
|
+
}
|
|
203
|
+
subscription.handlers.onConnected();
|
|
204
|
+
return;
|
|
107
205
|
case 'unsubscribed':
|
|
108
206
|
return;
|
|
109
207
|
case 'published':
|
|
110
|
-
{
|
|
111
|
-
|
|
112
|
-
if (pending) {
|
|
113
|
-
clearTimeout(pending.timer);
|
|
114
|
-
pendingPublishes.delete(requestId);
|
|
115
|
-
pending.resolve();
|
|
116
|
-
}
|
|
117
|
-
return;
|
|
208
|
+
if (pendingPublishes.has(requestId)) {
|
|
209
|
+
settlePublish(requestId).resolve();
|
|
118
210
|
}
|
|
211
|
+
return;
|
|
119
212
|
case 'error':
|
|
120
213
|
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
214
|
+
// Reply frames carry the error payload; broadcasts and frame-format
|
|
215
|
+
// errors carry only a message string.
|
|
216
|
+
const error = errorPayload ? decodeServerError(errorPayload) : new ServiceError(message ?? 'WebSocket error.', {
|
|
217
|
+
service: 'WebSocket'
|
|
218
|
+
});
|
|
219
|
+
if (!type.isNone(requestId) && pendingPublishes.has(requestId)) {
|
|
220
|
+
settlePublish(requestId).reject(error);
|
|
127
221
|
return;
|
|
128
222
|
}
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
clearTimeout(pending.timer);
|
|
132
|
-
pendingSubscribes.delete(websocketId);
|
|
133
|
-
subscriptions.delete(websocketId);
|
|
134
|
-
pending.reject(error);
|
|
223
|
+
if (!subscription) {
|
|
224
|
+
lowdefy._internal.logger.warn(error);
|
|
135
225
|
return;
|
|
136
226
|
}
|
|
137
|
-
|
|
227
|
+
// A broadcast from the feed's source carries no requestId.
|
|
228
|
+
if (type.isNone(requestId)) {
|
|
138
229
|
subscription.handlers.onError(error.message);
|
|
139
230
|
return;
|
|
140
231
|
}
|
|
141
|
-
|
|
232
|
+
if (requestId !== subscription.requestId) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
clearAckTimer(subscription);
|
|
236
|
+
if (subscription.pending) {
|
|
237
|
+
subscriptions.delete(websocketId);
|
|
238
|
+
scheduleIdleClose();
|
|
239
|
+
subscription.pending.reject(error);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
subscription.handlers.onError(error.message);
|
|
142
243
|
return;
|
|
143
244
|
}
|
|
144
245
|
default:
|
|
145
246
|
}
|
|
146
247
|
}
|
|
147
|
-
function handleClose() {
|
|
248
|
+
function handleClose(event) {
|
|
148
249
|
socket = null;
|
|
149
250
|
openPromise = null;
|
|
150
|
-
|
|
151
|
-
|
|
251
|
+
clearTimeout(stableTimer);
|
|
252
|
+
subscriptions.forEach((subscription)=>{
|
|
253
|
+
// A resubscribe lost with the connection is sent again, with a new ack
|
|
254
|
+
// timer, when the connection reopens. A subscribe whose caller still
|
|
255
|
+
// waits keeps its timer, so it fails if no connection opens in time.
|
|
256
|
+
if (!subscription.pending) {
|
|
257
|
+
clearAckTimer(subscription);
|
|
258
|
+
}
|
|
259
|
+
subscription.handlers.onDisconnected();
|
|
152
260
|
});
|
|
153
|
-
if (
|
|
154
|
-
closedByClient = false;
|
|
261
|
+
if (subscriptions.size === 0 && !hasUnsentPublishes()) {
|
|
155
262
|
return;
|
|
156
263
|
}
|
|
157
|
-
|
|
158
|
-
|
|
264
|
+
// A caller change is not a failing server, so the first reconnect after
|
|
265
|
+
// one does not back off.
|
|
266
|
+
if (event.code === CALLER_CHANGED_CODE) {
|
|
267
|
+
const repeated = reconnectingAfterCallerChange && Date.now() - openedAt < CALLER_CHANGE_REPEAT_MS;
|
|
268
|
+
if (!repeated) {
|
|
269
|
+
reconnectAttempt = 0;
|
|
270
|
+
}
|
|
271
|
+
reconnectingAfterCallerChange = true;
|
|
272
|
+
} else {
|
|
273
|
+
reconnectingAfterCallerChange = false;
|
|
159
274
|
}
|
|
160
275
|
// Capped exponential backoff with full jitter.
|
|
161
276
|
const base = Math.min(RECONNECT_BASE_MS * 2 ** reconnectAttempt, RECONNECT_CAP_MS);
|
|
@@ -175,9 +290,15 @@ function createWebSocketClient(lowdefy) {
|
|
|
175
290
|
const ws = new window.WebSocket(url());
|
|
176
291
|
ws.onopen = ()=>{
|
|
177
292
|
socket = ws;
|
|
178
|
-
|
|
293
|
+
openedAt = Date.now();
|
|
294
|
+
stableTimer = setTimeout(()=>{
|
|
295
|
+
reconnectAttempt = 0;
|
|
296
|
+
}, STABLE_CONNECTION_MS);
|
|
179
297
|
resubscribeAll();
|
|
180
|
-
|
|
298
|
+
sendUnsentPublishes();
|
|
299
|
+
// Everything that asked for this connection may have finished while
|
|
300
|
+
// it was opening.
|
|
301
|
+
scheduleIdleClose();
|
|
181
302
|
resolve();
|
|
182
303
|
};
|
|
183
304
|
ws.onmessage = (event)=>{
|
|
@@ -189,13 +310,15 @@ function createWebSocketClient(lowdefy) {
|
|
|
189
310
|
}
|
|
190
311
|
handleFrame(frame);
|
|
191
312
|
};
|
|
192
|
-
ws.onclose = ()=>{
|
|
313
|
+
ws.onclose = (event)=>{
|
|
193
314
|
if (socket !== ws) {
|
|
194
315
|
// Never opened — treat as a failed connect and retry.
|
|
195
316
|
openPromise = null;
|
|
196
|
-
reject(new
|
|
317
|
+
reject(new ServiceError('Connection failed.', {
|
|
318
|
+
service: 'WebSocket'
|
|
319
|
+
}));
|
|
197
320
|
}
|
|
198
|
-
handleClose();
|
|
321
|
+
handleClose(event);
|
|
199
322
|
};
|
|
200
323
|
ws.onerror = ()=>{
|
|
201
324
|
// onclose always follows onerror — reconnect is handled there.
|
|
@@ -207,34 +330,41 @@ function createWebSocketClient(lowdefy) {
|
|
|
207
330
|
}
|
|
208
331
|
function subscribe({ handlers, payload, websocketId }) {
|
|
209
332
|
clearIdleTimer();
|
|
210
|
-
subscriptions.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
333
|
+
const previous = subscriptions.get(websocketId);
|
|
334
|
+
if (previous) {
|
|
335
|
+
releaseSubscription(previous);
|
|
336
|
+
}
|
|
214
337
|
return new Promise((resolve, reject)=>{
|
|
215
|
-
const
|
|
216
|
-
pendingSubscribes.delete(websocketId);
|
|
217
|
-
subscriptions.delete(websocketId);
|
|
218
|
-
reject(new Error(`Subscribe to "${websocketId}" timed out.`));
|
|
219
|
-
}, ACK_TIMEOUT_MS);
|
|
220
|
-
pendingSubscribes.set(websocketId, {
|
|
221
|
-
resolve,
|
|
222
|
-
reject,
|
|
223
|
-
timer
|
|
224
|
-
});
|
|
225
|
-
send({
|
|
226
|
-
type: 'subscribe',
|
|
338
|
+
const subscription = {
|
|
227
339
|
websocketId,
|
|
228
|
-
payload
|
|
229
|
-
|
|
340
|
+
payload,
|
|
341
|
+
handlers,
|
|
342
|
+
ackTimer: null,
|
|
343
|
+
pending: {
|
|
344
|
+
resolve,
|
|
345
|
+
reject
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
subscriptions.set(websocketId, subscription);
|
|
349
|
+
if (isSocketOpen()) {
|
|
350
|
+
sendSubscribe(subscription);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
// The frame is sent by resubscribeAll when the connection opens, which
|
|
354
|
+
// restarts the ack timer. Until then the timer bounds the wait for a
|
|
355
|
+
// connection.
|
|
356
|
+
startAckTimer(subscription);
|
|
357
|
+
connect();
|
|
230
358
|
});
|
|
231
359
|
}
|
|
232
360
|
function unsubscribe({ websocketId }) {
|
|
233
|
-
|
|
361
|
+
const subscription = subscriptions.get(websocketId);
|
|
362
|
+
if (!subscription) {
|
|
234
363
|
return;
|
|
235
364
|
}
|
|
365
|
+
releaseSubscription(subscription);
|
|
236
366
|
subscriptions.delete(websocketId);
|
|
237
|
-
if (
|
|
367
|
+
if (isSocketOpen()) {
|
|
238
368
|
socket.send(JSON.stringify({
|
|
239
369
|
type: 'unsubscribe',
|
|
240
370
|
websocketId
|
|
@@ -243,24 +373,33 @@ function createWebSocketClient(lowdefy) {
|
|
|
243
373
|
scheduleIdleClose();
|
|
244
374
|
}
|
|
245
375
|
function publish({ payload, websocketId }) {
|
|
376
|
+
clearIdleTimer();
|
|
246
377
|
publishCounter += 1;
|
|
247
378
|
const requestId = `p${publishCounter}`;
|
|
248
379
|
return new Promise((resolve, reject)=>{
|
|
249
380
|
const timer = setTimeout(()=>{
|
|
250
|
-
|
|
251
|
-
|
|
381
|
+
settlePublish(requestId).reject(new ServiceError(`Publish to "${websocketId}" timed out.`, {
|
|
382
|
+
service: 'WebSocket'
|
|
383
|
+
}));
|
|
252
384
|
}, ACK_TIMEOUT_MS);
|
|
253
|
-
|
|
385
|
+
const pending = {
|
|
386
|
+
frame: {
|
|
387
|
+
type: 'publish',
|
|
388
|
+
websocketId,
|
|
389
|
+
requestId,
|
|
390
|
+
payload
|
|
391
|
+
},
|
|
392
|
+
sent: false,
|
|
254
393
|
resolve,
|
|
255
394
|
reject,
|
|
256
395
|
timer
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
396
|
+
};
|
|
397
|
+
pendingPublishes.set(requestId, pending);
|
|
398
|
+
if (isSocketOpen()) {
|
|
399
|
+
sendPublish(pending);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
connect();
|
|
264
403
|
});
|
|
265
404
|
}
|
|
266
405
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Lowdefy Client",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -33,22 +33,23 @@
|
|
|
33
33
|
"dist/*"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@ant-design/icons": "6.
|
|
37
|
-
"
|
|
38
|
-
"@lowdefy/
|
|
39
|
-
"@lowdefy/
|
|
40
|
-
"@lowdefy/
|
|
41
|
-
"@lowdefy/
|
|
42
|
-
"@lowdefy/
|
|
43
|
-
"
|
|
36
|
+
"@ant-design/icons": "6.3.4",
|
|
37
|
+
"@lowdefy/block-utils": "7.0.0",
|
|
38
|
+
"@lowdefy/engine": "7.0.0",
|
|
39
|
+
"@lowdefy/errors": "7.0.0",
|
|
40
|
+
"@lowdefy/helpers": "7.0.0",
|
|
41
|
+
"@lowdefy/layout": "7.0.0",
|
|
42
|
+
"@lowdefy/logger": "7.0.0",
|
|
43
|
+
"antd": "6.6.5",
|
|
44
44
|
"dayjs": "1.11.20",
|
|
45
|
+
"lucide-react": "1.48.0",
|
|
45
46
|
"react": "18.2.0",
|
|
46
47
|
"react-dom": "18.2.0",
|
|
47
48
|
"tinykeys": "^3.0.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@jest/globals": "28.1.3",
|
|
51
|
-
"@lowdefy/jest-yaml-transform": "
|
|
52
|
+
"@lowdefy/jest-yaml-transform": "7.0.0",
|
|
52
53
|
"@swc/cli": "0.8.1",
|
|
53
54
|
"@swc/core": "1.15.32",
|
|
54
55
|
"@swc/jest": "0.2.39",
|