@schematichq/schematic-react 1.2.7 → 1.2.8
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/schematic-react.cjs.js +168 -15
- package/dist/schematic-react.esm.js +168 -15
- package/package.json +4 -4
|
@@ -799,7 +799,7 @@ function contextString(context) {
|
|
|
799
799
|
}, {});
|
|
800
800
|
return JSON.stringify(sortedContext);
|
|
801
801
|
}
|
|
802
|
-
var version = "1.2.
|
|
802
|
+
var version = "1.2.8";
|
|
803
803
|
var anonymousIdKey = "schematicId";
|
|
804
804
|
var Schematic = class {
|
|
805
805
|
additionalHeaders = {};
|
|
@@ -829,6 +829,15 @@ var Schematic = class {
|
|
|
829
829
|
wsReconnectAttempts = 0;
|
|
830
830
|
wsReconnectTimer = null;
|
|
831
831
|
wsIntentionalDisconnect = false;
|
|
832
|
+
maxEventQueueSize = 100;
|
|
833
|
+
// Prevent memory issues with very long network outages
|
|
834
|
+
maxEventRetries = 5;
|
|
835
|
+
// Maximum retry attempts for failed events
|
|
836
|
+
eventRetryInitialDelay = 1e3;
|
|
837
|
+
// Initial retry delay in ms
|
|
838
|
+
eventRetryMaxDelay = 3e4;
|
|
839
|
+
// Maximum retry delay in ms
|
|
840
|
+
retryTimer = null;
|
|
832
841
|
constructor(apiKey, options) {
|
|
833
842
|
this.apiKey = apiKey;
|
|
834
843
|
this.eventQueue = [];
|
|
@@ -887,6 +896,18 @@ var Schematic = class {
|
|
|
887
896
|
if (options?.webSocketMaxRetryDelay !== void 0) {
|
|
888
897
|
this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
|
|
889
898
|
}
|
|
899
|
+
if (options?.maxEventQueueSize !== void 0) {
|
|
900
|
+
this.maxEventQueueSize = options.maxEventQueueSize;
|
|
901
|
+
}
|
|
902
|
+
if (options?.maxEventRetries !== void 0) {
|
|
903
|
+
this.maxEventRetries = options.maxEventRetries;
|
|
904
|
+
}
|
|
905
|
+
if (options?.eventRetryInitialDelay !== void 0) {
|
|
906
|
+
this.eventRetryInitialDelay = options.eventRetryInitialDelay;
|
|
907
|
+
}
|
|
908
|
+
if (options?.eventRetryMaxDelay !== void 0) {
|
|
909
|
+
this.eventRetryMaxDelay = options.eventRetryMaxDelay;
|
|
910
|
+
}
|
|
890
911
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
891
912
|
window.addEventListener("beforeunload", () => {
|
|
892
913
|
this.flushEventQueue();
|
|
@@ -1302,11 +1323,53 @@ var Schematic = class {
|
|
|
1302
1323
|
}
|
|
1303
1324
|
}
|
|
1304
1325
|
};
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1326
|
+
startRetryTimer = () => {
|
|
1327
|
+
if (this.retryTimer !== null) {
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
this.retryTimer = setInterval(() => {
|
|
1331
|
+
this.flushEventQueue().catch((error) => {
|
|
1332
|
+
this.debug("Error in retry timer flush:", error);
|
|
1333
|
+
});
|
|
1334
|
+
if (this.eventQueue.length === 0) {
|
|
1335
|
+
this.stopRetryTimer();
|
|
1336
|
+
}
|
|
1337
|
+
}, 5e3);
|
|
1338
|
+
this.debug("Started retry timer");
|
|
1339
|
+
};
|
|
1340
|
+
stopRetryTimer = () => {
|
|
1341
|
+
if (this.retryTimer !== null) {
|
|
1342
|
+
clearInterval(this.retryTimer);
|
|
1343
|
+
this.retryTimer = null;
|
|
1344
|
+
this.debug("Stopped retry timer");
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
flushEventQueue = async () => {
|
|
1348
|
+
if (this.eventQueue.length === 0) {
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const now = Date.now();
|
|
1352
|
+
const readyEvents = [];
|
|
1353
|
+
const notReadyEvents = [];
|
|
1354
|
+
for (const event of this.eventQueue) {
|
|
1355
|
+
if (event.next_retry_at === void 0 || event.next_retry_at <= now) {
|
|
1356
|
+
readyEvents.push(event);
|
|
1357
|
+
} else {
|
|
1358
|
+
notReadyEvents.push(event);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
if (readyEvents.length === 0) {
|
|
1362
|
+
this.debug(`No events ready for retry yet (${notReadyEvents.length} still in backoff)`);
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
this.debug(`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`);
|
|
1366
|
+
this.eventQueue = notReadyEvents;
|
|
1367
|
+
for (const event of readyEvents) {
|
|
1368
|
+
try {
|
|
1369
|
+
await this.sendEvent(event);
|
|
1370
|
+
this.debug(`Queued event sent successfully:`, event.type);
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
this.debug(`Failed to send queued event:`, error);
|
|
1310
1373
|
}
|
|
1311
1374
|
}
|
|
1312
1375
|
};
|
|
@@ -1354,12 +1417,37 @@ var Schematic = class {
|
|
|
1354
1417
|
},
|
|
1355
1418
|
body: payload
|
|
1356
1419
|
});
|
|
1420
|
+
if (!response.ok) {
|
|
1421
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1422
|
+
}
|
|
1357
1423
|
this.debug(`event sent:`, {
|
|
1358
1424
|
status: response.status,
|
|
1359
1425
|
statusText: response.statusText
|
|
1360
1426
|
});
|
|
1361
1427
|
} catch (error) {
|
|
1362
|
-
|
|
1428
|
+
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1429
|
+
if (retryCount <= this.maxEventRetries) {
|
|
1430
|
+
this.debug(`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`, error);
|
|
1431
|
+
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1432
|
+
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1433
|
+
const nextRetryAt = Date.now() + jitterDelay;
|
|
1434
|
+
const retryEvent = {
|
|
1435
|
+
...event,
|
|
1436
|
+
retry_count: retryCount,
|
|
1437
|
+
next_retry_at: nextRetryAt
|
|
1438
|
+
};
|
|
1439
|
+
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1440
|
+
this.eventQueue.push(retryEvent);
|
|
1441
|
+
this.debug(`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`);
|
|
1442
|
+
} else {
|
|
1443
|
+
this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`);
|
|
1444
|
+
this.eventQueue.shift();
|
|
1445
|
+
this.eventQueue.push(retryEvent);
|
|
1446
|
+
}
|
|
1447
|
+
this.startRetryTimer();
|
|
1448
|
+
} else {
|
|
1449
|
+
this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`, error);
|
|
1450
|
+
}
|
|
1363
1451
|
}
|
|
1364
1452
|
return Promise.resolve();
|
|
1365
1453
|
};
|
|
@@ -1384,6 +1472,7 @@ var Schematic = class {
|
|
|
1384
1472
|
clearTimeout(this.wsReconnectTimer);
|
|
1385
1473
|
this.wsReconnectTimer = null;
|
|
1386
1474
|
}
|
|
1475
|
+
this.stopRetryTimer();
|
|
1387
1476
|
if (this.conn) {
|
|
1388
1477
|
try {
|
|
1389
1478
|
const socket = await this.conn;
|
|
@@ -1431,16 +1520,15 @@ var Schematic = class {
|
|
|
1431
1520
|
* Handle browser coming back online
|
|
1432
1521
|
*/
|
|
1433
1522
|
handleNetworkOnline = () => {
|
|
1434
|
-
|
|
1435
|
-
this.debug("No context set, skipping reconnection");
|
|
1436
|
-
return;
|
|
1437
|
-
}
|
|
1523
|
+
this.debug("Network online, attempting reconnection and flushing queued events");
|
|
1438
1524
|
this.wsReconnectAttempts = 0;
|
|
1439
1525
|
if (this.wsReconnectTimer !== null) {
|
|
1440
1526
|
clearTimeout(this.wsReconnectTimer);
|
|
1441
1527
|
this.wsReconnectTimer = null;
|
|
1442
1528
|
}
|
|
1443
|
-
this.
|
|
1529
|
+
this.flushEventQueue().catch((error) => {
|
|
1530
|
+
this.debug("Error flushing event queue on network online:", error);
|
|
1531
|
+
});
|
|
1444
1532
|
this.attemptReconnect();
|
|
1445
1533
|
};
|
|
1446
1534
|
/**
|
|
@@ -1470,10 +1558,20 @@ var Schematic = class {
|
|
|
1470
1558
|
try {
|
|
1471
1559
|
this.conn = this.wsConnect();
|
|
1472
1560
|
const socket = await this.conn;
|
|
1561
|
+
this.debug(`Reconnection context check:`, {
|
|
1562
|
+
hasCompany: this.context.company !== void 0,
|
|
1563
|
+
hasUser: this.context.user !== void 0,
|
|
1564
|
+
context: this.context
|
|
1565
|
+
});
|
|
1473
1566
|
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1474
|
-
this.debug(`Reconnected, re-sending context`);
|
|
1475
|
-
await this.
|
|
1567
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1568
|
+
await this.wsSendContextAfterReconnection(socket, this.context);
|
|
1569
|
+
} else {
|
|
1570
|
+
this.debug(`No context to re-send after reconnection - websocket ready for new context`);
|
|
1476
1571
|
}
|
|
1572
|
+
this.flushEventQueue().catch((error) => {
|
|
1573
|
+
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1574
|
+
});
|
|
1477
1575
|
this.debug(`Reconnection successful`);
|
|
1478
1576
|
} catch (error) {
|
|
1479
1577
|
this.debug(`Reconnection attempt failed:`, error);
|
|
@@ -1534,6 +1632,61 @@ var Schematic = class {
|
|
|
1534
1632
|
};
|
|
1535
1633
|
});
|
|
1536
1634
|
};
|
|
1635
|
+
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1636
|
+
// because the server has lost all state and needs the initial context
|
|
1637
|
+
wsSendContextAfterReconnection = (socket, context) => {
|
|
1638
|
+
if (this.isOffline()) {
|
|
1639
|
+
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1640
|
+
this.setIsPending(false);
|
|
1641
|
+
return Promise.resolve();
|
|
1642
|
+
}
|
|
1643
|
+
return new Promise((resolve) => {
|
|
1644
|
+
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1645
|
+
this.context = context;
|
|
1646
|
+
const sendMessage = () => {
|
|
1647
|
+
let resolved = false;
|
|
1648
|
+
const messageHandler = (event) => {
|
|
1649
|
+
const message = JSON.parse(event.data);
|
|
1650
|
+
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1651
|
+
if (!(contextString(context) in this.checks)) {
|
|
1652
|
+
this.checks[contextString(context)] = {};
|
|
1653
|
+
}
|
|
1654
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1655
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1656
|
+
const contextStr = contextString(context);
|
|
1657
|
+
if (this.checks[contextStr] === void 0) {
|
|
1658
|
+
this.checks[contextStr] = {};
|
|
1659
|
+
}
|
|
1660
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1661
|
+
});
|
|
1662
|
+
this.useWebSocket = true;
|
|
1663
|
+
socket.removeEventListener("message", messageHandler);
|
|
1664
|
+
if (!resolved) {
|
|
1665
|
+
resolved = true;
|
|
1666
|
+
resolve(this.setIsPending(false));
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
socket.addEventListener("message", messageHandler);
|
|
1670
|
+
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1671
|
+
const messagePayload = {
|
|
1672
|
+
apiKey: this.apiKey,
|
|
1673
|
+
clientVersion,
|
|
1674
|
+
data: context
|
|
1675
|
+
};
|
|
1676
|
+
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1677
|
+
socket.send(JSON.stringify(messagePayload));
|
|
1678
|
+
};
|
|
1679
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
1680
|
+
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1681
|
+
sendMessage();
|
|
1682
|
+
} else {
|
|
1683
|
+
socket.addEventListener("open", () => {
|
|
1684
|
+
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1685
|
+
sendMessage();
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1689
|
+
};
|
|
1537
1690
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1538
1691
|
// and wait for the initial set of flag values to be returned
|
|
1539
1692
|
wsSendMessage = (socket, context) => {
|
|
@@ -1722,7 +1875,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1722
1875
|
var import_react = __toESM(require("react"));
|
|
1723
1876
|
|
|
1724
1877
|
// src/version.ts
|
|
1725
|
-
var version2 = "1.2.
|
|
1878
|
+
var version2 = "1.2.8";
|
|
1726
1879
|
|
|
1727
1880
|
// src/context/schematic.tsx
|
|
1728
1881
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -754,7 +754,7 @@ function contextString(context) {
|
|
|
754
754
|
}, {});
|
|
755
755
|
return JSON.stringify(sortedContext);
|
|
756
756
|
}
|
|
757
|
-
var version = "1.2.
|
|
757
|
+
var version = "1.2.8";
|
|
758
758
|
var anonymousIdKey = "schematicId";
|
|
759
759
|
var Schematic = class {
|
|
760
760
|
additionalHeaders = {};
|
|
@@ -784,6 +784,15 @@ var Schematic = class {
|
|
|
784
784
|
wsReconnectAttempts = 0;
|
|
785
785
|
wsReconnectTimer = null;
|
|
786
786
|
wsIntentionalDisconnect = false;
|
|
787
|
+
maxEventQueueSize = 100;
|
|
788
|
+
// Prevent memory issues with very long network outages
|
|
789
|
+
maxEventRetries = 5;
|
|
790
|
+
// Maximum retry attempts for failed events
|
|
791
|
+
eventRetryInitialDelay = 1e3;
|
|
792
|
+
// Initial retry delay in ms
|
|
793
|
+
eventRetryMaxDelay = 3e4;
|
|
794
|
+
// Maximum retry delay in ms
|
|
795
|
+
retryTimer = null;
|
|
787
796
|
constructor(apiKey, options) {
|
|
788
797
|
this.apiKey = apiKey;
|
|
789
798
|
this.eventQueue = [];
|
|
@@ -842,6 +851,18 @@ var Schematic = class {
|
|
|
842
851
|
if (options?.webSocketMaxRetryDelay !== void 0) {
|
|
843
852
|
this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
|
|
844
853
|
}
|
|
854
|
+
if (options?.maxEventQueueSize !== void 0) {
|
|
855
|
+
this.maxEventQueueSize = options.maxEventQueueSize;
|
|
856
|
+
}
|
|
857
|
+
if (options?.maxEventRetries !== void 0) {
|
|
858
|
+
this.maxEventRetries = options.maxEventRetries;
|
|
859
|
+
}
|
|
860
|
+
if (options?.eventRetryInitialDelay !== void 0) {
|
|
861
|
+
this.eventRetryInitialDelay = options.eventRetryInitialDelay;
|
|
862
|
+
}
|
|
863
|
+
if (options?.eventRetryMaxDelay !== void 0) {
|
|
864
|
+
this.eventRetryMaxDelay = options.eventRetryMaxDelay;
|
|
865
|
+
}
|
|
845
866
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
846
867
|
window.addEventListener("beforeunload", () => {
|
|
847
868
|
this.flushEventQueue();
|
|
@@ -1257,11 +1278,53 @@ var Schematic = class {
|
|
|
1257
1278
|
}
|
|
1258
1279
|
}
|
|
1259
1280
|
};
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1281
|
+
startRetryTimer = () => {
|
|
1282
|
+
if (this.retryTimer !== null) {
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
this.retryTimer = setInterval(() => {
|
|
1286
|
+
this.flushEventQueue().catch((error) => {
|
|
1287
|
+
this.debug("Error in retry timer flush:", error);
|
|
1288
|
+
});
|
|
1289
|
+
if (this.eventQueue.length === 0) {
|
|
1290
|
+
this.stopRetryTimer();
|
|
1291
|
+
}
|
|
1292
|
+
}, 5e3);
|
|
1293
|
+
this.debug("Started retry timer");
|
|
1294
|
+
};
|
|
1295
|
+
stopRetryTimer = () => {
|
|
1296
|
+
if (this.retryTimer !== null) {
|
|
1297
|
+
clearInterval(this.retryTimer);
|
|
1298
|
+
this.retryTimer = null;
|
|
1299
|
+
this.debug("Stopped retry timer");
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
flushEventQueue = async () => {
|
|
1303
|
+
if (this.eventQueue.length === 0) {
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
const now = Date.now();
|
|
1307
|
+
const readyEvents = [];
|
|
1308
|
+
const notReadyEvents = [];
|
|
1309
|
+
for (const event of this.eventQueue) {
|
|
1310
|
+
if (event.next_retry_at === void 0 || event.next_retry_at <= now) {
|
|
1311
|
+
readyEvents.push(event);
|
|
1312
|
+
} else {
|
|
1313
|
+
notReadyEvents.push(event);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
if (readyEvents.length === 0) {
|
|
1317
|
+
this.debug(`No events ready for retry yet (${notReadyEvents.length} still in backoff)`);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
this.debug(`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`);
|
|
1321
|
+
this.eventQueue = notReadyEvents;
|
|
1322
|
+
for (const event of readyEvents) {
|
|
1323
|
+
try {
|
|
1324
|
+
await this.sendEvent(event);
|
|
1325
|
+
this.debug(`Queued event sent successfully:`, event.type);
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
this.debug(`Failed to send queued event:`, error);
|
|
1265
1328
|
}
|
|
1266
1329
|
}
|
|
1267
1330
|
};
|
|
@@ -1309,12 +1372,37 @@ var Schematic = class {
|
|
|
1309
1372
|
},
|
|
1310
1373
|
body: payload
|
|
1311
1374
|
});
|
|
1375
|
+
if (!response.ok) {
|
|
1376
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1377
|
+
}
|
|
1312
1378
|
this.debug(`event sent:`, {
|
|
1313
1379
|
status: response.status,
|
|
1314
1380
|
statusText: response.statusText
|
|
1315
1381
|
});
|
|
1316
1382
|
} catch (error) {
|
|
1317
|
-
|
|
1383
|
+
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1384
|
+
if (retryCount <= this.maxEventRetries) {
|
|
1385
|
+
this.debug(`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`, error);
|
|
1386
|
+
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1387
|
+
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1388
|
+
const nextRetryAt = Date.now() + jitterDelay;
|
|
1389
|
+
const retryEvent = {
|
|
1390
|
+
...event,
|
|
1391
|
+
retry_count: retryCount,
|
|
1392
|
+
next_retry_at: nextRetryAt
|
|
1393
|
+
};
|
|
1394
|
+
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1395
|
+
this.eventQueue.push(retryEvent);
|
|
1396
|
+
this.debug(`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`);
|
|
1397
|
+
} else {
|
|
1398
|
+
this.debug(`Event queue full (${this.maxEventQueueSize}), dropping oldest event`);
|
|
1399
|
+
this.eventQueue.shift();
|
|
1400
|
+
this.eventQueue.push(retryEvent);
|
|
1401
|
+
}
|
|
1402
|
+
this.startRetryTimer();
|
|
1403
|
+
} else {
|
|
1404
|
+
this.debug(`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`, error);
|
|
1405
|
+
}
|
|
1318
1406
|
}
|
|
1319
1407
|
return Promise.resolve();
|
|
1320
1408
|
};
|
|
@@ -1339,6 +1427,7 @@ var Schematic = class {
|
|
|
1339
1427
|
clearTimeout(this.wsReconnectTimer);
|
|
1340
1428
|
this.wsReconnectTimer = null;
|
|
1341
1429
|
}
|
|
1430
|
+
this.stopRetryTimer();
|
|
1342
1431
|
if (this.conn) {
|
|
1343
1432
|
try {
|
|
1344
1433
|
const socket = await this.conn;
|
|
@@ -1386,16 +1475,15 @@ var Schematic = class {
|
|
|
1386
1475
|
* Handle browser coming back online
|
|
1387
1476
|
*/
|
|
1388
1477
|
handleNetworkOnline = () => {
|
|
1389
|
-
|
|
1390
|
-
this.debug("No context set, skipping reconnection");
|
|
1391
|
-
return;
|
|
1392
|
-
}
|
|
1478
|
+
this.debug("Network online, attempting reconnection and flushing queued events");
|
|
1393
1479
|
this.wsReconnectAttempts = 0;
|
|
1394
1480
|
if (this.wsReconnectTimer !== null) {
|
|
1395
1481
|
clearTimeout(this.wsReconnectTimer);
|
|
1396
1482
|
this.wsReconnectTimer = null;
|
|
1397
1483
|
}
|
|
1398
|
-
this.
|
|
1484
|
+
this.flushEventQueue().catch((error) => {
|
|
1485
|
+
this.debug("Error flushing event queue on network online:", error);
|
|
1486
|
+
});
|
|
1399
1487
|
this.attemptReconnect();
|
|
1400
1488
|
};
|
|
1401
1489
|
/**
|
|
@@ -1425,10 +1513,20 @@ var Schematic = class {
|
|
|
1425
1513
|
try {
|
|
1426
1514
|
this.conn = this.wsConnect();
|
|
1427
1515
|
const socket = await this.conn;
|
|
1516
|
+
this.debug(`Reconnection context check:`, {
|
|
1517
|
+
hasCompany: this.context.company !== void 0,
|
|
1518
|
+
hasUser: this.context.user !== void 0,
|
|
1519
|
+
context: this.context
|
|
1520
|
+
});
|
|
1428
1521
|
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1429
|
-
this.debug(`Reconnected, re-sending context`);
|
|
1430
|
-
await this.
|
|
1522
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1523
|
+
await this.wsSendContextAfterReconnection(socket, this.context);
|
|
1524
|
+
} else {
|
|
1525
|
+
this.debug(`No context to re-send after reconnection - websocket ready for new context`);
|
|
1431
1526
|
}
|
|
1527
|
+
this.flushEventQueue().catch((error) => {
|
|
1528
|
+
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1529
|
+
});
|
|
1432
1530
|
this.debug(`Reconnection successful`);
|
|
1433
1531
|
} catch (error) {
|
|
1434
1532
|
this.debug(`Reconnection attempt failed:`, error);
|
|
@@ -1489,6 +1587,61 @@ var Schematic = class {
|
|
|
1489
1587
|
};
|
|
1490
1588
|
});
|
|
1491
1589
|
};
|
|
1590
|
+
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1591
|
+
// because the server has lost all state and needs the initial context
|
|
1592
|
+
wsSendContextAfterReconnection = (socket, context) => {
|
|
1593
|
+
if (this.isOffline()) {
|
|
1594
|
+
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1595
|
+
this.setIsPending(false);
|
|
1596
|
+
return Promise.resolve();
|
|
1597
|
+
}
|
|
1598
|
+
return new Promise((resolve) => {
|
|
1599
|
+
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1600
|
+
this.context = context;
|
|
1601
|
+
const sendMessage = () => {
|
|
1602
|
+
let resolved = false;
|
|
1603
|
+
const messageHandler = (event) => {
|
|
1604
|
+
const message = JSON.parse(event.data);
|
|
1605
|
+
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1606
|
+
if (!(contextString(context) in this.checks)) {
|
|
1607
|
+
this.checks[contextString(context)] = {};
|
|
1608
|
+
}
|
|
1609
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1610
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1611
|
+
const contextStr = contextString(context);
|
|
1612
|
+
if (this.checks[contextStr] === void 0) {
|
|
1613
|
+
this.checks[contextStr] = {};
|
|
1614
|
+
}
|
|
1615
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1616
|
+
});
|
|
1617
|
+
this.useWebSocket = true;
|
|
1618
|
+
socket.removeEventListener("message", messageHandler);
|
|
1619
|
+
if (!resolved) {
|
|
1620
|
+
resolved = true;
|
|
1621
|
+
resolve(this.setIsPending(false));
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1624
|
+
socket.addEventListener("message", messageHandler);
|
|
1625
|
+
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1626
|
+
const messagePayload = {
|
|
1627
|
+
apiKey: this.apiKey,
|
|
1628
|
+
clientVersion,
|
|
1629
|
+
data: context
|
|
1630
|
+
};
|
|
1631
|
+
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1632
|
+
socket.send(JSON.stringify(messagePayload));
|
|
1633
|
+
};
|
|
1634
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
1635
|
+
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1636
|
+
sendMessage();
|
|
1637
|
+
} else {
|
|
1638
|
+
socket.addEventListener("open", () => {
|
|
1639
|
+
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1640
|
+
sendMessage();
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
};
|
|
1492
1645
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1493
1646
|
// and wait for the initial set of flag values to be returned
|
|
1494
1647
|
wsSendMessage = (socket, context) => {
|
|
@@ -1677,7 +1830,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1677
1830
|
import React, { createContext, useEffect, useMemo, useRef } from "react";
|
|
1678
1831
|
|
|
1679
1832
|
// src/version.ts
|
|
1680
|
-
var version2 = "1.2.
|
|
1833
|
+
var version2 = "1.2.8";
|
|
1681
1834
|
|
|
1682
1835
|
// src/context/schematic.tsx
|
|
1683
1836
|
import { jsx } from "react/jsx-runtime";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schematichq/schematic-react",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"main": "dist/schematic-react.cjs.js",
|
|
5
5
|
"module": "dist/schematic-react.esm.js",
|
|
6
6
|
"types": "dist/schematic-react.d.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"prepare": "husky"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@schematichq/schematic-js": "^1.2.
|
|
34
|
+
"@schematichq/schematic-js": "^1.2.8"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@eslint/js": "^9.39.1",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@testing-library/dom": "^10.4.1",
|
|
40
40
|
"@testing-library/jest-dom": "^6.9.1",
|
|
41
41
|
"@testing-library/react": "^16.3.0",
|
|
42
|
-
"@types/react": "^19.2.
|
|
42
|
+
"@types/react": "^19.2.6",
|
|
43
43
|
"@vitest/browser": "^4.0.8",
|
|
44
44
|
"esbuild": "^0.27.0",
|
|
45
45
|
"eslint": "^9.39.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"react": "^19.2.0",
|
|
55
55
|
"react-dom": "^19.2.0",
|
|
56
56
|
"typescript": "^5.9.3",
|
|
57
|
-
"typescript-eslint": "^8.
|
|
57
|
+
"typescript-eslint": "^8.47.0",
|
|
58
58
|
"vitest": "^4.0.8"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|