@schematichq/schematic-react 1.2.9 → 1.2.10
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 +186 -118
- package/dist/schematic-react.esm.js +186 -118
- package/package.json +2 -2
|
@@ -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.10";
|
|
803
803
|
var anonymousIdKey = "schematicId";
|
|
804
804
|
var Schematic = class {
|
|
805
805
|
additionalHeaders = {};
|
|
@@ -829,6 +829,8 @@ var Schematic = class {
|
|
|
829
829
|
wsReconnectAttempts = 0;
|
|
830
830
|
wsReconnectTimer = null;
|
|
831
831
|
wsIntentionalDisconnect = false;
|
|
832
|
+
currentWebSocket = null;
|
|
833
|
+
isConnecting = false;
|
|
832
834
|
maxEventQueueSize = 100;
|
|
833
835
|
// Prevent memory issues with very long network outages
|
|
834
836
|
maxEventRetries = 5;
|
|
@@ -1106,6 +1108,49 @@ var Schematic = class {
|
|
|
1106
1108
|
console.log(`[Schematic] ${message}`, ...args);
|
|
1107
1109
|
}
|
|
1108
1110
|
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Create a persistent message handler for websocket flag updates
|
|
1113
|
+
*/
|
|
1114
|
+
createPersistentMessageHandler(context) {
|
|
1115
|
+
return (event) => {
|
|
1116
|
+
const message = JSON.parse(event.data);
|
|
1117
|
+
this.debug(`WebSocket persistent message received:`, message);
|
|
1118
|
+
if (!(contextString(context) in this.checks)) {
|
|
1119
|
+
this.checks[contextString(context)] = {};
|
|
1120
|
+
}
|
|
1121
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1122
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1123
|
+
const contextStr = contextString(context);
|
|
1124
|
+
if (this.checks[contextStr] === void 0) {
|
|
1125
|
+
this.checks[contextStr] = {};
|
|
1126
|
+
}
|
|
1127
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1128
|
+
this.debug(`WebSocket flag update:`, {
|
|
1129
|
+
flag: flagCheck.flag,
|
|
1130
|
+
value: flagCheck.value,
|
|
1131
|
+
flagCheck
|
|
1132
|
+
});
|
|
1133
|
+
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1134
|
+
this.updateFeatureUsageEventMap(flagCheck);
|
|
1135
|
+
}
|
|
1136
|
+
if ((this.flagCheckListeners[flag.flag]?.size ?? 0) > 0 || (this.flagValueListeners[flag.flag]?.size ?? 0) > 0) {
|
|
1137
|
+
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1138
|
+
}
|
|
1139
|
+
this.debug(`About to notify listeners for flag ${flag.flag}`, {
|
|
1140
|
+
flag: flag.flag,
|
|
1141
|
+
value: flagCheck.value
|
|
1142
|
+
});
|
|
1143
|
+
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1144
|
+
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1145
|
+
this.debug(`Finished notifying listeners for flag ${flag.flag}`, {
|
|
1146
|
+
flag: flag.flag,
|
|
1147
|
+
value: flagCheck.value
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
this.flushContextDependentEventQueue();
|
|
1151
|
+
this.setIsPending(false);
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1109
1154
|
/**
|
|
1110
1155
|
* Helper function to check if client is in offline mode
|
|
1111
1156
|
*/
|
|
@@ -1257,6 +1302,19 @@ var Schematic = class {
|
|
|
1257
1302
|
try {
|
|
1258
1303
|
this.setIsPending(true);
|
|
1259
1304
|
if (!this.conn) {
|
|
1305
|
+
if (this.isConnecting) {
|
|
1306
|
+
this.debug(
|
|
1307
|
+
`Connection already in progress, waiting for it to complete`
|
|
1308
|
+
);
|
|
1309
|
+
while (this.isConnecting && this.conn === null) {
|
|
1310
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1311
|
+
}
|
|
1312
|
+
if (this.conn !== null) {
|
|
1313
|
+
const socket2 = await this.conn;
|
|
1314
|
+
await this.wsSendMessage(socket2, context);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1260
1318
|
if (this.wsReconnectTimer !== null) {
|
|
1261
1319
|
this.debug(
|
|
1262
1320
|
`Cancelling scheduled reconnection, connecting immediately`
|
|
@@ -1264,7 +1322,17 @@ var Schematic = class {
|
|
|
1264
1322
|
clearTimeout(this.wsReconnectTimer);
|
|
1265
1323
|
this.wsReconnectTimer = null;
|
|
1266
1324
|
}
|
|
1267
|
-
this.
|
|
1325
|
+
this.isConnecting = true;
|
|
1326
|
+
try {
|
|
1327
|
+
this.conn = this.wsConnect();
|
|
1328
|
+
const socket2 = await this.conn;
|
|
1329
|
+
this.isConnecting = false;
|
|
1330
|
+
await this.wsSendMessage(socket2, context);
|
|
1331
|
+
return;
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
this.isConnecting = false;
|
|
1334
|
+
throw error;
|
|
1335
|
+
}
|
|
1268
1336
|
}
|
|
1269
1337
|
const socket = await this.conn;
|
|
1270
1338
|
await this.wsSendMessage(socket, context);
|
|
@@ -1429,10 +1497,14 @@ var Schematic = class {
|
|
|
1429
1497
|
}
|
|
1430
1498
|
}
|
|
1431
1499
|
if (readyEvents.length === 0) {
|
|
1432
|
-
this.debug(
|
|
1500
|
+
this.debug(
|
|
1501
|
+
`No events ready for retry yet (${notReadyEvents.length} still in backoff)`
|
|
1502
|
+
);
|
|
1433
1503
|
return;
|
|
1434
1504
|
}
|
|
1435
|
-
this.debug(
|
|
1505
|
+
this.debug(
|
|
1506
|
+
`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`
|
|
1507
|
+
);
|
|
1436
1508
|
this.eventQueue = notReadyEvents;
|
|
1437
1509
|
for (const event of readyEvents) {
|
|
1438
1510
|
try {
|
|
@@ -1497,7 +1569,10 @@ var Schematic = class {
|
|
|
1497
1569
|
} catch (error) {
|
|
1498
1570
|
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1499
1571
|
if (retryCount <= this.maxEventRetries) {
|
|
1500
|
-
this.debug(
|
|
1572
|
+
this.debug(
|
|
1573
|
+
`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`,
|
|
1574
|
+
error
|
|
1575
|
+
);
|
|
1501
1576
|
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1502
1577
|
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1503
1578
|
const nextRetryAt = Date.now() + jitterDelay;
|
|
@@ -1508,15 +1583,22 @@ var Schematic = class {
|
|
|
1508
1583
|
};
|
|
1509
1584
|
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1510
1585
|
this.eventQueue.push(retryEvent);
|
|
1511
|
-
this.debug(
|
|
1586
|
+
this.debug(
|
|
1587
|
+
`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`
|
|
1588
|
+
);
|
|
1512
1589
|
} else {
|
|
1513
|
-
this.debug(
|
|
1590
|
+
this.debug(
|
|
1591
|
+
`Event queue full (${this.maxEventQueueSize}), dropping oldest event`
|
|
1592
|
+
);
|
|
1514
1593
|
this.eventQueue.shift();
|
|
1515
1594
|
this.eventQueue.push(retryEvent);
|
|
1516
1595
|
}
|
|
1517
1596
|
this.startRetryTimer();
|
|
1518
1597
|
} else {
|
|
1519
|
-
this.debug(
|
|
1598
|
+
this.debug(
|
|
1599
|
+
`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`,
|
|
1600
|
+
error
|
|
1601
|
+
);
|
|
1520
1602
|
}
|
|
1521
1603
|
}
|
|
1522
1604
|
return Promise.resolve();
|
|
@@ -1546,11 +1628,17 @@ var Schematic = class {
|
|
|
1546
1628
|
if (this.conn) {
|
|
1547
1629
|
try {
|
|
1548
1630
|
const socket = await this.conn;
|
|
1631
|
+
if (this.currentWebSocket === socket) {
|
|
1632
|
+
this.debug(`Cleaning up current websocket tracking`);
|
|
1633
|
+
this.currentWebSocket = null;
|
|
1634
|
+
}
|
|
1549
1635
|
socket.close();
|
|
1550
1636
|
} catch (error) {
|
|
1551
1637
|
console.error("Error during cleanup:", error);
|
|
1552
1638
|
} finally {
|
|
1553
1639
|
this.conn = null;
|
|
1640
|
+
this.currentWebSocket = null;
|
|
1641
|
+
this.isConnecting = false;
|
|
1554
1642
|
}
|
|
1555
1643
|
}
|
|
1556
1644
|
};
|
|
@@ -1575,7 +1663,9 @@ var Schematic = class {
|
|
|
1575
1663
|
if (this.conn !== null) {
|
|
1576
1664
|
try {
|
|
1577
1665
|
const socket = await this.conn;
|
|
1578
|
-
socket.
|
|
1666
|
+
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
|
|
1667
|
+
socket.close();
|
|
1668
|
+
}
|
|
1579
1669
|
} catch (error) {
|
|
1580
1670
|
this.debug("Error closing connection on offline:", error);
|
|
1581
1671
|
}
|
|
@@ -1590,7 +1680,9 @@ var Schematic = class {
|
|
|
1590
1680
|
* Handle browser coming back online
|
|
1591
1681
|
*/
|
|
1592
1682
|
handleNetworkOnline = () => {
|
|
1593
|
-
this.debug(
|
|
1683
|
+
this.debug(
|
|
1684
|
+
"Network online, attempting reconnection and flushing queued events"
|
|
1685
|
+
);
|
|
1594
1686
|
this.wsReconnectAttempts = 0;
|
|
1595
1687
|
if (this.wsReconnectTimer !== null) {
|
|
1596
1688
|
clearTimeout(this.wsReconnectTimer);
|
|
@@ -1613,7 +1705,10 @@ var Schematic = class {
|
|
|
1613
1705
|
return;
|
|
1614
1706
|
}
|
|
1615
1707
|
if (this.wsReconnectTimer !== null) {
|
|
1616
|
-
|
|
1708
|
+
this.debug(
|
|
1709
|
+
`Reconnection attempt already scheduled, ignoring duplicate request`
|
|
1710
|
+
);
|
|
1711
|
+
return;
|
|
1617
1712
|
}
|
|
1618
1713
|
const delay = this.calculateReconnectDelay();
|
|
1619
1714
|
this.debug(
|
|
@@ -1626,23 +1721,57 @@ var Schematic = class {
|
|
|
1626
1721
|
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1627
1722
|
);
|
|
1628
1723
|
try {
|
|
1629
|
-
this.conn
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1724
|
+
if (this.conn !== null) {
|
|
1725
|
+
this.debug(`Cleaning up existing connection before reconnection`);
|
|
1726
|
+
try {
|
|
1727
|
+
const existingSocket = await this.conn;
|
|
1728
|
+
if (this.currentWebSocket === existingSocket) {
|
|
1729
|
+
this.debug(`Existing websocket is current, will be replaced`);
|
|
1730
|
+
this.currentWebSocket = null;
|
|
1731
|
+
}
|
|
1732
|
+
if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
|
|
1733
|
+
existingSocket.close();
|
|
1734
|
+
}
|
|
1735
|
+
} catch (error) {
|
|
1736
|
+
this.debug(`Error cleaning up existing connection:`, error);
|
|
1737
|
+
}
|
|
1738
|
+
this.conn = null;
|
|
1739
|
+
this.currentWebSocket = null;
|
|
1740
|
+
this.isConnecting = false;
|
|
1741
|
+
}
|
|
1742
|
+
this.isConnecting = true;
|
|
1743
|
+
try {
|
|
1744
|
+
this.conn = this.wsConnect();
|
|
1745
|
+
const socket = await this.conn;
|
|
1746
|
+
this.isConnecting = false;
|
|
1747
|
+
this.debug(`Reconnection context check:`, {
|
|
1748
|
+
hasCompany: this.context.company !== void 0,
|
|
1749
|
+
hasUser: this.context.user !== void 0,
|
|
1750
|
+
context: this.context
|
|
1751
|
+
});
|
|
1752
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1753
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1754
|
+
await this.wsSendMessage(socket, this.context, true);
|
|
1755
|
+
} else {
|
|
1756
|
+
this.debug(
|
|
1757
|
+
`No context to re-send after reconnection - websocket ready for new context`
|
|
1758
|
+
);
|
|
1759
|
+
this.debug(
|
|
1760
|
+
`Setting up tracking for reconnected websocket (no context to send)`
|
|
1761
|
+
);
|
|
1762
|
+
this.currentWebSocket = socket;
|
|
1763
|
+
}
|
|
1764
|
+
this.flushEventQueue().catch((error) => {
|
|
1765
|
+
this.debug(
|
|
1766
|
+
"Error flushing event queue after websocket reconnection:",
|
|
1767
|
+
error
|
|
1768
|
+
);
|
|
1769
|
+
});
|
|
1770
|
+
this.debug(`Reconnection successful`);
|
|
1771
|
+
} catch (error) {
|
|
1772
|
+
this.isConnecting = false;
|
|
1773
|
+
throw error;
|
|
1641
1774
|
}
|
|
1642
|
-
this.flushEventQueue().catch((error) => {
|
|
1643
|
-
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1644
|
-
});
|
|
1645
|
-
this.debug(`Reconnection successful`);
|
|
1646
1775
|
} catch (error) {
|
|
1647
1776
|
this.debug(`Reconnection attempt failed:`, error);
|
|
1648
1777
|
}
|
|
@@ -1660,6 +1789,8 @@ var Schematic = class {
|
|
|
1660
1789
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1661
1790
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1662
1791
|
const webSocket = new WebSocket(wsUrl);
|
|
1792
|
+
const connectionId = Math.random().toString(36).substring(7);
|
|
1793
|
+
this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
|
|
1663
1794
|
let timeoutId = null;
|
|
1664
1795
|
let isResolved = false;
|
|
1665
1796
|
timeoutId = setTimeout(() => {
|
|
@@ -1678,7 +1809,7 @@ var Schematic = class {
|
|
|
1678
1809
|
}
|
|
1679
1810
|
this.wsReconnectAttempts = 0;
|
|
1680
1811
|
this.wsIntentionalDisconnect = false;
|
|
1681
|
-
this.debug(`WebSocket connection opened`);
|
|
1812
|
+
this.debug(`WebSocket connection ${connectionId} opened successfully`);
|
|
1682
1813
|
resolve(webSocket);
|
|
1683
1814
|
};
|
|
1684
1815
|
webSocket.onerror = (error) => {
|
|
@@ -1686,7 +1817,7 @@ var Schematic = class {
|
|
|
1686
1817
|
if (timeoutId !== null) {
|
|
1687
1818
|
clearTimeout(timeoutId);
|
|
1688
1819
|
}
|
|
1689
|
-
this.debug(`WebSocket connection error:`, error);
|
|
1820
|
+
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1690
1821
|
reject(error);
|
|
1691
1822
|
};
|
|
1692
1823
|
webSocket.onclose = () => {
|
|
@@ -1694,121 +1825,48 @@ var Schematic = class {
|
|
|
1694
1825
|
if (timeoutId !== null) {
|
|
1695
1826
|
clearTimeout(timeoutId);
|
|
1696
1827
|
}
|
|
1697
|
-
this.debug(`WebSocket connection closed`);
|
|
1828
|
+
this.debug(`WebSocket connection ${connectionId} closed`);
|
|
1698
1829
|
this.conn = null;
|
|
1830
|
+
if (this.currentWebSocket === webSocket) {
|
|
1831
|
+
this.currentWebSocket = null;
|
|
1832
|
+
this.isConnecting = false;
|
|
1833
|
+
}
|
|
1699
1834
|
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1700
1835
|
this.attemptReconnect();
|
|
1701
1836
|
}
|
|
1702
1837
|
};
|
|
1703
1838
|
});
|
|
1704
1839
|
};
|
|
1705
|
-
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1706
|
-
// because the server has lost all state and needs the initial context
|
|
1707
|
-
wsSendContextAfterReconnection = (socket, context) => {
|
|
1708
|
-
if (this.isOffline()) {
|
|
1709
|
-
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1710
|
-
this.setIsPending(false);
|
|
1711
|
-
return Promise.resolve();
|
|
1712
|
-
}
|
|
1713
|
-
return new Promise((resolve) => {
|
|
1714
|
-
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1715
|
-
this.context = context;
|
|
1716
|
-
const sendMessage = () => {
|
|
1717
|
-
let resolved = false;
|
|
1718
|
-
const messageHandler = (event) => {
|
|
1719
|
-
const message = JSON.parse(event.data);
|
|
1720
|
-
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1721
|
-
if (!(contextString(context) in this.checks)) {
|
|
1722
|
-
this.checks[contextString(context)] = {};
|
|
1723
|
-
}
|
|
1724
|
-
(message.flags ?? []).forEach((flag) => {
|
|
1725
|
-
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1726
|
-
const contextStr = contextString(context);
|
|
1727
|
-
if (this.checks[contextStr] === void 0) {
|
|
1728
|
-
this.checks[contextStr] = {};
|
|
1729
|
-
}
|
|
1730
|
-
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1731
|
-
});
|
|
1732
|
-
this.useWebSocket = true;
|
|
1733
|
-
socket.removeEventListener("message", messageHandler);
|
|
1734
|
-
if (!resolved) {
|
|
1735
|
-
resolved = true;
|
|
1736
|
-
resolve(this.setIsPending(false));
|
|
1737
|
-
}
|
|
1738
|
-
};
|
|
1739
|
-
socket.addEventListener("message", messageHandler);
|
|
1740
|
-
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1741
|
-
const messagePayload = {
|
|
1742
|
-
apiKey: this.apiKey,
|
|
1743
|
-
clientVersion,
|
|
1744
|
-
data: context
|
|
1745
|
-
};
|
|
1746
|
-
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1747
|
-
socket.send(JSON.stringify(messagePayload));
|
|
1748
|
-
};
|
|
1749
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
1750
|
-
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1751
|
-
sendMessage();
|
|
1752
|
-
} else {
|
|
1753
|
-
socket.addEventListener("open", () => {
|
|
1754
|
-
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1755
|
-
sendMessage();
|
|
1756
|
-
});
|
|
1757
|
-
}
|
|
1758
|
-
});
|
|
1759
|
-
};
|
|
1760
1840
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1761
1841
|
// and wait for the initial set of flag values to be returned
|
|
1762
|
-
wsSendMessage = (socket, context) => {
|
|
1842
|
+
wsSendMessage = (socket, context, forceContextSend = false) => {
|
|
1763
1843
|
if (this.isOffline()) {
|
|
1764
1844
|
this.debug("wsSendMessage: skipped (offline mode)");
|
|
1765
1845
|
this.setIsPending(false);
|
|
1766
1846
|
return Promise.resolve();
|
|
1767
1847
|
}
|
|
1768
1848
|
return new Promise((resolve, reject) => {
|
|
1769
|
-
if (contextString(context) == contextString(this.context)) {
|
|
1849
|
+
if (!forceContextSend && contextString(context) == contextString(this.context)) {
|
|
1770
1850
|
this.debug(`WebSocket context unchanged, skipping update`);
|
|
1771
1851
|
return resolve(this.setIsPending(false));
|
|
1772
1852
|
}
|
|
1773
|
-
this.debug(
|
|
1853
|
+
this.debug(
|
|
1854
|
+
forceContextSend ? `WebSocket force sending context (reconnection):` : `WebSocket context updated:`,
|
|
1855
|
+
context
|
|
1856
|
+
);
|
|
1774
1857
|
this.context = context;
|
|
1775
1858
|
const sendMessage = () => {
|
|
1776
1859
|
let resolved = false;
|
|
1860
|
+
const persistentMessageHandler = this.createPersistentMessageHandler(context);
|
|
1777
1861
|
const messageHandler = (event) => {
|
|
1778
|
-
|
|
1779
|
-
this.debug(`WebSocket message received:`, message);
|
|
1780
|
-
if (!(contextString(context) in this.checks)) {
|
|
1781
|
-
this.checks[contextString(context)] = {};
|
|
1782
|
-
}
|
|
1783
|
-
(message.flags ?? []).forEach((flag) => {
|
|
1784
|
-
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1785
|
-
const contextStr = contextString(context);
|
|
1786
|
-
if (this.checks[contextStr] === void 0) {
|
|
1787
|
-
this.checks[contextStr] = {};
|
|
1788
|
-
}
|
|
1789
|
-
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1790
|
-
this.debug(`WebSocket flag update:`, {
|
|
1791
|
-
flag: flagCheck.flag,
|
|
1792
|
-
value: flagCheck.value,
|
|
1793
|
-
flagCheck
|
|
1794
|
-
});
|
|
1795
|
-
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1796
|
-
this.updateFeatureUsageEventMap(flagCheck);
|
|
1797
|
-
}
|
|
1798
|
-
if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
|
|
1799
|
-
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1800
|
-
}
|
|
1801
|
-
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1802
|
-
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1803
|
-
});
|
|
1804
|
-
this.flushContextDependentEventQueue();
|
|
1805
|
-
this.setIsPending(false);
|
|
1862
|
+
persistentMessageHandler(event);
|
|
1806
1863
|
if (!resolved) {
|
|
1807
1864
|
resolved = true;
|
|
1808
1865
|
resolve();
|
|
1809
1866
|
}
|
|
1810
1867
|
};
|
|
1811
1868
|
socket.addEventListener("message", messageHandler);
|
|
1869
|
+
this.currentWebSocket = socket;
|
|
1812
1870
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1813
1871
|
const messagePayload = {
|
|
1814
1872
|
apiKey: this.apiKey,
|
|
@@ -1916,7 +1974,17 @@ var Schematic = class {
|
|
|
1916
1974
|
{ value }
|
|
1917
1975
|
);
|
|
1918
1976
|
}
|
|
1919
|
-
listeners.forEach((listener) =>
|
|
1977
|
+
listeners.forEach((listener, index) => {
|
|
1978
|
+
this.debug(`Calling listener ${index} for flag ${flagKey}`, {
|
|
1979
|
+
flagKey,
|
|
1980
|
+
value
|
|
1981
|
+
});
|
|
1982
|
+
notifyFlagValueListener(listener, value);
|
|
1983
|
+
this.debug(`Listener ${index} for flag ${flagKey} completed`, {
|
|
1984
|
+
flagKey,
|
|
1985
|
+
value
|
|
1986
|
+
});
|
|
1987
|
+
});
|
|
1920
1988
|
};
|
|
1921
1989
|
};
|
|
1922
1990
|
var notifyPendingListener = (listener, value) => {
|
|
@@ -1945,7 +2013,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1945
2013
|
var import_react = __toESM(require("react"));
|
|
1946
2014
|
|
|
1947
2015
|
// src/version.ts
|
|
1948
|
-
var version2 = "1.2.
|
|
2016
|
+
var version2 = "1.2.10";
|
|
1949
2017
|
|
|
1950
2018
|
// src/context/schematic.tsx
|
|
1951
2019
|
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.10";
|
|
758
758
|
var anonymousIdKey = "schematicId";
|
|
759
759
|
var Schematic = class {
|
|
760
760
|
additionalHeaders = {};
|
|
@@ -784,6 +784,8 @@ var Schematic = class {
|
|
|
784
784
|
wsReconnectAttempts = 0;
|
|
785
785
|
wsReconnectTimer = null;
|
|
786
786
|
wsIntentionalDisconnect = false;
|
|
787
|
+
currentWebSocket = null;
|
|
788
|
+
isConnecting = false;
|
|
787
789
|
maxEventQueueSize = 100;
|
|
788
790
|
// Prevent memory issues with very long network outages
|
|
789
791
|
maxEventRetries = 5;
|
|
@@ -1061,6 +1063,49 @@ var Schematic = class {
|
|
|
1061
1063
|
console.log(`[Schematic] ${message}`, ...args);
|
|
1062
1064
|
}
|
|
1063
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Create a persistent message handler for websocket flag updates
|
|
1068
|
+
*/
|
|
1069
|
+
createPersistentMessageHandler(context) {
|
|
1070
|
+
return (event) => {
|
|
1071
|
+
const message = JSON.parse(event.data);
|
|
1072
|
+
this.debug(`WebSocket persistent message received:`, message);
|
|
1073
|
+
if (!(contextString(context) in this.checks)) {
|
|
1074
|
+
this.checks[contextString(context)] = {};
|
|
1075
|
+
}
|
|
1076
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1077
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1078
|
+
const contextStr = contextString(context);
|
|
1079
|
+
if (this.checks[contextStr] === void 0) {
|
|
1080
|
+
this.checks[contextStr] = {};
|
|
1081
|
+
}
|
|
1082
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1083
|
+
this.debug(`WebSocket flag update:`, {
|
|
1084
|
+
flag: flagCheck.flag,
|
|
1085
|
+
value: flagCheck.value,
|
|
1086
|
+
flagCheck
|
|
1087
|
+
});
|
|
1088
|
+
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1089
|
+
this.updateFeatureUsageEventMap(flagCheck);
|
|
1090
|
+
}
|
|
1091
|
+
if ((this.flagCheckListeners[flag.flag]?.size ?? 0) > 0 || (this.flagValueListeners[flag.flag]?.size ?? 0) > 0) {
|
|
1092
|
+
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1093
|
+
}
|
|
1094
|
+
this.debug(`About to notify listeners for flag ${flag.flag}`, {
|
|
1095
|
+
flag: flag.flag,
|
|
1096
|
+
value: flagCheck.value
|
|
1097
|
+
});
|
|
1098
|
+
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1099
|
+
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1100
|
+
this.debug(`Finished notifying listeners for flag ${flag.flag}`, {
|
|
1101
|
+
flag: flag.flag,
|
|
1102
|
+
value: flagCheck.value
|
|
1103
|
+
});
|
|
1104
|
+
});
|
|
1105
|
+
this.flushContextDependentEventQueue();
|
|
1106
|
+
this.setIsPending(false);
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1064
1109
|
/**
|
|
1065
1110
|
* Helper function to check if client is in offline mode
|
|
1066
1111
|
*/
|
|
@@ -1212,6 +1257,19 @@ var Schematic = class {
|
|
|
1212
1257
|
try {
|
|
1213
1258
|
this.setIsPending(true);
|
|
1214
1259
|
if (!this.conn) {
|
|
1260
|
+
if (this.isConnecting) {
|
|
1261
|
+
this.debug(
|
|
1262
|
+
`Connection already in progress, waiting for it to complete`
|
|
1263
|
+
);
|
|
1264
|
+
while (this.isConnecting && this.conn === null) {
|
|
1265
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1266
|
+
}
|
|
1267
|
+
if (this.conn !== null) {
|
|
1268
|
+
const socket2 = await this.conn;
|
|
1269
|
+
await this.wsSendMessage(socket2, context);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1215
1273
|
if (this.wsReconnectTimer !== null) {
|
|
1216
1274
|
this.debug(
|
|
1217
1275
|
`Cancelling scheduled reconnection, connecting immediately`
|
|
@@ -1219,7 +1277,17 @@ var Schematic = class {
|
|
|
1219
1277
|
clearTimeout(this.wsReconnectTimer);
|
|
1220
1278
|
this.wsReconnectTimer = null;
|
|
1221
1279
|
}
|
|
1222
|
-
this.
|
|
1280
|
+
this.isConnecting = true;
|
|
1281
|
+
try {
|
|
1282
|
+
this.conn = this.wsConnect();
|
|
1283
|
+
const socket2 = await this.conn;
|
|
1284
|
+
this.isConnecting = false;
|
|
1285
|
+
await this.wsSendMessage(socket2, context);
|
|
1286
|
+
return;
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
this.isConnecting = false;
|
|
1289
|
+
throw error;
|
|
1290
|
+
}
|
|
1223
1291
|
}
|
|
1224
1292
|
const socket = await this.conn;
|
|
1225
1293
|
await this.wsSendMessage(socket, context);
|
|
@@ -1384,10 +1452,14 @@ var Schematic = class {
|
|
|
1384
1452
|
}
|
|
1385
1453
|
}
|
|
1386
1454
|
if (readyEvents.length === 0) {
|
|
1387
|
-
this.debug(
|
|
1455
|
+
this.debug(
|
|
1456
|
+
`No events ready for retry yet (${notReadyEvents.length} still in backoff)`
|
|
1457
|
+
);
|
|
1388
1458
|
return;
|
|
1389
1459
|
}
|
|
1390
|
-
this.debug(
|
|
1460
|
+
this.debug(
|
|
1461
|
+
`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`
|
|
1462
|
+
);
|
|
1391
1463
|
this.eventQueue = notReadyEvents;
|
|
1392
1464
|
for (const event of readyEvents) {
|
|
1393
1465
|
try {
|
|
@@ -1452,7 +1524,10 @@ var Schematic = class {
|
|
|
1452
1524
|
} catch (error) {
|
|
1453
1525
|
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1454
1526
|
if (retryCount <= this.maxEventRetries) {
|
|
1455
|
-
this.debug(
|
|
1527
|
+
this.debug(
|
|
1528
|
+
`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`,
|
|
1529
|
+
error
|
|
1530
|
+
);
|
|
1456
1531
|
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1457
1532
|
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1458
1533
|
const nextRetryAt = Date.now() + jitterDelay;
|
|
@@ -1463,15 +1538,22 @@ var Schematic = class {
|
|
|
1463
1538
|
};
|
|
1464
1539
|
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1465
1540
|
this.eventQueue.push(retryEvent);
|
|
1466
|
-
this.debug(
|
|
1541
|
+
this.debug(
|
|
1542
|
+
`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`
|
|
1543
|
+
);
|
|
1467
1544
|
} else {
|
|
1468
|
-
this.debug(
|
|
1545
|
+
this.debug(
|
|
1546
|
+
`Event queue full (${this.maxEventQueueSize}), dropping oldest event`
|
|
1547
|
+
);
|
|
1469
1548
|
this.eventQueue.shift();
|
|
1470
1549
|
this.eventQueue.push(retryEvent);
|
|
1471
1550
|
}
|
|
1472
1551
|
this.startRetryTimer();
|
|
1473
1552
|
} else {
|
|
1474
|
-
this.debug(
|
|
1553
|
+
this.debug(
|
|
1554
|
+
`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`,
|
|
1555
|
+
error
|
|
1556
|
+
);
|
|
1475
1557
|
}
|
|
1476
1558
|
}
|
|
1477
1559
|
return Promise.resolve();
|
|
@@ -1501,11 +1583,17 @@ var Schematic = class {
|
|
|
1501
1583
|
if (this.conn) {
|
|
1502
1584
|
try {
|
|
1503
1585
|
const socket = await this.conn;
|
|
1586
|
+
if (this.currentWebSocket === socket) {
|
|
1587
|
+
this.debug(`Cleaning up current websocket tracking`);
|
|
1588
|
+
this.currentWebSocket = null;
|
|
1589
|
+
}
|
|
1504
1590
|
socket.close();
|
|
1505
1591
|
} catch (error) {
|
|
1506
1592
|
console.error("Error during cleanup:", error);
|
|
1507
1593
|
} finally {
|
|
1508
1594
|
this.conn = null;
|
|
1595
|
+
this.currentWebSocket = null;
|
|
1596
|
+
this.isConnecting = false;
|
|
1509
1597
|
}
|
|
1510
1598
|
}
|
|
1511
1599
|
};
|
|
@@ -1530,7 +1618,9 @@ var Schematic = class {
|
|
|
1530
1618
|
if (this.conn !== null) {
|
|
1531
1619
|
try {
|
|
1532
1620
|
const socket = await this.conn;
|
|
1533
|
-
socket.
|
|
1621
|
+
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
|
|
1622
|
+
socket.close();
|
|
1623
|
+
}
|
|
1534
1624
|
} catch (error) {
|
|
1535
1625
|
this.debug("Error closing connection on offline:", error);
|
|
1536
1626
|
}
|
|
@@ -1545,7 +1635,9 @@ var Schematic = class {
|
|
|
1545
1635
|
* Handle browser coming back online
|
|
1546
1636
|
*/
|
|
1547
1637
|
handleNetworkOnline = () => {
|
|
1548
|
-
this.debug(
|
|
1638
|
+
this.debug(
|
|
1639
|
+
"Network online, attempting reconnection and flushing queued events"
|
|
1640
|
+
);
|
|
1549
1641
|
this.wsReconnectAttempts = 0;
|
|
1550
1642
|
if (this.wsReconnectTimer !== null) {
|
|
1551
1643
|
clearTimeout(this.wsReconnectTimer);
|
|
@@ -1568,7 +1660,10 @@ var Schematic = class {
|
|
|
1568
1660
|
return;
|
|
1569
1661
|
}
|
|
1570
1662
|
if (this.wsReconnectTimer !== null) {
|
|
1571
|
-
|
|
1663
|
+
this.debug(
|
|
1664
|
+
`Reconnection attempt already scheduled, ignoring duplicate request`
|
|
1665
|
+
);
|
|
1666
|
+
return;
|
|
1572
1667
|
}
|
|
1573
1668
|
const delay = this.calculateReconnectDelay();
|
|
1574
1669
|
this.debug(
|
|
@@ -1581,23 +1676,57 @@ var Schematic = class {
|
|
|
1581
1676
|
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1582
1677
|
);
|
|
1583
1678
|
try {
|
|
1584
|
-
this.conn
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1679
|
+
if (this.conn !== null) {
|
|
1680
|
+
this.debug(`Cleaning up existing connection before reconnection`);
|
|
1681
|
+
try {
|
|
1682
|
+
const existingSocket = await this.conn;
|
|
1683
|
+
if (this.currentWebSocket === existingSocket) {
|
|
1684
|
+
this.debug(`Existing websocket is current, will be replaced`);
|
|
1685
|
+
this.currentWebSocket = null;
|
|
1686
|
+
}
|
|
1687
|
+
if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
|
|
1688
|
+
existingSocket.close();
|
|
1689
|
+
}
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
this.debug(`Error cleaning up existing connection:`, error);
|
|
1692
|
+
}
|
|
1693
|
+
this.conn = null;
|
|
1694
|
+
this.currentWebSocket = null;
|
|
1695
|
+
this.isConnecting = false;
|
|
1696
|
+
}
|
|
1697
|
+
this.isConnecting = true;
|
|
1698
|
+
try {
|
|
1699
|
+
this.conn = this.wsConnect();
|
|
1700
|
+
const socket = await this.conn;
|
|
1701
|
+
this.isConnecting = false;
|
|
1702
|
+
this.debug(`Reconnection context check:`, {
|
|
1703
|
+
hasCompany: this.context.company !== void 0,
|
|
1704
|
+
hasUser: this.context.user !== void 0,
|
|
1705
|
+
context: this.context
|
|
1706
|
+
});
|
|
1707
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1708
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1709
|
+
await this.wsSendMessage(socket, this.context, true);
|
|
1710
|
+
} else {
|
|
1711
|
+
this.debug(
|
|
1712
|
+
`No context to re-send after reconnection - websocket ready for new context`
|
|
1713
|
+
);
|
|
1714
|
+
this.debug(
|
|
1715
|
+
`Setting up tracking for reconnected websocket (no context to send)`
|
|
1716
|
+
);
|
|
1717
|
+
this.currentWebSocket = socket;
|
|
1718
|
+
}
|
|
1719
|
+
this.flushEventQueue().catch((error) => {
|
|
1720
|
+
this.debug(
|
|
1721
|
+
"Error flushing event queue after websocket reconnection:",
|
|
1722
|
+
error
|
|
1723
|
+
);
|
|
1724
|
+
});
|
|
1725
|
+
this.debug(`Reconnection successful`);
|
|
1726
|
+
} catch (error) {
|
|
1727
|
+
this.isConnecting = false;
|
|
1728
|
+
throw error;
|
|
1596
1729
|
}
|
|
1597
|
-
this.flushEventQueue().catch((error) => {
|
|
1598
|
-
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1599
|
-
});
|
|
1600
|
-
this.debug(`Reconnection successful`);
|
|
1601
1730
|
} catch (error) {
|
|
1602
1731
|
this.debug(`Reconnection attempt failed:`, error);
|
|
1603
1732
|
}
|
|
@@ -1615,6 +1744,8 @@ var Schematic = class {
|
|
|
1615
1744
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1616
1745
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1617
1746
|
const webSocket = new WebSocket(wsUrl);
|
|
1747
|
+
const connectionId = Math.random().toString(36).substring(7);
|
|
1748
|
+
this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
|
|
1618
1749
|
let timeoutId = null;
|
|
1619
1750
|
let isResolved = false;
|
|
1620
1751
|
timeoutId = setTimeout(() => {
|
|
@@ -1633,7 +1764,7 @@ var Schematic = class {
|
|
|
1633
1764
|
}
|
|
1634
1765
|
this.wsReconnectAttempts = 0;
|
|
1635
1766
|
this.wsIntentionalDisconnect = false;
|
|
1636
|
-
this.debug(`WebSocket connection opened`);
|
|
1767
|
+
this.debug(`WebSocket connection ${connectionId} opened successfully`);
|
|
1637
1768
|
resolve(webSocket);
|
|
1638
1769
|
};
|
|
1639
1770
|
webSocket.onerror = (error) => {
|
|
@@ -1641,7 +1772,7 @@ var Schematic = class {
|
|
|
1641
1772
|
if (timeoutId !== null) {
|
|
1642
1773
|
clearTimeout(timeoutId);
|
|
1643
1774
|
}
|
|
1644
|
-
this.debug(`WebSocket connection error:`, error);
|
|
1775
|
+
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1645
1776
|
reject(error);
|
|
1646
1777
|
};
|
|
1647
1778
|
webSocket.onclose = () => {
|
|
@@ -1649,121 +1780,48 @@ var Schematic = class {
|
|
|
1649
1780
|
if (timeoutId !== null) {
|
|
1650
1781
|
clearTimeout(timeoutId);
|
|
1651
1782
|
}
|
|
1652
|
-
this.debug(`WebSocket connection closed`);
|
|
1783
|
+
this.debug(`WebSocket connection ${connectionId} closed`);
|
|
1653
1784
|
this.conn = null;
|
|
1785
|
+
if (this.currentWebSocket === webSocket) {
|
|
1786
|
+
this.currentWebSocket = null;
|
|
1787
|
+
this.isConnecting = false;
|
|
1788
|
+
}
|
|
1654
1789
|
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1655
1790
|
this.attemptReconnect();
|
|
1656
1791
|
}
|
|
1657
1792
|
};
|
|
1658
1793
|
});
|
|
1659
1794
|
};
|
|
1660
|
-
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1661
|
-
// because the server has lost all state and needs the initial context
|
|
1662
|
-
wsSendContextAfterReconnection = (socket, context) => {
|
|
1663
|
-
if (this.isOffline()) {
|
|
1664
|
-
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1665
|
-
this.setIsPending(false);
|
|
1666
|
-
return Promise.resolve();
|
|
1667
|
-
}
|
|
1668
|
-
return new Promise((resolve) => {
|
|
1669
|
-
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1670
|
-
this.context = context;
|
|
1671
|
-
const sendMessage = () => {
|
|
1672
|
-
let resolved = false;
|
|
1673
|
-
const messageHandler = (event) => {
|
|
1674
|
-
const message = JSON.parse(event.data);
|
|
1675
|
-
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1676
|
-
if (!(contextString(context) in this.checks)) {
|
|
1677
|
-
this.checks[contextString(context)] = {};
|
|
1678
|
-
}
|
|
1679
|
-
(message.flags ?? []).forEach((flag) => {
|
|
1680
|
-
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1681
|
-
const contextStr = contextString(context);
|
|
1682
|
-
if (this.checks[contextStr] === void 0) {
|
|
1683
|
-
this.checks[contextStr] = {};
|
|
1684
|
-
}
|
|
1685
|
-
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1686
|
-
});
|
|
1687
|
-
this.useWebSocket = true;
|
|
1688
|
-
socket.removeEventListener("message", messageHandler);
|
|
1689
|
-
if (!resolved) {
|
|
1690
|
-
resolved = true;
|
|
1691
|
-
resolve(this.setIsPending(false));
|
|
1692
|
-
}
|
|
1693
|
-
};
|
|
1694
|
-
socket.addEventListener("message", messageHandler);
|
|
1695
|
-
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1696
|
-
const messagePayload = {
|
|
1697
|
-
apiKey: this.apiKey,
|
|
1698
|
-
clientVersion,
|
|
1699
|
-
data: context
|
|
1700
|
-
};
|
|
1701
|
-
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1702
|
-
socket.send(JSON.stringify(messagePayload));
|
|
1703
|
-
};
|
|
1704
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
1705
|
-
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1706
|
-
sendMessage();
|
|
1707
|
-
} else {
|
|
1708
|
-
socket.addEventListener("open", () => {
|
|
1709
|
-
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1710
|
-
sendMessage();
|
|
1711
|
-
});
|
|
1712
|
-
}
|
|
1713
|
-
});
|
|
1714
|
-
};
|
|
1715
1795
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1716
1796
|
// and wait for the initial set of flag values to be returned
|
|
1717
|
-
wsSendMessage = (socket, context) => {
|
|
1797
|
+
wsSendMessage = (socket, context, forceContextSend = false) => {
|
|
1718
1798
|
if (this.isOffline()) {
|
|
1719
1799
|
this.debug("wsSendMessage: skipped (offline mode)");
|
|
1720
1800
|
this.setIsPending(false);
|
|
1721
1801
|
return Promise.resolve();
|
|
1722
1802
|
}
|
|
1723
1803
|
return new Promise((resolve, reject) => {
|
|
1724
|
-
if (contextString(context) == contextString(this.context)) {
|
|
1804
|
+
if (!forceContextSend && contextString(context) == contextString(this.context)) {
|
|
1725
1805
|
this.debug(`WebSocket context unchanged, skipping update`);
|
|
1726
1806
|
return resolve(this.setIsPending(false));
|
|
1727
1807
|
}
|
|
1728
|
-
this.debug(
|
|
1808
|
+
this.debug(
|
|
1809
|
+
forceContextSend ? `WebSocket force sending context (reconnection):` : `WebSocket context updated:`,
|
|
1810
|
+
context
|
|
1811
|
+
);
|
|
1729
1812
|
this.context = context;
|
|
1730
1813
|
const sendMessage = () => {
|
|
1731
1814
|
let resolved = false;
|
|
1815
|
+
const persistentMessageHandler = this.createPersistentMessageHandler(context);
|
|
1732
1816
|
const messageHandler = (event) => {
|
|
1733
|
-
|
|
1734
|
-
this.debug(`WebSocket message received:`, message);
|
|
1735
|
-
if (!(contextString(context) in this.checks)) {
|
|
1736
|
-
this.checks[contextString(context)] = {};
|
|
1737
|
-
}
|
|
1738
|
-
(message.flags ?? []).forEach((flag) => {
|
|
1739
|
-
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1740
|
-
const contextStr = contextString(context);
|
|
1741
|
-
if (this.checks[contextStr] === void 0) {
|
|
1742
|
-
this.checks[contextStr] = {};
|
|
1743
|
-
}
|
|
1744
|
-
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1745
|
-
this.debug(`WebSocket flag update:`, {
|
|
1746
|
-
flag: flagCheck.flag,
|
|
1747
|
-
value: flagCheck.value,
|
|
1748
|
-
flagCheck
|
|
1749
|
-
});
|
|
1750
|
-
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1751
|
-
this.updateFeatureUsageEventMap(flagCheck);
|
|
1752
|
-
}
|
|
1753
|
-
if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
|
|
1754
|
-
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1755
|
-
}
|
|
1756
|
-
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1757
|
-
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1758
|
-
});
|
|
1759
|
-
this.flushContextDependentEventQueue();
|
|
1760
|
-
this.setIsPending(false);
|
|
1817
|
+
persistentMessageHandler(event);
|
|
1761
1818
|
if (!resolved) {
|
|
1762
1819
|
resolved = true;
|
|
1763
1820
|
resolve();
|
|
1764
1821
|
}
|
|
1765
1822
|
};
|
|
1766
1823
|
socket.addEventListener("message", messageHandler);
|
|
1824
|
+
this.currentWebSocket = socket;
|
|
1767
1825
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1768
1826
|
const messagePayload = {
|
|
1769
1827
|
apiKey: this.apiKey,
|
|
@@ -1871,7 +1929,17 @@ var Schematic = class {
|
|
|
1871
1929
|
{ value }
|
|
1872
1930
|
);
|
|
1873
1931
|
}
|
|
1874
|
-
listeners.forEach((listener) =>
|
|
1932
|
+
listeners.forEach((listener, index) => {
|
|
1933
|
+
this.debug(`Calling listener ${index} for flag ${flagKey}`, {
|
|
1934
|
+
flagKey,
|
|
1935
|
+
value
|
|
1936
|
+
});
|
|
1937
|
+
notifyFlagValueListener(listener, value);
|
|
1938
|
+
this.debug(`Listener ${index} for flag ${flagKey} completed`, {
|
|
1939
|
+
flagKey,
|
|
1940
|
+
value
|
|
1941
|
+
});
|
|
1942
|
+
});
|
|
1875
1943
|
};
|
|
1876
1944
|
};
|
|
1877
1945
|
var notifyPendingListener = (listener, value) => {
|
|
@@ -1900,7 +1968,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1900
1968
|
import React, { createContext, useEffect, useMemo, useRef } from "react";
|
|
1901
1969
|
|
|
1902
1970
|
// src/version.ts
|
|
1903
|
-
var version2 = "1.2.
|
|
1971
|
+
var version2 = "1.2.10";
|
|
1904
1972
|
|
|
1905
1973
|
// src/context/schematic.tsx
|
|
1906
1974
|
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.10",
|
|
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.10"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@eslint/js": "^9.39.1",
|