@lowdefy/client 6.1.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.
Files changed (51) hide show
  1. package/dist/Client.js +54 -17
  2. package/dist/Context.js +30 -12
  3. package/dist/HtmlOverlay.js +127 -0
  4. package/dist/IconProvider.js +24 -0
  5. package/dist/MountEvents.js +3 -3
  6. package/dist/adapters/createRouter.js +9 -3
  7. package/dist/adapters/url.js +1 -7
  8. package/dist/auth/createAuthMethods.js +1034 -41
  9. package/dist/block/Block.js +6 -3
  10. package/dist/block/CategorySwitch.js +6 -0
  11. package/dist/block/Container.js +3 -0
  12. package/dist/block/InputContainer.js +8 -2
  13. package/dist/block/List.js +3 -0
  14. package/dist/block/LoadingBlock.js +6 -12
  15. package/dist/block/LoadingContainer.js +6 -1
  16. package/dist/block/LoadingList.js +6 -1
  17. package/dist/block/loadingBlockMethods.js +26 -0
  18. package/dist/claimCallerReload.js +38 -0
  19. package/dist/createAntdIconConfig.js +91 -0
  20. package/dist/createCallAPI.js +6 -2
  21. package/dist/createCallRequest.js +6 -2
  22. package/dist/createHandleError.js +6 -4
  23. package/dist/createIcon.js +138 -36
  24. package/dist/createPageLifecycleManager.js +172 -0
  25. package/dist/formatIconTitle.js +26 -0
  26. package/dist/getActiveLocale.js +27 -0
  27. package/dist/getContextKey.js +29 -0
  28. package/dist/getMessageIcon.js +43 -0
  29. package/dist/hasOtherRoles.js +30 -0
  30. package/dist/index.js +1 -0
  31. package/dist/initLowdefyContext.js +103 -4
  32. package/dist/isOtherCaller.js +29 -0
  33. package/dist/recordCallerStamp.js +31 -0
  34. package/dist/renderIconNodes.js +46 -0
  35. package/dist/request.js +72 -5
  36. package/dist/setLowdefyUser.js +32 -0
  37. package/dist/setupLink.js +19 -13
  38. package/dist/shouldReloadForAuthError.js +39 -0
  39. package/dist/shouldReloadForBuild.js +44 -0
  40. package/dist/shouldReloadForCaller.js +50 -0
  41. package/dist/storage/clearUserStorage.js +36 -0
  42. package/dist/storage/clearUserStorageOnOwnerChange.js +41 -0
  43. package/dist/storage/createStorage.js +48 -0
  44. package/dist/storage/listUserStorageKeys.js +36 -0
  45. package/dist/storage/readStorageItem.js +31 -0
  46. package/dist/storage/userStorageKey.js +24 -0
  47. package/dist/storage/userStoragePrefix.js +25 -0
  48. package/dist/useDarkMode.js +17 -4
  49. package/dist/useLocale.js +18 -5
  50. package/dist/websocket/createWebSocketClient.js +234 -98
  51. package/package.json +11 -10
package/dist/useLocale.js CHANGED
@@ -14,6 +14,10 @@
14
14
  limitations under the License.
15
15
  */ import { useCallback, useEffect, useMemo, useState } from 'react';
16
16
  import dayjs from 'dayjs';
17
+ import antdEnUS from 'antd/locale/en_US.js';
18
+ import readStorageItem from './storage/readStorageItem.js';
19
+ // antd shows its en_US strings until an app locale loads, and with none configured.
20
+ const defaultAntdLocale = antdEnUS.default ?? antdEnUS;
17
21
  function pickBest(candidates, supported) {
18
22
  for (const candidate of candidates ?? []){
19
23
  if (supported.includes(candidate)) return candidate;
@@ -40,12 +44,17 @@ async function loadAntdXLocale({ active, antdXLocaleLoaders }) {
40
44
  const mod = await antdXLocaleLoaders[active]();
41
45
  return mod?.default ?? mod;
42
46
  }
43
- function useLocale({ i18n, antdLocaleLoaders, antdXLocaleLoaders, dayjsLocaleMap }) {
47
+ // storageKey is the app key the preference is kept under (see @lowdefy/helpers appStorageKey).
48
+ function useLocale({ i18n, antdLocaleLoaders, antdXLocaleLoaders, dayjsLocaleMap, storageKey }) {
44
49
  const supportedCodes = useMemo(()=>(i18n?.locales ?? []).map((l)=>l.code), [
45
50
  i18n?.locales
46
51
  ]);
47
52
  const [userPreference, setUserPreference] = useState(()=>{
48
- return window.localStorage?.getItem('lowdefy_locale') ?? 'auto';
53
+ return readStorageItem({
54
+ key: storageKey,
55
+ legacyKey: 'lowdefy_locale',
56
+ storage: window.localStorage
57
+ }) ?? 'auto';
49
58
  });
50
59
  const browserLocale = useMemo(()=>{
51
60
  if (supportedCodes.length === 0) return undefined;
@@ -58,12 +67,14 @@ function useLocale({ i18n, antdLocaleLoaders, antdXLocaleLoaders, dayjsLocaleMap
58
67
  ]);
59
68
  const setPreference = useCallback((code)=>{
60
69
  if (code === 'auto') {
61
- window.localStorage?.removeItem('lowdefy_locale');
70
+ window.localStorage?.removeItem(storageKey);
62
71
  } else {
63
- window.localStorage?.setItem('lowdefy_locale', code);
72
+ window.localStorage?.setItem(storageKey, code);
64
73
  }
65
74
  setUserPreference(code ?? 'auto');
66
- }, []);
75
+ }, [
76
+ storageKey
77
+ ]);
67
78
  window.__lowdefy_setLocale = setPreference;
68
79
  const active = resolveActive({
69
80
  userPreference,
@@ -75,6 +86,8 @@ function useLocale({ i18n, antdLocaleLoaders, antdXLocaleLoaders, dayjsLocaleMap
75
86
  window.__lowdefy_supported_locales = i18n?.locales ?? [];
76
87
  const [antdLocale, setAntdLocale] = useState(null);
77
88
  const [antdXLocale, setAntdXLocale] = useState(null);
89
+ // The engine trace reads the antd strings in use as config text; it has no antd dependency.
90
+ window.__lowdefy_antd_locale = antdLocale ?? defaultAntdLocale;
78
91
  useEffect(()=>{
79
92
  let cancelled = false;
80
93
  loadAntdLocale({
@@ -13,31 +13,52 @@
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';
16
18
  const ACK_TIMEOUT_MS = 10 * 1000;
17
19
  const IDLE_CLOSE_GRACE_MS = 5 * 1000;
18
20
  const RECONNECT_BASE_MS = 500;
19
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;
20
34
  // One websocket per browser tab, shared by all channels, created lazily on
21
35
  // the first subscribe or publish. Connections are ephemeral in production
22
36
  // (serverless platforms close them at function max duration), so reconnect
23
37
  // with backoff and resubscribe is the steady state, not an edge case.
24
38
  function createWebSocketClient(lowdefy) {
25
39
  const { window } = lowdefy._internal.globals;
26
- // 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.
27
45
  const subscriptions = new Map();
28
- // websocketId → { resolve, reject, timer } for pending subscribe acks
29
- const pendingSubscribes = new Map();
30
- // requestId → { resolve, reject, timer } for pending publish acks
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.
31
49
  const pendingPublishes = new Map();
32
- // Frames queued while the socket is (re)connecting.
33
- const sendQueue = [];
34
50
  let socket = null;
35
51
  let openPromise = null;
36
52
  let reconnectAttempt = 0;
37
53
  let reconnectTimer = null;
38
54
  let idleTimer = null;
39
- let closedByClient = false;
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;
40
60
  let publishCounter = 0;
61
+ let subscribeCounter = 0;
41
62
  function url() {
42
63
  const { location } = window;
43
64
  const scheme = location.protocol === 'https:' ? 'wss' : 'ws';
@@ -49,43 +70,119 @@ function createWebSocketClient(lowdefy) {
49
70
  idleTimer = null;
50
71
  }
51
72
  }
73
+ function isIdle() {
74
+ return subscriptions.size === 0 && pendingPublishes.size === 0;
75
+ }
52
76
  function scheduleIdleClose() {
53
77
  clearIdleTimer();
54
- if (subscriptions.size > 0) {
78
+ if (!isIdle()) {
55
79
  return;
56
80
  }
57
81
  // Grace period so rapid page navigation doesn't thrash connections.
58
82
  idleTimer = setTimeout(()=>{
59
- if (subscriptions.size === 0 && socket) {
60
- closedByClient = true;
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;
61
92
  socket.close();
62
93
  socket = null;
63
94
  openPromise = null;
64
95
  }
65
96
  }, IDLE_CLOSE_GRACE_MS);
66
97
  }
67
- function send(frame) {
68
- const message = JSON.stringify(frame);
69
- if (socket && socket.readyState === window.WebSocket.OPEN) {
70
- socket.send(message);
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);
71
138
  return;
72
139
  }
73
- sendQueue.push(message);
74
- connect();
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;
75
173
  }
76
- function flushQueue() {
77
- while(sendQueue.length > 0 && socket && socket.readyState === window.WebSocket.OPEN){
78
- socket.send(sendQueue.shift());
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;
79
182
  }
80
183
  }
81
184
  function resubscribeAll() {
82
- subscriptions.forEach(({ payload }, websocketId)=>{
83
- socket.send(JSON.stringify({
84
- type: 'subscribe',
85
- websocketId,
86
- payload
87
- }));
88
- });
185
+ subscriptions.forEach(sendSubscribe);
89
186
  }
90
187
  function handleFrame(frame) {
91
188
  const { error: errorPayload, message, payload, requestId, websocketId } = frame;
@@ -95,70 +192,85 @@ function createWebSocketClient(lowdefy) {
95
192
  subscription?.handlers.onMessage(payload);
96
193
  return;
97
194
  case 'subscribed':
98
- {
99
- const pending = pendingSubscribes.get(websocketId);
100
- if (pending) {
101
- clearTimeout(pending.timer);
102
- pendingSubscribes.delete(websocketId);
103
- pending.resolve();
104
- }
105
- subscription?.handlers.onConnected();
195
+ if (!subscription || !answersSubscription(subscription, requestId)) {
106
196
  return;
107
197
  }
198
+ clearAckTimer(subscription);
199
+ if (subscription.pending) {
200
+ subscription.pending.resolve();
201
+ subscription.pending = null;
202
+ }
203
+ subscription.handlers.onConnected();
204
+ return;
108
205
  case 'unsubscribed':
109
206
  return;
110
207
  case 'published':
111
- {
112
- const pending = pendingPublishes.get(requestId);
113
- if (pending) {
114
- clearTimeout(pending.timer);
115
- pendingPublishes.delete(requestId);
116
- pending.resolve();
117
- }
118
- return;
208
+ if (pendingPublishes.has(requestId)) {
209
+ settlePublish(requestId).resolve();
119
210
  }
211
+ return;
120
212
  case 'error':
121
213
  {
122
214
  // Reply frames carry the error payload; broadcasts and frame-format
123
215
  // errors carry only a message string.
124
- const error = errorPayload ? decodeServerError(errorPayload) : new Error(message ?? 'WebSocket error.');
125
- if (requestId && pendingPublishes.has(requestId)) {
126
- const pending = pendingPublishes.get(requestId);
127
- clearTimeout(pending.timer);
128
- pendingPublishes.delete(requestId);
129
- pending.reject(error);
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);
130
221
  return;
131
222
  }
132
- if (websocketId && pendingSubscribes.has(websocketId)) {
133
- const pending = pendingSubscribes.get(websocketId);
134
- clearTimeout(pending.timer);
135
- pendingSubscribes.delete(websocketId);
136
- subscriptions.delete(websocketId);
137
- pending.reject(error);
223
+ if (!subscription) {
224
+ lowdefy._internal.logger.warn(error);
138
225
  return;
139
226
  }
140
- if (subscription) {
227
+ // A broadcast from the feed's source carries no requestId.
228
+ if (type.isNone(requestId)) {
141
229
  subscription.handlers.onError(error.message);
142
230
  return;
143
231
  }
144
- lowdefy._internal.logger.warn(error);
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);
145
243
  return;
146
244
  }
147
245
  default:
148
246
  }
149
247
  }
150
- function handleClose() {
248
+ function handleClose(event) {
151
249
  socket = null;
152
250
  openPromise = null;
153
- subscriptions.forEach(({ handlers })=>{
154
- handlers.onDisconnected();
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();
155
260
  });
156
- if (closedByClient) {
157
- closedByClient = false;
261
+ if (subscriptions.size === 0 && !hasUnsentPublishes()) {
158
262
  return;
159
263
  }
160
- if (subscriptions.size === 0 && sendQueue.length === 0) {
161
- return;
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;
162
274
  }
163
275
  // Capped exponential backoff with full jitter.
164
276
  const base = Math.min(RECONNECT_BASE_MS * 2 ** reconnectAttempt, RECONNECT_CAP_MS);
@@ -178,9 +290,15 @@ function createWebSocketClient(lowdefy) {
178
290
  const ws = new window.WebSocket(url());
179
291
  ws.onopen = ()=>{
180
292
  socket = ws;
181
- reconnectAttempt = 0;
293
+ openedAt = Date.now();
294
+ stableTimer = setTimeout(()=>{
295
+ reconnectAttempt = 0;
296
+ }, STABLE_CONNECTION_MS);
182
297
  resubscribeAll();
183
- flushQueue();
298
+ sendUnsentPublishes();
299
+ // Everything that asked for this connection may have finished while
300
+ // it was opening.
301
+ scheduleIdleClose();
184
302
  resolve();
185
303
  };
186
304
  ws.onmessage = (event)=>{
@@ -192,13 +310,15 @@ function createWebSocketClient(lowdefy) {
192
310
  }
193
311
  handleFrame(frame);
194
312
  };
195
- ws.onclose = ()=>{
313
+ ws.onclose = (event)=>{
196
314
  if (socket !== ws) {
197
315
  // Never opened — treat as a failed connect and retry.
198
316
  openPromise = null;
199
- reject(new Error('WebSocket connection failed.'));
317
+ reject(new ServiceError('Connection failed.', {
318
+ service: 'WebSocket'
319
+ }));
200
320
  }
201
- handleClose();
321
+ handleClose(event);
202
322
  };
203
323
  ws.onerror = ()=>{
204
324
  // onclose always follows onerror — reconnect is handled there.
@@ -210,34 +330,41 @@ function createWebSocketClient(lowdefy) {
210
330
  }
211
331
  function subscribe({ handlers, payload, websocketId }) {
212
332
  clearIdleTimer();
213
- subscriptions.set(websocketId, {
214
- handlers,
215
- payload
216
- });
333
+ const previous = subscriptions.get(websocketId);
334
+ if (previous) {
335
+ releaseSubscription(previous);
336
+ }
217
337
  return new Promise((resolve, reject)=>{
218
- const timer = setTimeout(()=>{
219
- pendingSubscribes.delete(websocketId);
220
- subscriptions.delete(websocketId);
221
- reject(new Error(`Subscribe to "${websocketId}" timed out.`));
222
- }, ACK_TIMEOUT_MS);
223
- pendingSubscribes.set(websocketId, {
224
- resolve,
225
- reject,
226
- timer
227
- });
228
- send({
229
- type: 'subscribe',
338
+ const subscription = {
230
339
  websocketId,
231
- payload
232
- });
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();
233
358
  });
234
359
  }
235
360
  function unsubscribe({ websocketId }) {
236
- if (!subscriptions.has(websocketId)) {
361
+ const subscription = subscriptions.get(websocketId);
362
+ if (!subscription) {
237
363
  return;
238
364
  }
365
+ releaseSubscription(subscription);
239
366
  subscriptions.delete(websocketId);
240
- if (socket && socket.readyState === window.WebSocket.OPEN) {
367
+ if (isSocketOpen()) {
241
368
  socket.send(JSON.stringify({
242
369
  type: 'unsubscribe',
243
370
  websocketId
@@ -246,24 +373,33 @@ function createWebSocketClient(lowdefy) {
246
373
  scheduleIdleClose();
247
374
  }
248
375
  function publish({ payload, websocketId }) {
376
+ clearIdleTimer();
249
377
  publishCounter += 1;
250
378
  const requestId = `p${publishCounter}`;
251
379
  return new Promise((resolve, reject)=>{
252
380
  const timer = setTimeout(()=>{
253
- pendingPublishes.delete(requestId);
254
- reject(new Error(`Publish to "${websocketId}" timed out.`));
381
+ settlePublish(requestId).reject(new ServiceError(`Publish to "${websocketId}" timed out.`, {
382
+ service: 'WebSocket'
383
+ }));
255
384
  }, ACK_TIMEOUT_MS);
256
- pendingPublishes.set(requestId, {
385
+ const pending = {
386
+ frame: {
387
+ type: 'publish',
388
+ websocketId,
389
+ requestId,
390
+ payload
391
+ },
392
+ sent: false,
257
393
  resolve,
258
394
  reject,
259
395
  timer
260
- });
261
- send({
262
- type: 'publish',
263
- websocketId,
264
- requestId,
265
- payload
266
- });
396
+ };
397
+ pendingPublishes.set(requestId, pending);
398
+ if (isSocketOpen()) {
399
+ sendPublish(pending);
400
+ return;
401
+ }
402
+ connect();
267
403
  });
268
404
  }
269
405
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/client",
3
- "version": "6.1.0",
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.1.0",
37
- "antd": "6.3.1",
38
- "@lowdefy/block-utils": "6.1.0",
39
- "@lowdefy/engine": "6.1.0",
40
- "@lowdefy/errors": "6.1.0",
41
- "@lowdefy/helpers": "6.1.0",
42
- "@lowdefy/layout": "6.1.0",
43
- "@lowdefy/logger": "6.1.0",
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": "6.1.0",
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",