@schematichq/schematic-react 1.2.6 → 1.2.7
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/LICENSE +21 -0
- package/README.md +2 -0
- package/dist/schematic-react.cjs.js +160 -2
- package/dist/schematic-react.esm.js +160 -2
- package/package.json +21 -23
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2025 Schematic, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -134,6 +134,8 @@ const MyComponent = () => {
|
|
|
134
134
|
};
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
+
*Note: `useSchematicIsPending` is checking if entitlement data has been loaded, typically via `identify`. It should, therefore, be used to wrap flag and entitlement checks, but never the initial call to `identify`.*
|
|
138
|
+
|
|
137
139
|
## Troubleshooting
|
|
138
140
|
|
|
139
141
|
For debugging and development, Schematic supports two special modes:
|
|
@@ -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.7";
|
|
803
803
|
var anonymousIdKey = "schematicId";
|
|
804
804
|
var Schematic = class {
|
|
805
805
|
additionalHeaders = {};
|
|
@@ -821,6 +821,14 @@ var Schematic = class {
|
|
|
821
821
|
checks = {};
|
|
822
822
|
featureUsageEventMap = {};
|
|
823
823
|
webSocketUrl = "wss://api.schematichq.com";
|
|
824
|
+
webSocketConnectionTimeout = 1e4;
|
|
825
|
+
webSocketReconnect = true;
|
|
826
|
+
webSocketMaxReconnectAttempts = 7;
|
|
827
|
+
webSocketInitialRetryDelay = 1e3;
|
|
828
|
+
webSocketMaxRetryDelay = 3e4;
|
|
829
|
+
wsReconnectAttempts = 0;
|
|
830
|
+
wsReconnectTimer = null;
|
|
831
|
+
wsIntentionalDisconnect = false;
|
|
824
832
|
constructor(apiKey, options) {
|
|
825
833
|
this.apiKey = apiKey;
|
|
826
834
|
this.eventQueue = [];
|
|
@@ -864,11 +872,36 @@ var Schematic = class {
|
|
|
864
872
|
if (options?.webSocketUrl !== void 0) {
|
|
865
873
|
this.webSocketUrl = options.webSocketUrl;
|
|
866
874
|
}
|
|
875
|
+
if (options?.webSocketConnectionTimeout !== void 0) {
|
|
876
|
+
this.webSocketConnectionTimeout = options.webSocketConnectionTimeout;
|
|
877
|
+
}
|
|
878
|
+
if (options?.webSocketReconnect !== void 0) {
|
|
879
|
+
this.webSocketReconnect = options.webSocketReconnect;
|
|
880
|
+
}
|
|
881
|
+
if (options?.webSocketMaxReconnectAttempts !== void 0) {
|
|
882
|
+
this.webSocketMaxReconnectAttempts = options.webSocketMaxReconnectAttempts;
|
|
883
|
+
}
|
|
884
|
+
if (options?.webSocketInitialRetryDelay !== void 0) {
|
|
885
|
+
this.webSocketInitialRetryDelay = options.webSocketInitialRetryDelay;
|
|
886
|
+
}
|
|
887
|
+
if (options?.webSocketMaxRetryDelay !== void 0) {
|
|
888
|
+
this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
|
|
889
|
+
}
|
|
867
890
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
868
891
|
window.addEventListener("beforeunload", () => {
|
|
869
892
|
this.flushEventQueue();
|
|
870
893
|
this.flushContextDependentEventQueue();
|
|
871
894
|
});
|
|
895
|
+
if (this.useWebSocket) {
|
|
896
|
+
window.addEventListener("offline", () => {
|
|
897
|
+
this.debug("Browser went offline, closing WebSocket connection");
|
|
898
|
+
this.handleNetworkOffline();
|
|
899
|
+
});
|
|
900
|
+
window.addEventListener("online", () => {
|
|
901
|
+
this.debug("Browser came online, attempting to reconnect WebSocket");
|
|
902
|
+
this.handleNetworkOnline();
|
|
903
|
+
});
|
|
904
|
+
}
|
|
872
905
|
}
|
|
873
906
|
if (this.offlineEnabled) {
|
|
874
907
|
this.debug(
|
|
@@ -1133,6 +1166,13 @@ var Schematic = class {
|
|
|
1133
1166
|
try {
|
|
1134
1167
|
this.setIsPending(true);
|
|
1135
1168
|
if (!this.conn) {
|
|
1169
|
+
if (this.wsReconnectTimer !== null) {
|
|
1170
|
+
this.debug(
|
|
1171
|
+
`Cancelling scheduled reconnection, connecting immediately`
|
|
1172
|
+
);
|
|
1173
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1174
|
+
this.wsReconnectTimer = null;
|
|
1175
|
+
}
|
|
1136
1176
|
this.conn = this.wsConnect();
|
|
1137
1177
|
}
|
|
1138
1178
|
const socket = await this.conn;
|
|
@@ -1339,6 +1379,11 @@ var Schematic = class {
|
|
|
1339
1379
|
this.debug("cleanup: skipped (offline mode)");
|
|
1340
1380
|
return Promise.resolve();
|
|
1341
1381
|
}
|
|
1382
|
+
this.wsIntentionalDisconnect = true;
|
|
1383
|
+
if (this.wsReconnectTimer !== null) {
|
|
1384
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1385
|
+
this.wsReconnectTimer = null;
|
|
1386
|
+
}
|
|
1342
1387
|
if (this.conn) {
|
|
1343
1388
|
try {
|
|
1344
1389
|
const socket = await this.conn;
|
|
@@ -1350,6 +1395,91 @@ var Schematic = class {
|
|
|
1350
1395
|
}
|
|
1351
1396
|
}
|
|
1352
1397
|
};
|
|
1398
|
+
/**
|
|
1399
|
+
* Calculate the delay for the next reconnection attempt using exponential backoff with jitter.
|
|
1400
|
+
* This helps prevent dogpiling when the server recovers from an outage.
|
|
1401
|
+
*/
|
|
1402
|
+
calculateReconnectDelay = () => {
|
|
1403
|
+
const exponentialDelay = this.webSocketInitialRetryDelay * Math.pow(2, this.wsReconnectAttempts);
|
|
1404
|
+
const cappedDelay = Math.min(exponentialDelay, this.webSocketMaxRetryDelay);
|
|
1405
|
+
const jitter = Math.random() * cappedDelay * 0.5;
|
|
1406
|
+
const totalDelay = cappedDelay + jitter;
|
|
1407
|
+
this.debug(
|
|
1408
|
+
`Reconnect delay calculated: ${totalDelay.toFixed(0)}ms (attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts})`
|
|
1409
|
+
);
|
|
1410
|
+
return totalDelay;
|
|
1411
|
+
};
|
|
1412
|
+
/**
|
|
1413
|
+
* Handle browser going offline
|
|
1414
|
+
*/
|
|
1415
|
+
handleNetworkOffline = async () => {
|
|
1416
|
+
if (this.conn !== null) {
|
|
1417
|
+
try {
|
|
1418
|
+
const socket = await this.conn;
|
|
1419
|
+
socket.close();
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
this.debug("Error closing connection on offline:", error);
|
|
1422
|
+
}
|
|
1423
|
+
this.conn = null;
|
|
1424
|
+
}
|
|
1425
|
+
if (this.wsReconnectTimer !== null) {
|
|
1426
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1427
|
+
this.wsReconnectTimer = null;
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
/**
|
|
1431
|
+
* Handle browser coming back online
|
|
1432
|
+
*/
|
|
1433
|
+
handleNetworkOnline = () => {
|
|
1434
|
+
if (this.context.company === void 0 && this.context.user === void 0) {
|
|
1435
|
+
this.debug("No context set, skipping reconnection");
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
this.wsReconnectAttempts = 0;
|
|
1439
|
+
if (this.wsReconnectTimer !== null) {
|
|
1440
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1441
|
+
this.wsReconnectTimer = null;
|
|
1442
|
+
}
|
|
1443
|
+
this.debug("Network online, reconnecting immediately");
|
|
1444
|
+
this.attemptReconnect();
|
|
1445
|
+
};
|
|
1446
|
+
/**
|
|
1447
|
+
* Attempt to reconnect the WebSocket connection with exponential backoff.
|
|
1448
|
+
* Called automatically when the connection closes unexpectedly.
|
|
1449
|
+
*/
|
|
1450
|
+
attemptReconnect = () => {
|
|
1451
|
+
if (this.wsReconnectAttempts >= this.webSocketMaxReconnectAttempts) {
|
|
1452
|
+
this.debug(
|
|
1453
|
+
`Maximum reconnection attempts (${this.webSocketMaxReconnectAttempts}) reached, giving up`
|
|
1454
|
+
);
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
if (this.wsReconnectTimer !== null) {
|
|
1458
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1459
|
+
}
|
|
1460
|
+
const delay = this.calculateReconnectDelay();
|
|
1461
|
+
this.debug(
|
|
1462
|
+
`Scheduling reconnection attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts} in ${delay.toFixed(0)}ms`
|
|
1463
|
+
);
|
|
1464
|
+
this.wsReconnectTimer = setTimeout(async () => {
|
|
1465
|
+
this.wsReconnectTimer = null;
|
|
1466
|
+
this.wsReconnectAttempts++;
|
|
1467
|
+
this.debug(
|
|
1468
|
+
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1469
|
+
);
|
|
1470
|
+
try {
|
|
1471
|
+
this.conn = this.wsConnect();
|
|
1472
|
+
const socket = await this.conn;
|
|
1473
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1474
|
+
this.debug(`Reconnected, re-sending context`);
|
|
1475
|
+
await this.wsSendMessage(socket, this.context);
|
|
1476
|
+
}
|
|
1477
|
+
this.debug(`Reconnection successful`);
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
this.debug(`Reconnection attempt failed:`, error);
|
|
1480
|
+
}
|
|
1481
|
+
}, delay);
|
|
1482
|
+
};
|
|
1353
1483
|
// Open a websocket connection
|
|
1354
1484
|
wsConnect = () => {
|
|
1355
1485
|
if (this.isOffline()) {
|
|
@@ -1362,17 +1492,45 @@ var Schematic = class {
|
|
|
1362
1492
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1363
1493
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1364
1494
|
const webSocket = new WebSocket(wsUrl);
|
|
1495
|
+
let timeoutId = null;
|
|
1496
|
+
let isResolved = false;
|
|
1497
|
+
timeoutId = setTimeout(() => {
|
|
1498
|
+
if (!isResolved) {
|
|
1499
|
+
this.debug(
|
|
1500
|
+
`WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`
|
|
1501
|
+
);
|
|
1502
|
+
webSocket.close();
|
|
1503
|
+
reject(new Error("WebSocket connection timeout"));
|
|
1504
|
+
}
|
|
1505
|
+
}, this.webSocketConnectionTimeout);
|
|
1365
1506
|
webSocket.onopen = () => {
|
|
1507
|
+
isResolved = true;
|
|
1508
|
+
if (timeoutId !== null) {
|
|
1509
|
+
clearTimeout(timeoutId);
|
|
1510
|
+
}
|
|
1511
|
+
this.wsReconnectAttempts = 0;
|
|
1512
|
+
this.wsIntentionalDisconnect = false;
|
|
1366
1513
|
this.debug(`WebSocket connection opened`);
|
|
1367
1514
|
resolve(webSocket);
|
|
1368
1515
|
};
|
|
1369
1516
|
webSocket.onerror = (error) => {
|
|
1517
|
+
isResolved = true;
|
|
1518
|
+
if (timeoutId !== null) {
|
|
1519
|
+
clearTimeout(timeoutId);
|
|
1520
|
+
}
|
|
1370
1521
|
this.debug(`WebSocket connection error:`, error);
|
|
1371
1522
|
reject(error);
|
|
1372
1523
|
};
|
|
1373
1524
|
webSocket.onclose = () => {
|
|
1525
|
+
isResolved = true;
|
|
1526
|
+
if (timeoutId !== null) {
|
|
1527
|
+
clearTimeout(timeoutId);
|
|
1528
|
+
}
|
|
1374
1529
|
this.debug(`WebSocket connection closed`);
|
|
1375
1530
|
this.conn = null;
|
|
1531
|
+
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1532
|
+
this.attemptReconnect();
|
|
1533
|
+
}
|
|
1376
1534
|
};
|
|
1377
1535
|
});
|
|
1378
1536
|
};
|
|
@@ -1564,7 +1722,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1564
1722
|
var import_react = __toESM(require("react"));
|
|
1565
1723
|
|
|
1566
1724
|
// src/version.ts
|
|
1567
|
-
var version2 = "1.2.
|
|
1725
|
+
var version2 = "1.2.7";
|
|
1568
1726
|
|
|
1569
1727
|
// src/context/schematic.tsx
|
|
1570
1728
|
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.7";
|
|
758
758
|
var anonymousIdKey = "schematicId";
|
|
759
759
|
var Schematic = class {
|
|
760
760
|
additionalHeaders = {};
|
|
@@ -776,6 +776,14 @@ var Schematic = class {
|
|
|
776
776
|
checks = {};
|
|
777
777
|
featureUsageEventMap = {};
|
|
778
778
|
webSocketUrl = "wss://api.schematichq.com";
|
|
779
|
+
webSocketConnectionTimeout = 1e4;
|
|
780
|
+
webSocketReconnect = true;
|
|
781
|
+
webSocketMaxReconnectAttempts = 7;
|
|
782
|
+
webSocketInitialRetryDelay = 1e3;
|
|
783
|
+
webSocketMaxRetryDelay = 3e4;
|
|
784
|
+
wsReconnectAttempts = 0;
|
|
785
|
+
wsReconnectTimer = null;
|
|
786
|
+
wsIntentionalDisconnect = false;
|
|
779
787
|
constructor(apiKey, options) {
|
|
780
788
|
this.apiKey = apiKey;
|
|
781
789
|
this.eventQueue = [];
|
|
@@ -819,11 +827,36 @@ var Schematic = class {
|
|
|
819
827
|
if (options?.webSocketUrl !== void 0) {
|
|
820
828
|
this.webSocketUrl = options.webSocketUrl;
|
|
821
829
|
}
|
|
830
|
+
if (options?.webSocketConnectionTimeout !== void 0) {
|
|
831
|
+
this.webSocketConnectionTimeout = options.webSocketConnectionTimeout;
|
|
832
|
+
}
|
|
833
|
+
if (options?.webSocketReconnect !== void 0) {
|
|
834
|
+
this.webSocketReconnect = options.webSocketReconnect;
|
|
835
|
+
}
|
|
836
|
+
if (options?.webSocketMaxReconnectAttempts !== void 0) {
|
|
837
|
+
this.webSocketMaxReconnectAttempts = options.webSocketMaxReconnectAttempts;
|
|
838
|
+
}
|
|
839
|
+
if (options?.webSocketInitialRetryDelay !== void 0) {
|
|
840
|
+
this.webSocketInitialRetryDelay = options.webSocketInitialRetryDelay;
|
|
841
|
+
}
|
|
842
|
+
if (options?.webSocketMaxRetryDelay !== void 0) {
|
|
843
|
+
this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
|
|
844
|
+
}
|
|
822
845
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
823
846
|
window.addEventListener("beforeunload", () => {
|
|
824
847
|
this.flushEventQueue();
|
|
825
848
|
this.flushContextDependentEventQueue();
|
|
826
849
|
});
|
|
850
|
+
if (this.useWebSocket) {
|
|
851
|
+
window.addEventListener("offline", () => {
|
|
852
|
+
this.debug("Browser went offline, closing WebSocket connection");
|
|
853
|
+
this.handleNetworkOffline();
|
|
854
|
+
});
|
|
855
|
+
window.addEventListener("online", () => {
|
|
856
|
+
this.debug("Browser came online, attempting to reconnect WebSocket");
|
|
857
|
+
this.handleNetworkOnline();
|
|
858
|
+
});
|
|
859
|
+
}
|
|
827
860
|
}
|
|
828
861
|
if (this.offlineEnabled) {
|
|
829
862
|
this.debug(
|
|
@@ -1088,6 +1121,13 @@ var Schematic = class {
|
|
|
1088
1121
|
try {
|
|
1089
1122
|
this.setIsPending(true);
|
|
1090
1123
|
if (!this.conn) {
|
|
1124
|
+
if (this.wsReconnectTimer !== null) {
|
|
1125
|
+
this.debug(
|
|
1126
|
+
`Cancelling scheduled reconnection, connecting immediately`
|
|
1127
|
+
);
|
|
1128
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1129
|
+
this.wsReconnectTimer = null;
|
|
1130
|
+
}
|
|
1091
1131
|
this.conn = this.wsConnect();
|
|
1092
1132
|
}
|
|
1093
1133
|
const socket = await this.conn;
|
|
@@ -1294,6 +1334,11 @@ var Schematic = class {
|
|
|
1294
1334
|
this.debug("cleanup: skipped (offline mode)");
|
|
1295
1335
|
return Promise.resolve();
|
|
1296
1336
|
}
|
|
1337
|
+
this.wsIntentionalDisconnect = true;
|
|
1338
|
+
if (this.wsReconnectTimer !== null) {
|
|
1339
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1340
|
+
this.wsReconnectTimer = null;
|
|
1341
|
+
}
|
|
1297
1342
|
if (this.conn) {
|
|
1298
1343
|
try {
|
|
1299
1344
|
const socket = await this.conn;
|
|
@@ -1305,6 +1350,91 @@ var Schematic = class {
|
|
|
1305
1350
|
}
|
|
1306
1351
|
}
|
|
1307
1352
|
};
|
|
1353
|
+
/**
|
|
1354
|
+
* Calculate the delay for the next reconnection attempt using exponential backoff with jitter.
|
|
1355
|
+
* This helps prevent dogpiling when the server recovers from an outage.
|
|
1356
|
+
*/
|
|
1357
|
+
calculateReconnectDelay = () => {
|
|
1358
|
+
const exponentialDelay = this.webSocketInitialRetryDelay * Math.pow(2, this.wsReconnectAttempts);
|
|
1359
|
+
const cappedDelay = Math.min(exponentialDelay, this.webSocketMaxRetryDelay);
|
|
1360
|
+
const jitter = Math.random() * cappedDelay * 0.5;
|
|
1361
|
+
const totalDelay = cappedDelay + jitter;
|
|
1362
|
+
this.debug(
|
|
1363
|
+
`Reconnect delay calculated: ${totalDelay.toFixed(0)}ms (attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts})`
|
|
1364
|
+
);
|
|
1365
|
+
return totalDelay;
|
|
1366
|
+
};
|
|
1367
|
+
/**
|
|
1368
|
+
* Handle browser going offline
|
|
1369
|
+
*/
|
|
1370
|
+
handleNetworkOffline = async () => {
|
|
1371
|
+
if (this.conn !== null) {
|
|
1372
|
+
try {
|
|
1373
|
+
const socket = await this.conn;
|
|
1374
|
+
socket.close();
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
this.debug("Error closing connection on offline:", error);
|
|
1377
|
+
}
|
|
1378
|
+
this.conn = null;
|
|
1379
|
+
}
|
|
1380
|
+
if (this.wsReconnectTimer !== null) {
|
|
1381
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1382
|
+
this.wsReconnectTimer = null;
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
/**
|
|
1386
|
+
* Handle browser coming back online
|
|
1387
|
+
*/
|
|
1388
|
+
handleNetworkOnline = () => {
|
|
1389
|
+
if (this.context.company === void 0 && this.context.user === void 0) {
|
|
1390
|
+
this.debug("No context set, skipping reconnection");
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
this.wsReconnectAttempts = 0;
|
|
1394
|
+
if (this.wsReconnectTimer !== null) {
|
|
1395
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1396
|
+
this.wsReconnectTimer = null;
|
|
1397
|
+
}
|
|
1398
|
+
this.debug("Network online, reconnecting immediately");
|
|
1399
|
+
this.attemptReconnect();
|
|
1400
|
+
};
|
|
1401
|
+
/**
|
|
1402
|
+
* Attempt to reconnect the WebSocket connection with exponential backoff.
|
|
1403
|
+
* Called automatically when the connection closes unexpectedly.
|
|
1404
|
+
*/
|
|
1405
|
+
attemptReconnect = () => {
|
|
1406
|
+
if (this.wsReconnectAttempts >= this.webSocketMaxReconnectAttempts) {
|
|
1407
|
+
this.debug(
|
|
1408
|
+
`Maximum reconnection attempts (${this.webSocketMaxReconnectAttempts}) reached, giving up`
|
|
1409
|
+
);
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (this.wsReconnectTimer !== null) {
|
|
1413
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1414
|
+
}
|
|
1415
|
+
const delay = this.calculateReconnectDelay();
|
|
1416
|
+
this.debug(
|
|
1417
|
+
`Scheduling reconnection attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts} in ${delay.toFixed(0)}ms`
|
|
1418
|
+
);
|
|
1419
|
+
this.wsReconnectTimer = setTimeout(async () => {
|
|
1420
|
+
this.wsReconnectTimer = null;
|
|
1421
|
+
this.wsReconnectAttempts++;
|
|
1422
|
+
this.debug(
|
|
1423
|
+
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1424
|
+
);
|
|
1425
|
+
try {
|
|
1426
|
+
this.conn = this.wsConnect();
|
|
1427
|
+
const socket = await this.conn;
|
|
1428
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1429
|
+
this.debug(`Reconnected, re-sending context`);
|
|
1430
|
+
await this.wsSendMessage(socket, this.context);
|
|
1431
|
+
}
|
|
1432
|
+
this.debug(`Reconnection successful`);
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
this.debug(`Reconnection attempt failed:`, error);
|
|
1435
|
+
}
|
|
1436
|
+
}, delay);
|
|
1437
|
+
};
|
|
1308
1438
|
// Open a websocket connection
|
|
1309
1439
|
wsConnect = () => {
|
|
1310
1440
|
if (this.isOffline()) {
|
|
@@ -1317,17 +1447,45 @@ var Schematic = class {
|
|
|
1317
1447
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1318
1448
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1319
1449
|
const webSocket = new WebSocket(wsUrl);
|
|
1450
|
+
let timeoutId = null;
|
|
1451
|
+
let isResolved = false;
|
|
1452
|
+
timeoutId = setTimeout(() => {
|
|
1453
|
+
if (!isResolved) {
|
|
1454
|
+
this.debug(
|
|
1455
|
+
`WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`
|
|
1456
|
+
);
|
|
1457
|
+
webSocket.close();
|
|
1458
|
+
reject(new Error("WebSocket connection timeout"));
|
|
1459
|
+
}
|
|
1460
|
+
}, this.webSocketConnectionTimeout);
|
|
1320
1461
|
webSocket.onopen = () => {
|
|
1462
|
+
isResolved = true;
|
|
1463
|
+
if (timeoutId !== null) {
|
|
1464
|
+
clearTimeout(timeoutId);
|
|
1465
|
+
}
|
|
1466
|
+
this.wsReconnectAttempts = 0;
|
|
1467
|
+
this.wsIntentionalDisconnect = false;
|
|
1321
1468
|
this.debug(`WebSocket connection opened`);
|
|
1322
1469
|
resolve(webSocket);
|
|
1323
1470
|
};
|
|
1324
1471
|
webSocket.onerror = (error) => {
|
|
1472
|
+
isResolved = true;
|
|
1473
|
+
if (timeoutId !== null) {
|
|
1474
|
+
clearTimeout(timeoutId);
|
|
1475
|
+
}
|
|
1325
1476
|
this.debug(`WebSocket connection error:`, error);
|
|
1326
1477
|
reject(error);
|
|
1327
1478
|
};
|
|
1328
1479
|
webSocket.onclose = () => {
|
|
1480
|
+
isResolved = true;
|
|
1481
|
+
if (timeoutId !== null) {
|
|
1482
|
+
clearTimeout(timeoutId);
|
|
1483
|
+
}
|
|
1329
1484
|
this.debug(`WebSocket connection closed`);
|
|
1330
1485
|
this.conn = null;
|
|
1486
|
+
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1487
|
+
this.attemptReconnect();
|
|
1488
|
+
}
|
|
1331
1489
|
};
|
|
1332
1490
|
});
|
|
1333
1491
|
};
|
|
@@ -1519,7 +1677,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1519
1677
|
import React, { createContext, useEffect, useMemo, useRef } from "react";
|
|
1520
1678
|
|
|
1521
1679
|
// src/version.ts
|
|
1522
|
-
var version2 = "1.2.
|
|
1680
|
+
var version2 = "1.2.7";
|
|
1523
1681
|
|
|
1524
1682
|
// src/context/schematic.tsx
|
|
1525
1683
|
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.7",
|
|
4
4
|
"main": "dist/schematic-react.cjs.js",
|
|
5
5
|
"module": "dist/schematic-react.esm.js",
|
|
6
6
|
"types": "dist/schematic-react.d.ts",
|
|
@@ -24,40 +24,38 @@
|
|
|
24
24
|
"clean": "rm -rf dist",
|
|
25
25
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
26
26
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --fix",
|
|
27
|
-
"test": "
|
|
28
|
-
"test:reactnative": "
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:reactnative": "vitest run --config vitest.config.reactnative.ts",
|
|
29
|
+
"test:watch": "vitest",
|
|
29
30
|
"tsc": "npx tsc",
|
|
30
31
|
"prepare": "husky"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@schematichq/schematic-js": "^1.2.
|
|
34
|
+
"@schematichq/schematic-js": "^1.2.7"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
|
-
"@eslint/js": "^9.
|
|
37
|
-
"@microsoft/api-extractor": "^7.
|
|
37
|
+
"@eslint/js": "^9.39.1",
|
|
38
|
+
"@microsoft/api-extractor": "^7.55.0",
|
|
38
39
|
"@testing-library/dom": "^10.4.1",
|
|
39
|
-
"@testing-library/jest-dom": "^6.
|
|
40
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
40
41
|
"@testing-library/react": "^16.3.0",
|
|
41
|
-
"@types/
|
|
42
|
-
"@
|
|
43
|
-
"esbuild": "^0.
|
|
44
|
-
"
|
|
45
|
-
"eslint": "^9.24.0",
|
|
42
|
+
"@types/react": "^19.2.3",
|
|
43
|
+
"@vitest/browser": "^4.0.8",
|
|
44
|
+
"esbuild": "^0.27.0",
|
|
45
|
+
"eslint": "^9.39.1",
|
|
46
46
|
"eslint-plugin-import": "^2.32.0",
|
|
47
47
|
"eslint-plugin-react": "^7.37.5",
|
|
48
|
-
"eslint-plugin-react-hooks": "^
|
|
49
|
-
"globals": "^16.
|
|
48
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
49
|
+
"globals": "^16.5.0",
|
|
50
|
+
"happy-dom": "^20.0.10",
|
|
50
51
|
"husky": "^9.1.7",
|
|
51
|
-
"
|
|
52
|
-
"jest-environment-jsdom": "^30.0.0",
|
|
53
|
-
"jest-esbuild": "^0.4.0",
|
|
54
|
-
"jest-fetch-mock": "^3.0.3",
|
|
52
|
+
"jsdom": "^27.2.0",
|
|
55
53
|
"prettier": "^3.6.2",
|
|
56
|
-
"react": "^19.
|
|
57
|
-
"react-dom": "^19.
|
|
58
|
-
"
|
|
59
|
-
"typescript": "^
|
|
60
|
-
"
|
|
54
|
+
"react": "^19.2.0",
|
|
55
|
+
"react-dom": "^19.2.0",
|
|
56
|
+
"typescript": "^5.9.3",
|
|
57
|
+
"typescript-eslint": "^8.46.4",
|
|
58
|
+
"vitest": "^4.0.8"
|
|
61
59
|
},
|
|
62
60
|
"peerDependencies": {
|
|
63
61
|
"react": ">=18"
|