@schematichq/schematic-js 1.2.9 → 1.2.11
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.browser.js +2 -2
- package/dist/schematic.cjs.js +262 -117
- package/dist/schematic.d.ts +36 -2
- package/dist/schematic.esm.js +262 -117
- package/package.json +2 -2
package/dist/schematic.esm.js
CHANGED
|
@@ -781,7 +781,7 @@ function contextString(context) {
|
|
|
781
781
|
}
|
|
782
782
|
|
|
783
783
|
// src/version.ts
|
|
784
|
-
var version = "1.2.
|
|
784
|
+
var version = "1.2.11";
|
|
785
785
|
|
|
786
786
|
// src/index.ts
|
|
787
787
|
var anonymousIdKey = "schematicId";
|
|
@@ -813,6 +813,8 @@ var Schematic = class {
|
|
|
813
813
|
wsReconnectAttempts = 0;
|
|
814
814
|
wsReconnectTimer = null;
|
|
815
815
|
wsIntentionalDisconnect = false;
|
|
816
|
+
currentWebSocket = null;
|
|
817
|
+
isConnecting = false;
|
|
816
818
|
maxEventQueueSize = 100;
|
|
817
819
|
// Prevent memory issues with very long network outages
|
|
818
820
|
maxEventRetries = 5;
|
|
@@ -1090,6 +1092,49 @@ var Schematic = class {
|
|
|
1090
1092
|
console.log(`[Schematic] ${message}`, ...args);
|
|
1091
1093
|
}
|
|
1092
1094
|
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Create a persistent message handler for websocket flag updates
|
|
1097
|
+
*/
|
|
1098
|
+
createPersistentMessageHandler(context) {
|
|
1099
|
+
return (event) => {
|
|
1100
|
+
const message = JSON.parse(event.data);
|
|
1101
|
+
this.debug(`WebSocket persistent message received:`, message);
|
|
1102
|
+
if (!(contextString(context) in this.checks)) {
|
|
1103
|
+
this.checks[contextString(context)] = {};
|
|
1104
|
+
}
|
|
1105
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1106
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1107
|
+
const contextStr = contextString(context);
|
|
1108
|
+
if (this.checks[contextStr] === void 0) {
|
|
1109
|
+
this.checks[contextStr] = {};
|
|
1110
|
+
}
|
|
1111
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1112
|
+
this.debug(`WebSocket flag update:`, {
|
|
1113
|
+
flag: flagCheck.flag,
|
|
1114
|
+
value: flagCheck.value,
|
|
1115
|
+
flagCheck
|
|
1116
|
+
});
|
|
1117
|
+
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1118
|
+
this.updateFeatureUsageEventMap(flagCheck);
|
|
1119
|
+
}
|
|
1120
|
+
if ((this.flagCheckListeners[flag.flag]?.size ?? 0) > 0 || (this.flagValueListeners[flag.flag]?.size ?? 0) > 0) {
|
|
1121
|
+
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1122
|
+
}
|
|
1123
|
+
this.debug(`About to notify listeners for flag ${flag.flag}`, {
|
|
1124
|
+
flag: flag.flag,
|
|
1125
|
+
value: flagCheck.value
|
|
1126
|
+
});
|
|
1127
|
+
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1128
|
+
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1129
|
+
this.debug(`Finished notifying listeners for flag ${flag.flag}`, {
|
|
1130
|
+
flag: flag.flag,
|
|
1131
|
+
value: flagCheck.value
|
|
1132
|
+
});
|
|
1133
|
+
});
|
|
1134
|
+
this.flushContextDependentEventQueue();
|
|
1135
|
+
this.setIsPending(false);
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1093
1138
|
/**
|
|
1094
1139
|
* Helper function to check if client is in offline mode
|
|
1095
1140
|
*/
|
|
@@ -1241,6 +1286,19 @@ var Schematic = class {
|
|
|
1241
1286
|
try {
|
|
1242
1287
|
this.setIsPending(true);
|
|
1243
1288
|
if (!this.conn) {
|
|
1289
|
+
if (this.isConnecting) {
|
|
1290
|
+
this.debug(
|
|
1291
|
+
`Connection already in progress, waiting for it to complete`
|
|
1292
|
+
);
|
|
1293
|
+
while (this.isConnecting && this.conn === null) {
|
|
1294
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1295
|
+
}
|
|
1296
|
+
if (this.conn !== null) {
|
|
1297
|
+
const socket2 = await this.conn;
|
|
1298
|
+
await this.wsSendMessage(socket2, context);
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1244
1302
|
if (this.wsReconnectTimer !== null) {
|
|
1245
1303
|
this.debug(
|
|
1246
1304
|
`Cancelling scheduled reconnection, connecting immediately`
|
|
@@ -1248,7 +1306,17 @@ var Schematic = class {
|
|
|
1248
1306
|
clearTimeout(this.wsReconnectTimer);
|
|
1249
1307
|
this.wsReconnectTimer = null;
|
|
1250
1308
|
}
|
|
1251
|
-
this.
|
|
1309
|
+
this.isConnecting = true;
|
|
1310
|
+
try {
|
|
1311
|
+
this.conn = this.wsConnect();
|
|
1312
|
+
const socket2 = await this.conn;
|
|
1313
|
+
this.isConnecting = false;
|
|
1314
|
+
await this.wsSendMessage(socket2, context);
|
|
1315
|
+
return;
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
this.isConnecting = false;
|
|
1318
|
+
throw error;
|
|
1319
|
+
}
|
|
1252
1320
|
}
|
|
1253
1321
|
const socket = await this.conn;
|
|
1254
1322
|
await this.wsSendMessage(socket, context);
|
|
@@ -1413,10 +1481,14 @@ var Schematic = class {
|
|
|
1413
1481
|
}
|
|
1414
1482
|
}
|
|
1415
1483
|
if (readyEvents.length === 0) {
|
|
1416
|
-
this.debug(
|
|
1484
|
+
this.debug(
|
|
1485
|
+
`No events ready for retry yet (${notReadyEvents.length} still in backoff)`
|
|
1486
|
+
);
|
|
1417
1487
|
return;
|
|
1418
1488
|
}
|
|
1419
|
-
this.debug(
|
|
1489
|
+
this.debug(
|
|
1490
|
+
`Flushing event queue: ${readyEvents.length} ready, ${notReadyEvents.length} waiting`
|
|
1491
|
+
);
|
|
1420
1492
|
this.eventQueue = notReadyEvents;
|
|
1421
1493
|
for (const event of readyEvents) {
|
|
1422
1494
|
try {
|
|
@@ -1481,7 +1553,10 @@ var Schematic = class {
|
|
|
1481
1553
|
} catch (error) {
|
|
1482
1554
|
const retryCount = (event.retry_count ?? 0) + 1;
|
|
1483
1555
|
if (retryCount <= this.maxEventRetries) {
|
|
1484
|
-
this.debug(
|
|
1556
|
+
this.debug(
|
|
1557
|
+
`Event failed to send (attempt ${retryCount}/${this.maxEventRetries}), queueing for retry:`,
|
|
1558
|
+
error
|
|
1559
|
+
);
|
|
1485
1560
|
const baseDelay = this.eventRetryInitialDelay * Math.pow(2, retryCount - 1);
|
|
1486
1561
|
const jitterDelay = Math.min(baseDelay, this.eventRetryMaxDelay);
|
|
1487
1562
|
const nextRetryAt = Date.now() + jitterDelay;
|
|
@@ -1492,15 +1567,22 @@ var Schematic = class {
|
|
|
1492
1567
|
};
|
|
1493
1568
|
if (this.eventQueue.length < this.maxEventQueueSize) {
|
|
1494
1569
|
this.eventQueue.push(retryEvent);
|
|
1495
|
-
this.debug(
|
|
1570
|
+
this.debug(
|
|
1571
|
+
`Event queued for retry in ${jitterDelay}ms (${this.eventQueue.length}/${this.maxEventQueueSize})`
|
|
1572
|
+
);
|
|
1496
1573
|
} else {
|
|
1497
|
-
this.debug(
|
|
1574
|
+
this.debug(
|
|
1575
|
+
`Event queue full (${this.maxEventQueueSize}), dropping oldest event`
|
|
1576
|
+
);
|
|
1498
1577
|
this.eventQueue.shift();
|
|
1499
1578
|
this.eventQueue.push(retryEvent);
|
|
1500
1579
|
}
|
|
1501
1580
|
this.startRetryTimer();
|
|
1502
1581
|
} else {
|
|
1503
|
-
this.debug(
|
|
1582
|
+
this.debug(
|
|
1583
|
+
`Event failed permanently after ${this.maxEventRetries} attempts, dropping:`,
|
|
1584
|
+
error
|
|
1585
|
+
);
|
|
1504
1586
|
}
|
|
1505
1587
|
}
|
|
1506
1588
|
return Promise.resolve();
|
|
@@ -1530,12 +1612,95 @@ var Schematic = class {
|
|
|
1530
1612
|
if (this.conn) {
|
|
1531
1613
|
try {
|
|
1532
1614
|
const socket = await this.conn;
|
|
1615
|
+
if (this.currentWebSocket === socket) {
|
|
1616
|
+
this.debug(`Cleaning up current websocket tracking`);
|
|
1617
|
+
this.currentWebSocket = null;
|
|
1618
|
+
}
|
|
1533
1619
|
socket.close();
|
|
1534
1620
|
} catch (error) {
|
|
1535
1621
|
console.error("Error during cleanup:", error);
|
|
1536
1622
|
} finally {
|
|
1537
1623
|
this.conn = null;
|
|
1624
|
+
this.currentWebSocket = null;
|
|
1625
|
+
this.isConnecting = false;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
/**
|
|
1630
|
+
* Force an immediate WebSocket reconnection.
|
|
1631
|
+
* This is useful when the application returns from a background state (e.g., mobile app
|
|
1632
|
+
* coming back to foreground) and wants to immediately re-establish the connection
|
|
1633
|
+
* rather than waiting for the exponential backoff timer.
|
|
1634
|
+
*
|
|
1635
|
+
* This method will:
|
|
1636
|
+
* - Cancel any pending reconnection timer
|
|
1637
|
+
* - Reset the reconnection attempt counter
|
|
1638
|
+
* - Immediately attempt to reconnect
|
|
1639
|
+
* - Re-send the current context to get fresh flag values
|
|
1640
|
+
*
|
|
1641
|
+
* If a connection is already active and healthy, this will close it and create a new one.
|
|
1642
|
+
* If you just want to ensure a connection exists, check the connection state first.
|
|
1643
|
+
*
|
|
1644
|
+
* @example
|
|
1645
|
+
* ```typescript
|
|
1646
|
+
* // React Native example: reconnect when app comes to foreground
|
|
1647
|
+
* useEffect(() => {
|
|
1648
|
+
* const subscription = AppState.addEventListener("change", (state) => {
|
|
1649
|
+
* if (state === "active") {
|
|
1650
|
+
* client.forceReconnect();
|
|
1651
|
+
* }
|
|
1652
|
+
* });
|
|
1653
|
+
* return () => subscription.remove();
|
|
1654
|
+
* }, [client]);
|
|
1655
|
+
* ```
|
|
1656
|
+
*/
|
|
1657
|
+
forceReconnect = async () => {
|
|
1658
|
+
if (this.isOffline()) {
|
|
1659
|
+
this.debug("forceReconnect: skipped (offline mode)");
|
|
1660
|
+
return Promise.resolve();
|
|
1661
|
+
}
|
|
1662
|
+
this.debug("forceReconnect: forcing immediate reconnection");
|
|
1663
|
+
this.wsIntentionalDisconnect = false;
|
|
1664
|
+
if (this.wsReconnectTimer !== null) {
|
|
1665
|
+
this.debug("forceReconnect: cancelling pending reconnection timer");
|
|
1666
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1667
|
+
this.wsReconnectTimer = null;
|
|
1668
|
+
}
|
|
1669
|
+
this.wsReconnectAttempts = 0;
|
|
1670
|
+
if (this.conn !== null) {
|
|
1671
|
+
this.debug("forceReconnect: closing existing connection");
|
|
1672
|
+
try {
|
|
1673
|
+
const existingSocket = await this.conn;
|
|
1674
|
+
if (this.currentWebSocket === existingSocket) {
|
|
1675
|
+
this.currentWebSocket = null;
|
|
1676
|
+
}
|
|
1677
|
+
if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
|
|
1678
|
+
existingSocket.close();
|
|
1679
|
+
}
|
|
1680
|
+
} catch (error) {
|
|
1681
|
+
this.debug("forceReconnect: error closing existing connection:", error);
|
|
1682
|
+
}
|
|
1683
|
+
this.conn = null;
|
|
1684
|
+
this.isConnecting = false;
|
|
1685
|
+
}
|
|
1686
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1687
|
+
this.debug("forceReconnect: reconnecting with existing context");
|
|
1688
|
+
try {
|
|
1689
|
+
this.isConnecting = true;
|
|
1690
|
+
this.conn = this.wsConnect();
|
|
1691
|
+
const socket = await this.conn;
|
|
1692
|
+
this.isConnecting = false;
|
|
1693
|
+
await this.wsSendMessage(socket, this.context, true);
|
|
1694
|
+
this.debug("forceReconnect: reconnection successful");
|
|
1695
|
+
} catch (error) {
|
|
1696
|
+
this.isConnecting = false;
|
|
1697
|
+
this.debug("forceReconnect: reconnection failed:", error);
|
|
1698
|
+
this.attemptReconnect();
|
|
1538
1699
|
}
|
|
1700
|
+
} else {
|
|
1701
|
+
this.debug(
|
|
1702
|
+
"forceReconnect: no context available, websocket will connect when context is set"
|
|
1703
|
+
);
|
|
1539
1704
|
}
|
|
1540
1705
|
};
|
|
1541
1706
|
/**
|
|
@@ -1559,7 +1724,9 @@ var Schematic = class {
|
|
|
1559
1724
|
if (this.conn !== null) {
|
|
1560
1725
|
try {
|
|
1561
1726
|
const socket = await this.conn;
|
|
1562
|
-
socket.
|
|
1727
|
+
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
|
|
1728
|
+
socket.close();
|
|
1729
|
+
}
|
|
1563
1730
|
} catch (error) {
|
|
1564
1731
|
this.debug("Error closing connection on offline:", error);
|
|
1565
1732
|
}
|
|
@@ -1574,7 +1741,9 @@ var Schematic = class {
|
|
|
1574
1741
|
* Handle browser coming back online
|
|
1575
1742
|
*/
|
|
1576
1743
|
handleNetworkOnline = () => {
|
|
1577
|
-
this.debug(
|
|
1744
|
+
this.debug(
|
|
1745
|
+
"Network online, attempting reconnection and flushing queued events"
|
|
1746
|
+
);
|
|
1578
1747
|
this.wsReconnectAttempts = 0;
|
|
1579
1748
|
if (this.wsReconnectTimer !== null) {
|
|
1580
1749
|
clearTimeout(this.wsReconnectTimer);
|
|
@@ -1597,7 +1766,10 @@ var Schematic = class {
|
|
|
1597
1766
|
return;
|
|
1598
1767
|
}
|
|
1599
1768
|
if (this.wsReconnectTimer !== null) {
|
|
1600
|
-
|
|
1769
|
+
this.debug(
|
|
1770
|
+
`Reconnection attempt already scheduled, ignoring duplicate request`
|
|
1771
|
+
);
|
|
1772
|
+
return;
|
|
1601
1773
|
}
|
|
1602
1774
|
const delay = this.calculateReconnectDelay();
|
|
1603
1775
|
this.debug(
|
|
@@ -1610,23 +1782,57 @@ var Schematic = class {
|
|
|
1610
1782
|
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1611
1783
|
);
|
|
1612
1784
|
try {
|
|
1613
|
-
this.conn
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1785
|
+
if (this.conn !== null) {
|
|
1786
|
+
this.debug(`Cleaning up existing connection before reconnection`);
|
|
1787
|
+
try {
|
|
1788
|
+
const existingSocket = await this.conn;
|
|
1789
|
+
if (this.currentWebSocket === existingSocket) {
|
|
1790
|
+
this.debug(`Existing websocket is current, will be replaced`);
|
|
1791
|
+
this.currentWebSocket = null;
|
|
1792
|
+
}
|
|
1793
|
+
if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
|
|
1794
|
+
existingSocket.close();
|
|
1795
|
+
}
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
this.debug(`Error cleaning up existing connection:`, error);
|
|
1798
|
+
}
|
|
1799
|
+
this.conn = null;
|
|
1800
|
+
this.currentWebSocket = null;
|
|
1801
|
+
this.isConnecting = false;
|
|
1802
|
+
}
|
|
1803
|
+
this.isConnecting = true;
|
|
1804
|
+
try {
|
|
1805
|
+
this.conn = this.wsConnect();
|
|
1806
|
+
const socket = await this.conn;
|
|
1807
|
+
this.isConnecting = false;
|
|
1808
|
+
this.debug(`Reconnection context check:`, {
|
|
1809
|
+
hasCompany: this.context.company !== void 0,
|
|
1810
|
+
hasUser: this.context.user !== void 0,
|
|
1811
|
+
context: this.context
|
|
1812
|
+
});
|
|
1813
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1814
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1815
|
+
await this.wsSendMessage(socket, this.context, true);
|
|
1816
|
+
} else {
|
|
1817
|
+
this.debug(
|
|
1818
|
+
`No context to re-send after reconnection - websocket ready for new context`
|
|
1819
|
+
);
|
|
1820
|
+
this.debug(
|
|
1821
|
+
`Setting up tracking for reconnected websocket (no context to send)`
|
|
1822
|
+
);
|
|
1823
|
+
this.currentWebSocket = socket;
|
|
1824
|
+
}
|
|
1825
|
+
this.flushEventQueue().catch((error) => {
|
|
1826
|
+
this.debug(
|
|
1827
|
+
"Error flushing event queue after websocket reconnection:",
|
|
1828
|
+
error
|
|
1829
|
+
);
|
|
1830
|
+
});
|
|
1831
|
+
this.debug(`Reconnection successful`);
|
|
1832
|
+
} catch (error) {
|
|
1833
|
+
this.isConnecting = false;
|
|
1834
|
+
throw error;
|
|
1625
1835
|
}
|
|
1626
|
-
this.flushEventQueue().catch((error) => {
|
|
1627
|
-
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1628
|
-
});
|
|
1629
|
-
this.debug(`Reconnection successful`);
|
|
1630
1836
|
} catch (error) {
|
|
1631
1837
|
this.debug(`Reconnection attempt failed:`, error);
|
|
1632
1838
|
}
|
|
@@ -1644,6 +1850,8 @@ var Schematic = class {
|
|
|
1644
1850
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1645
1851
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1646
1852
|
const webSocket = new WebSocket(wsUrl);
|
|
1853
|
+
const connectionId = Math.random().toString(36).substring(7);
|
|
1854
|
+
this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
|
|
1647
1855
|
let timeoutId = null;
|
|
1648
1856
|
let isResolved = false;
|
|
1649
1857
|
timeoutId = setTimeout(() => {
|
|
@@ -1662,7 +1870,7 @@ var Schematic = class {
|
|
|
1662
1870
|
}
|
|
1663
1871
|
this.wsReconnectAttempts = 0;
|
|
1664
1872
|
this.wsIntentionalDisconnect = false;
|
|
1665
|
-
this.debug(`WebSocket connection opened`);
|
|
1873
|
+
this.debug(`WebSocket connection ${connectionId} opened successfully`);
|
|
1666
1874
|
resolve(webSocket);
|
|
1667
1875
|
};
|
|
1668
1876
|
webSocket.onerror = (error) => {
|
|
@@ -1670,7 +1878,7 @@ var Schematic = class {
|
|
|
1670
1878
|
if (timeoutId !== null) {
|
|
1671
1879
|
clearTimeout(timeoutId);
|
|
1672
1880
|
}
|
|
1673
|
-
this.debug(`WebSocket connection error:`, error);
|
|
1881
|
+
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1674
1882
|
reject(error);
|
|
1675
1883
|
};
|
|
1676
1884
|
webSocket.onclose = () => {
|
|
@@ -1678,121 +1886,48 @@ var Schematic = class {
|
|
|
1678
1886
|
if (timeoutId !== null) {
|
|
1679
1887
|
clearTimeout(timeoutId);
|
|
1680
1888
|
}
|
|
1681
|
-
this.debug(`WebSocket connection closed`);
|
|
1889
|
+
this.debug(`WebSocket connection ${connectionId} closed`);
|
|
1682
1890
|
this.conn = null;
|
|
1891
|
+
if (this.currentWebSocket === webSocket) {
|
|
1892
|
+
this.currentWebSocket = null;
|
|
1893
|
+
this.isConnecting = false;
|
|
1894
|
+
}
|
|
1683
1895
|
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1684
1896
|
this.attemptReconnect();
|
|
1685
1897
|
}
|
|
1686
1898
|
};
|
|
1687
1899
|
});
|
|
1688
1900
|
};
|
|
1689
|
-
// Send a message on the websocket after reconnection, forcing the send even if context appears unchanged
|
|
1690
|
-
// because the server has lost all state and needs the initial context
|
|
1691
|
-
wsSendContextAfterReconnection = (socket, context) => {
|
|
1692
|
-
if (this.isOffline()) {
|
|
1693
|
-
this.debug("wsSendContextAfterReconnection: skipped (offline mode)");
|
|
1694
|
-
this.setIsPending(false);
|
|
1695
|
-
return Promise.resolve();
|
|
1696
|
-
}
|
|
1697
|
-
return new Promise((resolve) => {
|
|
1698
|
-
this.debug(`WebSocket force sending context after reconnection:`, context);
|
|
1699
|
-
this.context = context;
|
|
1700
|
-
const sendMessage = () => {
|
|
1701
|
-
let resolved = false;
|
|
1702
|
-
const messageHandler = (event) => {
|
|
1703
|
-
const message = JSON.parse(event.data);
|
|
1704
|
-
this.debug(`WebSocket message received after reconnection:`, message);
|
|
1705
|
-
if (!(contextString(context) in this.checks)) {
|
|
1706
|
-
this.checks[contextString(context)] = {};
|
|
1707
|
-
}
|
|
1708
|
-
(message.flags ?? []).forEach((flag) => {
|
|
1709
|
-
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1710
|
-
const contextStr = contextString(context);
|
|
1711
|
-
if (this.checks[contextStr] === void 0) {
|
|
1712
|
-
this.checks[contextStr] = {};
|
|
1713
|
-
}
|
|
1714
|
-
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1715
|
-
});
|
|
1716
|
-
this.useWebSocket = true;
|
|
1717
|
-
socket.removeEventListener("message", messageHandler);
|
|
1718
|
-
if (!resolved) {
|
|
1719
|
-
resolved = true;
|
|
1720
|
-
resolve(this.setIsPending(false));
|
|
1721
|
-
}
|
|
1722
|
-
};
|
|
1723
|
-
socket.addEventListener("message", messageHandler);
|
|
1724
|
-
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1725
|
-
const messagePayload = {
|
|
1726
|
-
apiKey: this.apiKey,
|
|
1727
|
-
clientVersion,
|
|
1728
|
-
data: context
|
|
1729
|
-
};
|
|
1730
|
-
this.debug(`WebSocket sending forced message after reconnection:`, messagePayload);
|
|
1731
|
-
socket.send(JSON.stringify(messagePayload));
|
|
1732
|
-
};
|
|
1733
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
1734
|
-
this.debug(`WebSocket already open, sending forced message after reconnection`);
|
|
1735
|
-
sendMessage();
|
|
1736
|
-
} else {
|
|
1737
|
-
socket.addEventListener("open", () => {
|
|
1738
|
-
this.debug(`WebSocket opened, sending forced message after reconnection`);
|
|
1739
|
-
sendMessage();
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
});
|
|
1743
|
-
};
|
|
1744
1901
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1745
1902
|
// and wait for the initial set of flag values to be returned
|
|
1746
|
-
wsSendMessage = (socket, context) => {
|
|
1903
|
+
wsSendMessage = (socket, context, forceContextSend = false) => {
|
|
1747
1904
|
if (this.isOffline()) {
|
|
1748
1905
|
this.debug("wsSendMessage: skipped (offline mode)");
|
|
1749
1906
|
this.setIsPending(false);
|
|
1750
1907
|
return Promise.resolve();
|
|
1751
1908
|
}
|
|
1752
1909
|
return new Promise((resolve, reject) => {
|
|
1753
|
-
if (contextString(context) == contextString(this.context)) {
|
|
1910
|
+
if (!forceContextSend && contextString(context) == contextString(this.context)) {
|
|
1754
1911
|
this.debug(`WebSocket context unchanged, skipping update`);
|
|
1755
1912
|
return resolve(this.setIsPending(false));
|
|
1756
1913
|
}
|
|
1757
|
-
this.debug(
|
|
1914
|
+
this.debug(
|
|
1915
|
+
forceContextSend ? `WebSocket force sending context (reconnection):` : `WebSocket context updated:`,
|
|
1916
|
+
context
|
|
1917
|
+
);
|
|
1758
1918
|
this.context = context;
|
|
1759
1919
|
const sendMessage = () => {
|
|
1760
1920
|
let resolved = false;
|
|
1921
|
+
const persistentMessageHandler = this.createPersistentMessageHandler(context);
|
|
1761
1922
|
const messageHandler = (event) => {
|
|
1762
|
-
|
|
1763
|
-
this.debug(`WebSocket message received:`, message);
|
|
1764
|
-
if (!(contextString(context) in this.checks)) {
|
|
1765
|
-
this.checks[contextString(context)] = {};
|
|
1766
|
-
}
|
|
1767
|
-
(message.flags ?? []).forEach((flag) => {
|
|
1768
|
-
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1769
|
-
const contextStr = contextString(context);
|
|
1770
|
-
if (this.checks[contextStr] === void 0) {
|
|
1771
|
-
this.checks[contextStr] = {};
|
|
1772
|
-
}
|
|
1773
|
-
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1774
|
-
this.debug(`WebSocket flag update:`, {
|
|
1775
|
-
flag: flagCheck.flag,
|
|
1776
|
-
value: flagCheck.value,
|
|
1777
|
-
flagCheck
|
|
1778
|
-
});
|
|
1779
|
-
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1780
|
-
this.updateFeatureUsageEventMap(flagCheck);
|
|
1781
|
-
}
|
|
1782
|
-
if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
|
|
1783
|
-
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1784
|
-
}
|
|
1785
|
-
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1786
|
-
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1787
|
-
});
|
|
1788
|
-
this.flushContextDependentEventQueue();
|
|
1789
|
-
this.setIsPending(false);
|
|
1923
|
+
persistentMessageHandler(event);
|
|
1790
1924
|
if (!resolved) {
|
|
1791
1925
|
resolved = true;
|
|
1792
1926
|
resolve();
|
|
1793
1927
|
}
|
|
1794
1928
|
};
|
|
1795
1929
|
socket.addEventListener("message", messageHandler);
|
|
1930
|
+
this.currentWebSocket = socket;
|
|
1796
1931
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1797
1932
|
const messagePayload = {
|
|
1798
1933
|
apiKey: this.apiKey,
|
|
@@ -1900,7 +2035,17 @@ var Schematic = class {
|
|
|
1900
2035
|
{ value }
|
|
1901
2036
|
);
|
|
1902
2037
|
}
|
|
1903
|
-
listeners.forEach((listener) =>
|
|
2038
|
+
listeners.forEach((listener, index) => {
|
|
2039
|
+
this.debug(`Calling listener ${index} for flag ${flagKey}`, {
|
|
2040
|
+
flagKey,
|
|
2041
|
+
value
|
|
2042
|
+
});
|
|
2043
|
+
notifyFlagValueListener(listener, value);
|
|
2044
|
+
this.debug(`Listener ${index} for flag ${flagKey} completed`, {
|
|
2045
|
+
flagKey,
|
|
2046
|
+
value
|
|
2047
|
+
});
|
|
2048
|
+
});
|
|
1904
2049
|
};
|
|
1905
2050
|
};
|
|
1906
2051
|
var notifyPendingListener = (listener, value) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schematichq/schematic-js",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.11",
|
|
4
4
|
"main": "dist/schematic.cjs.js",
|
|
5
5
|
"module": "dist/schematic.esm.js",
|
|
6
6
|
"types": "dist/schematic.d.ts",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@eslint/js": "^9.39.1",
|
|
41
41
|
"@microsoft/api-extractor": "^7.55.0",
|
|
42
|
-
"@openapitools/openapi-generator-cli": "^2.25.
|
|
42
|
+
"@openapitools/openapi-generator-cli": "^2.25.2",
|
|
43
43
|
"@vitest/browser": "^4.0.10",
|
|
44
44
|
"esbuild": "^0.27.0",
|
|
45
45
|
"eslint": "^9.39.1",
|