@schematichq/schematic-react 1.2.9 → 1.2.12
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/README.md +37 -0
- package/dist/schematic-react.cjs.js +311 -119
- package/dist/schematic-react.d.ts +1 -1
- package/dist/schematic-react.esm.js +311 -119
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -136,6 +136,43 @@ const MyComponent = () => {
|
|
|
136
136
|
|
|
137
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
138
|
|
|
139
|
+
## React Native
|
|
140
|
+
|
|
141
|
+
### Handling app background/foreground
|
|
142
|
+
|
|
143
|
+
When a React Native app is backgrounded for an extended period, the WebSocket connection may be closed by the OS. When the app returns to the foreground, the connection will automatically reconnect, but this happens on an exponential backoff timer which may cause a delay before fresh flag values are available.
|
|
144
|
+
|
|
145
|
+
For cases where you need immediate flag updates when returning to the foreground (e.g., after an in-app purchase), you can use one of these methods to re-establish the connection:
|
|
146
|
+
|
|
147
|
+
- `forceReconnect()`: Always closes and re-establishes the WebSocket connection, guaranteeing fresh values
|
|
148
|
+
- `reconnectIfNeeded()`: Only reconnects if the current connection is unhealthy (more efficient for frequent foreground events)
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { useSchematic } from "@schematichq/schematic-react";
|
|
152
|
+
import { useEffect } from "react";
|
|
153
|
+
import { AppState } from "react-native";
|
|
154
|
+
|
|
155
|
+
const SchematicAppStateHandler = () => {
|
|
156
|
+
const { client } = useSchematic();
|
|
157
|
+
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
const subscription = AppState.addEventListener("change", (state) => {
|
|
160
|
+
if (state === "active") {
|
|
161
|
+
// Use forceReconnect() for guaranteed fresh values
|
|
162
|
+
client.forceReconnect();
|
|
163
|
+
// Or use reconnectIfNeeded() to skip if connection is healthy
|
|
164
|
+
// client.reconnectIfNeeded();
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
return () => subscription.remove();
|
|
168
|
+
}, [client]);
|
|
169
|
+
|
|
170
|
+
return null;
|
|
171
|
+
};
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Render this inside your `SchematicProvider`.
|
|
175
|
+
|
|
139
176
|
## Troubleshooting
|
|
140
177
|
|
|
141
178
|
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.12";
|
|
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();
|
|
@@ -1528,6 +1610,130 @@ var Schematic = class {
|
|
|
1528
1610
|
/**
|
|
1529
1611
|
* Websocket management
|
|
1530
1612
|
*/
|
|
1613
|
+
/**
|
|
1614
|
+
* Force an immediate WebSocket reconnection.
|
|
1615
|
+
* This is useful when the application returns from a background state (e.g., mobile app
|
|
1616
|
+
* coming back to foreground) and wants to immediately re-establish the connection
|
|
1617
|
+
* rather than waiting for the exponential backoff timer.
|
|
1618
|
+
*
|
|
1619
|
+
* This method will:
|
|
1620
|
+
* - Cancel any pending reconnection timer
|
|
1621
|
+
* - Reset the reconnection attempt counter
|
|
1622
|
+
* - Close any existing connection
|
|
1623
|
+
* - Immediately attempt to reconnect
|
|
1624
|
+
* - Re-send the current context to get fresh flag values
|
|
1625
|
+
*
|
|
1626
|
+
* Use this when you need guaranteed fresh values (e.g., after an in-app purchase).
|
|
1627
|
+
*
|
|
1628
|
+
* @example
|
|
1629
|
+
* ```typescript
|
|
1630
|
+
* // React Native example: reconnect when app comes to foreground
|
|
1631
|
+
* useEffect(() => {
|
|
1632
|
+
* const subscription = AppState.addEventListener("change", (state) => {
|
|
1633
|
+
* if (state === "active") {
|
|
1634
|
+
* client.forceReconnect();
|
|
1635
|
+
* }
|
|
1636
|
+
* });
|
|
1637
|
+
* return () => subscription.remove();
|
|
1638
|
+
* }, [client]);
|
|
1639
|
+
* ```
|
|
1640
|
+
*/
|
|
1641
|
+
forceReconnect = async () => {
|
|
1642
|
+
return this.reconnect({ force: true });
|
|
1643
|
+
};
|
|
1644
|
+
/**
|
|
1645
|
+
* Reconnect the WebSocket connection only if the current connection is unhealthy.
|
|
1646
|
+
* This is useful when the application returns from a background state and wants to
|
|
1647
|
+
* ensure a healthy connection exists, but doesn't need to force a reconnection if
|
|
1648
|
+
* the connection is still active.
|
|
1649
|
+
*
|
|
1650
|
+
* This method will:
|
|
1651
|
+
* - Check if an existing connection is healthy (readyState === OPEN)
|
|
1652
|
+
* - If healthy, return immediately without reconnecting
|
|
1653
|
+
* - If unhealthy, perform the same reconnection logic as forceReconnect()
|
|
1654
|
+
*
|
|
1655
|
+
* Use this when you want efficient reconnection that avoids unnecessary disconnects.
|
|
1656
|
+
*
|
|
1657
|
+
* @example
|
|
1658
|
+
* ```typescript
|
|
1659
|
+
* // React Native example: reconnect only if needed when app comes to foreground
|
|
1660
|
+
* useEffect(() => {
|
|
1661
|
+
* const subscription = AppState.addEventListener("change", (state) => {
|
|
1662
|
+
* if (state === "active") {
|
|
1663
|
+
* client.reconnectIfNeeded();
|
|
1664
|
+
* }
|
|
1665
|
+
* });
|
|
1666
|
+
* return () => subscription.remove();
|
|
1667
|
+
* }, [client]);
|
|
1668
|
+
* ```
|
|
1669
|
+
*/
|
|
1670
|
+
reconnectIfNeeded = async () => {
|
|
1671
|
+
return this.reconnect({ force: false });
|
|
1672
|
+
};
|
|
1673
|
+
/**
|
|
1674
|
+
* Internal method to handle reconnection logic for both forceReconnect and reconnectIfNeeded.
|
|
1675
|
+
*/
|
|
1676
|
+
reconnect = async (options) => {
|
|
1677
|
+
const { force } = options;
|
|
1678
|
+
const methodName = force ? "forceReconnect" : "reconnectIfNeeded";
|
|
1679
|
+
if (this.isOffline()) {
|
|
1680
|
+
this.debug(`${methodName}: skipped (offline mode)`);
|
|
1681
|
+
return Promise.resolve();
|
|
1682
|
+
}
|
|
1683
|
+
if (!force && this.conn !== null) {
|
|
1684
|
+
try {
|
|
1685
|
+
const existingSocket = await this.conn;
|
|
1686
|
+
if (existingSocket.readyState === WebSocket.OPEN) {
|
|
1687
|
+
this.debug(`${methodName}: connection is healthy, skipping`);
|
|
1688
|
+
return Promise.resolve();
|
|
1689
|
+
}
|
|
1690
|
+
} catch {
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
this.debug(
|
|
1694
|
+
`${methodName}: ${force ? "forcing immediate reconnection" : "reconnecting"}`
|
|
1695
|
+
);
|
|
1696
|
+
this.wsIntentionalDisconnect = false;
|
|
1697
|
+
if (this.wsReconnectTimer !== null) {
|
|
1698
|
+
this.debug(`${methodName}: cancelling pending reconnection timer`);
|
|
1699
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1700
|
+
this.wsReconnectTimer = null;
|
|
1701
|
+
}
|
|
1702
|
+
this.wsReconnectAttempts = 0;
|
|
1703
|
+
if (this.conn !== null) {
|
|
1704
|
+
this.debug(`${methodName}: closing existing connection`);
|
|
1705
|
+
try {
|
|
1706
|
+
const existingSocket = await this.conn;
|
|
1707
|
+
if (this.currentWebSocket === existingSocket) {
|
|
1708
|
+
this.currentWebSocket = null;
|
|
1709
|
+
}
|
|
1710
|
+
if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
|
|
1711
|
+
existingSocket.close();
|
|
1712
|
+
}
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
this.debug(`${methodName}: error closing existing connection:`, error);
|
|
1715
|
+
}
|
|
1716
|
+
this.conn = null;
|
|
1717
|
+
this.isConnecting = false;
|
|
1718
|
+
}
|
|
1719
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1720
|
+
this.debug(`${methodName}: reconnecting with existing context`);
|
|
1721
|
+
try {
|
|
1722
|
+
this.isConnecting = true;
|
|
1723
|
+
this.conn = this.wsConnect();
|
|
1724
|
+
const socket = await this.conn;
|
|
1725
|
+
this.isConnecting = false;
|
|
1726
|
+
await this.wsSendMessage(socket, this.context, true);
|
|
1727
|
+
this.debug(`${methodName}: reconnection successful`);
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
this.isConnecting = false;
|
|
1730
|
+
this.debug(`${methodName}: reconnection failed:`, error);
|
|
1731
|
+
}
|
|
1732
|
+
} else {
|
|
1733
|
+
this.debug(`${methodName}: no context set, skipping reconnection`);
|
|
1734
|
+
}
|
|
1735
|
+
return Promise.resolve();
|
|
1736
|
+
};
|
|
1531
1737
|
/**
|
|
1532
1738
|
* If using websocket mode, close the connection when done.
|
|
1533
1739
|
* In offline mode, this is a no-op.
|
|
@@ -1546,11 +1752,17 @@ var Schematic = class {
|
|
|
1546
1752
|
if (this.conn) {
|
|
1547
1753
|
try {
|
|
1548
1754
|
const socket = await this.conn;
|
|
1755
|
+
if (this.currentWebSocket === socket) {
|
|
1756
|
+
this.debug(`Cleaning up current websocket tracking`);
|
|
1757
|
+
this.currentWebSocket = null;
|
|
1758
|
+
}
|
|
1549
1759
|
socket.close();
|
|
1550
1760
|
} catch (error) {
|
|
1551
1761
|
console.error("Error during cleanup:", error);
|
|
1552
1762
|
} finally {
|
|
1553
1763
|
this.conn = null;
|
|
1764
|
+
this.currentWebSocket = null;
|
|
1765
|
+
this.isConnecting = false;
|
|
1554
1766
|
}
|
|
1555
1767
|
}
|
|
1556
1768
|
};
|
|
@@ -1575,7 +1787,9 @@ var Schematic = class {
|
|
|
1575
1787
|
if (this.conn !== null) {
|
|
1576
1788
|
try {
|
|
1577
1789
|
const socket = await this.conn;
|
|
1578
|
-
socket.
|
|
1790
|
+
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
|
|
1791
|
+
socket.close();
|
|
1792
|
+
}
|
|
1579
1793
|
} catch (error) {
|
|
1580
1794
|
this.debug("Error closing connection on offline:", error);
|
|
1581
1795
|
}
|
|
@@ -1590,7 +1804,9 @@ var Schematic = class {
|
|
|
1590
1804
|
* Handle browser coming back online
|
|
1591
1805
|
*/
|
|
1592
1806
|
handleNetworkOnline = () => {
|
|
1593
|
-
this.debug(
|
|
1807
|
+
this.debug(
|
|
1808
|
+
"Network online, attempting reconnection and flushing queued events"
|
|
1809
|
+
);
|
|
1594
1810
|
this.wsReconnectAttempts = 0;
|
|
1595
1811
|
if (this.wsReconnectTimer !== null) {
|
|
1596
1812
|
clearTimeout(this.wsReconnectTimer);
|
|
@@ -1613,7 +1829,10 @@ var Schematic = class {
|
|
|
1613
1829
|
return;
|
|
1614
1830
|
}
|
|
1615
1831
|
if (this.wsReconnectTimer !== null) {
|
|
1616
|
-
|
|
1832
|
+
this.debug(
|
|
1833
|
+
`Reconnection attempt already scheduled, ignoring duplicate request`
|
|
1834
|
+
);
|
|
1835
|
+
return;
|
|
1617
1836
|
}
|
|
1618
1837
|
const delay = this.calculateReconnectDelay();
|
|
1619
1838
|
this.debug(
|
|
@@ -1626,23 +1845,57 @@ var Schematic = class {
|
|
|
1626
1845
|
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1627
1846
|
);
|
|
1628
1847
|
try {
|
|
1629
|
-
this.conn
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1848
|
+
if (this.conn !== null) {
|
|
1849
|
+
this.debug(`Cleaning up existing connection before reconnection`);
|
|
1850
|
+
try {
|
|
1851
|
+
const existingSocket = await this.conn;
|
|
1852
|
+
if (this.currentWebSocket === existingSocket) {
|
|
1853
|
+
this.debug(`Existing websocket is current, will be replaced`);
|
|
1854
|
+
this.currentWebSocket = null;
|
|
1855
|
+
}
|
|
1856
|
+
if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
|
|
1857
|
+
existingSocket.close();
|
|
1858
|
+
}
|
|
1859
|
+
} catch (error) {
|
|
1860
|
+
this.debug(`Error cleaning up existing connection:`, error);
|
|
1861
|
+
}
|
|
1862
|
+
this.conn = null;
|
|
1863
|
+
this.currentWebSocket = null;
|
|
1864
|
+
this.isConnecting = false;
|
|
1865
|
+
}
|
|
1866
|
+
this.isConnecting = true;
|
|
1867
|
+
try {
|
|
1868
|
+
this.conn = this.wsConnect();
|
|
1869
|
+
const socket = await this.conn;
|
|
1870
|
+
this.isConnecting = false;
|
|
1871
|
+
this.debug(`Reconnection context check:`, {
|
|
1872
|
+
hasCompany: this.context.company !== void 0,
|
|
1873
|
+
hasUser: this.context.user !== void 0,
|
|
1874
|
+
context: this.context
|
|
1875
|
+
});
|
|
1876
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1877
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1878
|
+
await this.wsSendMessage(socket, this.context, true);
|
|
1879
|
+
} else {
|
|
1880
|
+
this.debug(
|
|
1881
|
+
`No context to re-send after reconnection - websocket ready for new context`
|
|
1882
|
+
);
|
|
1883
|
+
this.debug(
|
|
1884
|
+
`Setting up tracking for reconnected websocket (no context to send)`
|
|
1885
|
+
);
|
|
1886
|
+
this.currentWebSocket = socket;
|
|
1887
|
+
}
|
|
1888
|
+
this.flushEventQueue().catch((error) => {
|
|
1889
|
+
this.debug(
|
|
1890
|
+
"Error flushing event queue after websocket reconnection:",
|
|
1891
|
+
error
|
|
1892
|
+
);
|
|
1893
|
+
});
|
|
1894
|
+
this.debug(`Reconnection successful`);
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
this.isConnecting = false;
|
|
1897
|
+
throw error;
|
|
1641
1898
|
}
|
|
1642
|
-
this.flushEventQueue().catch((error) => {
|
|
1643
|
-
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1644
|
-
});
|
|
1645
|
-
this.debug(`Reconnection successful`);
|
|
1646
1899
|
} catch (error) {
|
|
1647
1900
|
this.debug(`Reconnection attempt failed:`, error);
|
|
1648
1901
|
}
|
|
@@ -1660,6 +1913,8 @@ var Schematic = class {
|
|
|
1660
1913
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1661
1914
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1662
1915
|
const webSocket = new WebSocket(wsUrl);
|
|
1916
|
+
const connectionId = Math.random().toString(36).substring(7);
|
|
1917
|
+
this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
|
|
1663
1918
|
let timeoutId = null;
|
|
1664
1919
|
let isResolved = false;
|
|
1665
1920
|
timeoutId = setTimeout(() => {
|
|
@@ -1678,7 +1933,7 @@ var Schematic = class {
|
|
|
1678
1933
|
}
|
|
1679
1934
|
this.wsReconnectAttempts = 0;
|
|
1680
1935
|
this.wsIntentionalDisconnect = false;
|
|
1681
|
-
this.debug(`WebSocket connection opened`);
|
|
1936
|
+
this.debug(`WebSocket connection ${connectionId} opened successfully`);
|
|
1682
1937
|
resolve(webSocket);
|
|
1683
1938
|
};
|
|
1684
1939
|
webSocket.onerror = (error) => {
|
|
@@ -1686,7 +1941,7 @@ var Schematic = class {
|
|
|
1686
1941
|
if (timeoutId !== null) {
|
|
1687
1942
|
clearTimeout(timeoutId);
|
|
1688
1943
|
}
|
|
1689
|
-
this.debug(`WebSocket connection error:`, error);
|
|
1944
|
+
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1690
1945
|
reject(error);
|
|
1691
1946
|
};
|
|
1692
1947
|
webSocket.onclose = () => {
|
|
@@ -1694,121 +1949,48 @@ var Schematic = class {
|
|
|
1694
1949
|
if (timeoutId !== null) {
|
|
1695
1950
|
clearTimeout(timeoutId);
|
|
1696
1951
|
}
|
|
1697
|
-
this.debug(`WebSocket connection closed`);
|
|
1952
|
+
this.debug(`WebSocket connection ${connectionId} closed`);
|
|
1698
1953
|
this.conn = null;
|
|
1954
|
+
if (this.currentWebSocket === webSocket) {
|
|
1955
|
+
this.currentWebSocket = null;
|
|
1956
|
+
this.isConnecting = false;
|
|
1957
|
+
}
|
|
1699
1958
|
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1700
1959
|
this.attemptReconnect();
|
|
1701
1960
|
}
|
|
1702
1961
|
};
|
|
1703
1962
|
});
|
|
1704
1963
|
};
|
|
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
1964
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1761
1965
|
// and wait for the initial set of flag values to be returned
|
|
1762
|
-
wsSendMessage = (socket, context) => {
|
|
1966
|
+
wsSendMessage = (socket, context, forceContextSend = false) => {
|
|
1763
1967
|
if (this.isOffline()) {
|
|
1764
1968
|
this.debug("wsSendMessage: skipped (offline mode)");
|
|
1765
1969
|
this.setIsPending(false);
|
|
1766
1970
|
return Promise.resolve();
|
|
1767
1971
|
}
|
|
1768
1972
|
return new Promise((resolve, reject) => {
|
|
1769
|
-
if (contextString(context) == contextString(this.context)) {
|
|
1973
|
+
if (!forceContextSend && contextString(context) == contextString(this.context)) {
|
|
1770
1974
|
this.debug(`WebSocket context unchanged, skipping update`);
|
|
1771
1975
|
return resolve(this.setIsPending(false));
|
|
1772
1976
|
}
|
|
1773
|
-
this.debug(
|
|
1977
|
+
this.debug(
|
|
1978
|
+
forceContextSend ? `WebSocket force sending context (reconnection):` : `WebSocket context updated:`,
|
|
1979
|
+
context
|
|
1980
|
+
);
|
|
1774
1981
|
this.context = context;
|
|
1775
1982
|
const sendMessage = () => {
|
|
1776
1983
|
let resolved = false;
|
|
1984
|
+
const persistentMessageHandler = this.createPersistentMessageHandler(context);
|
|
1777
1985
|
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);
|
|
1986
|
+
persistentMessageHandler(event);
|
|
1806
1987
|
if (!resolved) {
|
|
1807
1988
|
resolved = true;
|
|
1808
1989
|
resolve();
|
|
1809
1990
|
}
|
|
1810
1991
|
};
|
|
1811
1992
|
socket.addEventListener("message", messageHandler);
|
|
1993
|
+
this.currentWebSocket = socket;
|
|
1812
1994
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1813
1995
|
const messagePayload = {
|
|
1814
1996
|
apiKey: this.apiKey,
|
|
@@ -1916,7 +2098,17 @@ var Schematic = class {
|
|
|
1916
2098
|
{ value }
|
|
1917
2099
|
);
|
|
1918
2100
|
}
|
|
1919
|
-
listeners.forEach((listener) =>
|
|
2101
|
+
listeners.forEach((listener, index) => {
|
|
2102
|
+
this.debug(`Calling listener ${index} for flag ${flagKey}`, {
|
|
2103
|
+
flagKey,
|
|
2104
|
+
value
|
|
2105
|
+
});
|
|
2106
|
+
notifyFlagValueListener(listener, value);
|
|
2107
|
+
this.debug(`Listener ${index} for flag ${flagKey} completed`, {
|
|
2108
|
+
flagKey,
|
|
2109
|
+
value
|
|
2110
|
+
});
|
|
2111
|
+
});
|
|
1920
2112
|
};
|
|
1921
2113
|
};
|
|
1922
2114
|
var notifyPendingListener = (listener, value) => {
|
|
@@ -1945,7 +2137,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1945
2137
|
var import_react = __toESM(require("react"));
|
|
1946
2138
|
|
|
1947
2139
|
// src/version.ts
|
|
1948
|
-
var version2 = "1.2.
|
|
2140
|
+
var version2 = "1.2.12";
|
|
1949
2141
|
|
|
1950
2142
|
// src/context/schematic.tsx
|
|
1951
2143
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -1960,7 +2152,7 @@ var SchematicProvider = ({
|
|
|
1960
2152
|
}) => {
|
|
1961
2153
|
const initialOptsRef = (0, import_react.useRef)({
|
|
1962
2154
|
publishableKey,
|
|
1963
|
-
useWebSocket:
|
|
2155
|
+
useWebSocket: true,
|
|
1964
2156
|
additionalHeaders: {
|
|
1965
2157
|
"X-Schematic-Client-Version": `schematic-react@${version2}`
|
|
1966
2158
|
},
|
|
@@ -15,7 +15,7 @@ import { StoragePersister } from '@schematichq/schematic-js';
|
|
|
15
15
|
import { Traits } from '@schematichq/schematic-js';
|
|
16
16
|
import { UsagePeriod } from '@schematichq/schematic-js';
|
|
17
17
|
|
|
18
|
-
declare type BaseSchematicProviderProps = Omit<SchematicJS.SchematicOptions, "client" | "publishableKey"> & {
|
|
18
|
+
declare type BaseSchematicProviderProps = Omit<SchematicJS.SchematicOptions, "client" | "publishableKey" | "useWebSocket"> & {
|
|
19
19
|
children: React_2.ReactNode;
|
|
20
20
|
};
|
|
21
21
|
|
|
@@ -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.12";
|
|
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();
|
|
@@ -1483,6 +1565,130 @@ var Schematic = class {
|
|
|
1483
1565
|
/**
|
|
1484
1566
|
* Websocket management
|
|
1485
1567
|
*/
|
|
1568
|
+
/**
|
|
1569
|
+
* Force an immediate WebSocket reconnection.
|
|
1570
|
+
* This is useful when the application returns from a background state (e.g., mobile app
|
|
1571
|
+
* coming back to foreground) and wants to immediately re-establish the connection
|
|
1572
|
+
* rather than waiting for the exponential backoff timer.
|
|
1573
|
+
*
|
|
1574
|
+
* This method will:
|
|
1575
|
+
* - Cancel any pending reconnection timer
|
|
1576
|
+
* - Reset the reconnection attempt counter
|
|
1577
|
+
* - Close any existing connection
|
|
1578
|
+
* - Immediately attempt to reconnect
|
|
1579
|
+
* - Re-send the current context to get fresh flag values
|
|
1580
|
+
*
|
|
1581
|
+
* Use this when you need guaranteed fresh values (e.g., after an in-app purchase).
|
|
1582
|
+
*
|
|
1583
|
+
* @example
|
|
1584
|
+
* ```typescript
|
|
1585
|
+
* // React Native example: reconnect when app comes to foreground
|
|
1586
|
+
* useEffect(() => {
|
|
1587
|
+
* const subscription = AppState.addEventListener("change", (state) => {
|
|
1588
|
+
* if (state === "active") {
|
|
1589
|
+
* client.forceReconnect();
|
|
1590
|
+
* }
|
|
1591
|
+
* });
|
|
1592
|
+
* return () => subscription.remove();
|
|
1593
|
+
* }, [client]);
|
|
1594
|
+
* ```
|
|
1595
|
+
*/
|
|
1596
|
+
forceReconnect = async () => {
|
|
1597
|
+
return this.reconnect({ force: true });
|
|
1598
|
+
};
|
|
1599
|
+
/**
|
|
1600
|
+
* Reconnect the WebSocket connection only if the current connection is unhealthy.
|
|
1601
|
+
* This is useful when the application returns from a background state and wants to
|
|
1602
|
+
* ensure a healthy connection exists, but doesn't need to force a reconnection if
|
|
1603
|
+
* the connection is still active.
|
|
1604
|
+
*
|
|
1605
|
+
* This method will:
|
|
1606
|
+
* - Check if an existing connection is healthy (readyState === OPEN)
|
|
1607
|
+
* - If healthy, return immediately without reconnecting
|
|
1608
|
+
* - If unhealthy, perform the same reconnection logic as forceReconnect()
|
|
1609
|
+
*
|
|
1610
|
+
* Use this when you want efficient reconnection that avoids unnecessary disconnects.
|
|
1611
|
+
*
|
|
1612
|
+
* @example
|
|
1613
|
+
* ```typescript
|
|
1614
|
+
* // React Native example: reconnect only if needed when app comes to foreground
|
|
1615
|
+
* useEffect(() => {
|
|
1616
|
+
* const subscription = AppState.addEventListener("change", (state) => {
|
|
1617
|
+
* if (state === "active") {
|
|
1618
|
+
* client.reconnectIfNeeded();
|
|
1619
|
+
* }
|
|
1620
|
+
* });
|
|
1621
|
+
* return () => subscription.remove();
|
|
1622
|
+
* }, [client]);
|
|
1623
|
+
* ```
|
|
1624
|
+
*/
|
|
1625
|
+
reconnectIfNeeded = async () => {
|
|
1626
|
+
return this.reconnect({ force: false });
|
|
1627
|
+
};
|
|
1628
|
+
/**
|
|
1629
|
+
* Internal method to handle reconnection logic for both forceReconnect and reconnectIfNeeded.
|
|
1630
|
+
*/
|
|
1631
|
+
reconnect = async (options) => {
|
|
1632
|
+
const { force } = options;
|
|
1633
|
+
const methodName = force ? "forceReconnect" : "reconnectIfNeeded";
|
|
1634
|
+
if (this.isOffline()) {
|
|
1635
|
+
this.debug(`${methodName}: skipped (offline mode)`);
|
|
1636
|
+
return Promise.resolve();
|
|
1637
|
+
}
|
|
1638
|
+
if (!force && this.conn !== null) {
|
|
1639
|
+
try {
|
|
1640
|
+
const existingSocket = await this.conn;
|
|
1641
|
+
if (existingSocket.readyState === WebSocket.OPEN) {
|
|
1642
|
+
this.debug(`${methodName}: connection is healthy, skipping`);
|
|
1643
|
+
return Promise.resolve();
|
|
1644
|
+
}
|
|
1645
|
+
} catch {
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
this.debug(
|
|
1649
|
+
`${methodName}: ${force ? "forcing immediate reconnection" : "reconnecting"}`
|
|
1650
|
+
);
|
|
1651
|
+
this.wsIntentionalDisconnect = false;
|
|
1652
|
+
if (this.wsReconnectTimer !== null) {
|
|
1653
|
+
this.debug(`${methodName}: cancelling pending reconnection timer`);
|
|
1654
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1655
|
+
this.wsReconnectTimer = null;
|
|
1656
|
+
}
|
|
1657
|
+
this.wsReconnectAttempts = 0;
|
|
1658
|
+
if (this.conn !== null) {
|
|
1659
|
+
this.debug(`${methodName}: closing existing connection`);
|
|
1660
|
+
try {
|
|
1661
|
+
const existingSocket = await this.conn;
|
|
1662
|
+
if (this.currentWebSocket === existingSocket) {
|
|
1663
|
+
this.currentWebSocket = null;
|
|
1664
|
+
}
|
|
1665
|
+
if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
|
|
1666
|
+
existingSocket.close();
|
|
1667
|
+
}
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
this.debug(`${methodName}: error closing existing connection:`, error);
|
|
1670
|
+
}
|
|
1671
|
+
this.conn = null;
|
|
1672
|
+
this.isConnecting = false;
|
|
1673
|
+
}
|
|
1674
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1675
|
+
this.debug(`${methodName}: reconnecting with existing context`);
|
|
1676
|
+
try {
|
|
1677
|
+
this.isConnecting = true;
|
|
1678
|
+
this.conn = this.wsConnect();
|
|
1679
|
+
const socket = await this.conn;
|
|
1680
|
+
this.isConnecting = false;
|
|
1681
|
+
await this.wsSendMessage(socket, this.context, true);
|
|
1682
|
+
this.debug(`${methodName}: reconnection successful`);
|
|
1683
|
+
} catch (error) {
|
|
1684
|
+
this.isConnecting = false;
|
|
1685
|
+
this.debug(`${methodName}: reconnection failed:`, error);
|
|
1686
|
+
}
|
|
1687
|
+
} else {
|
|
1688
|
+
this.debug(`${methodName}: no context set, skipping reconnection`);
|
|
1689
|
+
}
|
|
1690
|
+
return Promise.resolve();
|
|
1691
|
+
};
|
|
1486
1692
|
/**
|
|
1487
1693
|
* If using websocket mode, close the connection when done.
|
|
1488
1694
|
* In offline mode, this is a no-op.
|
|
@@ -1501,11 +1707,17 @@ var Schematic = class {
|
|
|
1501
1707
|
if (this.conn) {
|
|
1502
1708
|
try {
|
|
1503
1709
|
const socket = await this.conn;
|
|
1710
|
+
if (this.currentWebSocket === socket) {
|
|
1711
|
+
this.debug(`Cleaning up current websocket tracking`);
|
|
1712
|
+
this.currentWebSocket = null;
|
|
1713
|
+
}
|
|
1504
1714
|
socket.close();
|
|
1505
1715
|
} catch (error) {
|
|
1506
1716
|
console.error("Error during cleanup:", error);
|
|
1507
1717
|
} finally {
|
|
1508
1718
|
this.conn = null;
|
|
1719
|
+
this.currentWebSocket = null;
|
|
1720
|
+
this.isConnecting = false;
|
|
1509
1721
|
}
|
|
1510
1722
|
}
|
|
1511
1723
|
};
|
|
@@ -1530,7 +1742,9 @@ var Schematic = class {
|
|
|
1530
1742
|
if (this.conn !== null) {
|
|
1531
1743
|
try {
|
|
1532
1744
|
const socket = await this.conn;
|
|
1533
|
-
socket.
|
|
1745
|
+
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
|
|
1746
|
+
socket.close();
|
|
1747
|
+
}
|
|
1534
1748
|
} catch (error) {
|
|
1535
1749
|
this.debug("Error closing connection on offline:", error);
|
|
1536
1750
|
}
|
|
@@ -1545,7 +1759,9 @@ var Schematic = class {
|
|
|
1545
1759
|
* Handle browser coming back online
|
|
1546
1760
|
*/
|
|
1547
1761
|
handleNetworkOnline = () => {
|
|
1548
|
-
this.debug(
|
|
1762
|
+
this.debug(
|
|
1763
|
+
"Network online, attempting reconnection and flushing queued events"
|
|
1764
|
+
);
|
|
1549
1765
|
this.wsReconnectAttempts = 0;
|
|
1550
1766
|
if (this.wsReconnectTimer !== null) {
|
|
1551
1767
|
clearTimeout(this.wsReconnectTimer);
|
|
@@ -1568,7 +1784,10 @@ var Schematic = class {
|
|
|
1568
1784
|
return;
|
|
1569
1785
|
}
|
|
1570
1786
|
if (this.wsReconnectTimer !== null) {
|
|
1571
|
-
|
|
1787
|
+
this.debug(
|
|
1788
|
+
`Reconnection attempt already scheduled, ignoring duplicate request`
|
|
1789
|
+
);
|
|
1790
|
+
return;
|
|
1572
1791
|
}
|
|
1573
1792
|
const delay = this.calculateReconnectDelay();
|
|
1574
1793
|
this.debug(
|
|
@@ -1581,23 +1800,57 @@ var Schematic = class {
|
|
|
1581
1800
|
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1582
1801
|
);
|
|
1583
1802
|
try {
|
|
1584
|
-
this.conn
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1803
|
+
if (this.conn !== null) {
|
|
1804
|
+
this.debug(`Cleaning up existing connection before reconnection`);
|
|
1805
|
+
try {
|
|
1806
|
+
const existingSocket = await this.conn;
|
|
1807
|
+
if (this.currentWebSocket === existingSocket) {
|
|
1808
|
+
this.debug(`Existing websocket is current, will be replaced`);
|
|
1809
|
+
this.currentWebSocket = null;
|
|
1810
|
+
}
|
|
1811
|
+
if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
|
|
1812
|
+
existingSocket.close();
|
|
1813
|
+
}
|
|
1814
|
+
} catch (error) {
|
|
1815
|
+
this.debug(`Error cleaning up existing connection:`, error);
|
|
1816
|
+
}
|
|
1817
|
+
this.conn = null;
|
|
1818
|
+
this.currentWebSocket = null;
|
|
1819
|
+
this.isConnecting = false;
|
|
1820
|
+
}
|
|
1821
|
+
this.isConnecting = true;
|
|
1822
|
+
try {
|
|
1823
|
+
this.conn = this.wsConnect();
|
|
1824
|
+
const socket = await this.conn;
|
|
1825
|
+
this.isConnecting = false;
|
|
1826
|
+
this.debug(`Reconnection context check:`, {
|
|
1827
|
+
hasCompany: this.context.company !== void 0,
|
|
1828
|
+
hasUser: this.context.user !== void 0,
|
|
1829
|
+
context: this.context
|
|
1830
|
+
});
|
|
1831
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1832
|
+
this.debug(`Reconnected, force re-sending context`);
|
|
1833
|
+
await this.wsSendMessage(socket, this.context, true);
|
|
1834
|
+
} else {
|
|
1835
|
+
this.debug(
|
|
1836
|
+
`No context to re-send after reconnection - websocket ready for new context`
|
|
1837
|
+
);
|
|
1838
|
+
this.debug(
|
|
1839
|
+
`Setting up tracking for reconnected websocket (no context to send)`
|
|
1840
|
+
);
|
|
1841
|
+
this.currentWebSocket = socket;
|
|
1842
|
+
}
|
|
1843
|
+
this.flushEventQueue().catch((error) => {
|
|
1844
|
+
this.debug(
|
|
1845
|
+
"Error flushing event queue after websocket reconnection:",
|
|
1846
|
+
error
|
|
1847
|
+
);
|
|
1848
|
+
});
|
|
1849
|
+
this.debug(`Reconnection successful`);
|
|
1850
|
+
} catch (error) {
|
|
1851
|
+
this.isConnecting = false;
|
|
1852
|
+
throw error;
|
|
1596
1853
|
}
|
|
1597
|
-
this.flushEventQueue().catch((error) => {
|
|
1598
|
-
this.debug("Error flushing event queue after websocket reconnection:", error);
|
|
1599
|
-
});
|
|
1600
|
-
this.debug(`Reconnection successful`);
|
|
1601
1854
|
} catch (error) {
|
|
1602
1855
|
this.debug(`Reconnection attempt failed:`, error);
|
|
1603
1856
|
}
|
|
@@ -1615,6 +1868,8 @@ var Schematic = class {
|
|
|
1615
1868
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1616
1869
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1617
1870
|
const webSocket = new WebSocket(wsUrl);
|
|
1871
|
+
const connectionId = Math.random().toString(36).substring(7);
|
|
1872
|
+
this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
|
|
1618
1873
|
let timeoutId = null;
|
|
1619
1874
|
let isResolved = false;
|
|
1620
1875
|
timeoutId = setTimeout(() => {
|
|
@@ -1633,7 +1888,7 @@ var Schematic = class {
|
|
|
1633
1888
|
}
|
|
1634
1889
|
this.wsReconnectAttempts = 0;
|
|
1635
1890
|
this.wsIntentionalDisconnect = false;
|
|
1636
|
-
this.debug(`WebSocket connection opened`);
|
|
1891
|
+
this.debug(`WebSocket connection ${connectionId} opened successfully`);
|
|
1637
1892
|
resolve(webSocket);
|
|
1638
1893
|
};
|
|
1639
1894
|
webSocket.onerror = (error) => {
|
|
@@ -1641,7 +1896,7 @@ var Schematic = class {
|
|
|
1641
1896
|
if (timeoutId !== null) {
|
|
1642
1897
|
clearTimeout(timeoutId);
|
|
1643
1898
|
}
|
|
1644
|
-
this.debug(`WebSocket connection error:`, error);
|
|
1899
|
+
this.debug(`WebSocket connection ${connectionId} error:`, error);
|
|
1645
1900
|
reject(error);
|
|
1646
1901
|
};
|
|
1647
1902
|
webSocket.onclose = () => {
|
|
@@ -1649,121 +1904,48 @@ var Schematic = class {
|
|
|
1649
1904
|
if (timeoutId !== null) {
|
|
1650
1905
|
clearTimeout(timeoutId);
|
|
1651
1906
|
}
|
|
1652
|
-
this.debug(`WebSocket connection closed`);
|
|
1907
|
+
this.debug(`WebSocket connection ${connectionId} closed`);
|
|
1653
1908
|
this.conn = null;
|
|
1909
|
+
if (this.currentWebSocket === webSocket) {
|
|
1910
|
+
this.currentWebSocket = null;
|
|
1911
|
+
this.isConnecting = false;
|
|
1912
|
+
}
|
|
1654
1913
|
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1655
1914
|
this.attemptReconnect();
|
|
1656
1915
|
}
|
|
1657
1916
|
};
|
|
1658
1917
|
});
|
|
1659
1918
|
};
|
|
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
1919
|
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1716
1920
|
// and wait for the initial set of flag values to be returned
|
|
1717
|
-
wsSendMessage = (socket, context) => {
|
|
1921
|
+
wsSendMessage = (socket, context, forceContextSend = false) => {
|
|
1718
1922
|
if (this.isOffline()) {
|
|
1719
1923
|
this.debug("wsSendMessage: skipped (offline mode)");
|
|
1720
1924
|
this.setIsPending(false);
|
|
1721
1925
|
return Promise.resolve();
|
|
1722
1926
|
}
|
|
1723
1927
|
return new Promise((resolve, reject) => {
|
|
1724
|
-
if (contextString(context) == contextString(this.context)) {
|
|
1928
|
+
if (!forceContextSend && contextString(context) == contextString(this.context)) {
|
|
1725
1929
|
this.debug(`WebSocket context unchanged, skipping update`);
|
|
1726
1930
|
return resolve(this.setIsPending(false));
|
|
1727
1931
|
}
|
|
1728
|
-
this.debug(
|
|
1932
|
+
this.debug(
|
|
1933
|
+
forceContextSend ? `WebSocket force sending context (reconnection):` : `WebSocket context updated:`,
|
|
1934
|
+
context
|
|
1935
|
+
);
|
|
1729
1936
|
this.context = context;
|
|
1730
1937
|
const sendMessage = () => {
|
|
1731
1938
|
let resolved = false;
|
|
1939
|
+
const persistentMessageHandler = this.createPersistentMessageHandler(context);
|
|
1732
1940
|
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);
|
|
1941
|
+
persistentMessageHandler(event);
|
|
1761
1942
|
if (!resolved) {
|
|
1762
1943
|
resolved = true;
|
|
1763
1944
|
resolve();
|
|
1764
1945
|
}
|
|
1765
1946
|
};
|
|
1766
1947
|
socket.addEventListener("message", messageHandler);
|
|
1948
|
+
this.currentWebSocket = socket;
|
|
1767
1949
|
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1768
1950
|
const messagePayload = {
|
|
1769
1951
|
apiKey: this.apiKey,
|
|
@@ -1871,7 +2053,17 @@ var Schematic = class {
|
|
|
1871
2053
|
{ value }
|
|
1872
2054
|
);
|
|
1873
2055
|
}
|
|
1874
|
-
listeners.forEach((listener) =>
|
|
2056
|
+
listeners.forEach((listener, index) => {
|
|
2057
|
+
this.debug(`Calling listener ${index} for flag ${flagKey}`, {
|
|
2058
|
+
flagKey,
|
|
2059
|
+
value
|
|
2060
|
+
});
|
|
2061
|
+
notifyFlagValueListener(listener, value);
|
|
2062
|
+
this.debug(`Listener ${index} for flag ${flagKey} completed`, {
|
|
2063
|
+
flagKey,
|
|
2064
|
+
value
|
|
2065
|
+
});
|
|
2066
|
+
});
|
|
1875
2067
|
};
|
|
1876
2068
|
};
|
|
1877
2069
|
var notifyPendingListener = (listener, value) => {
|
|
@@ -1900,7 +2092,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1900
2092
|
import React, { createContext, useEffect, useMemo, useRef } from "react";
|
|
1901
2093
|
|
|
1902
2094
|
// src/version.ts
|
|
1903
|
-
var version2 = "1.2.
|
|
2095
|
+
var version2 = "1.2.12";
|
|
1904
2096
|
|
|
1905
2097
|
// src/context/schematic.tsx
|
|
1906
2098
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -1915,7 +2107,7 @@ var SchematicProvider = ({
|
|
|
1915
2107
|
}) => {
|
|
1916
2108
|
const initialOptsRef = useRef({
|
|
1917
2109
|
publishableKey,
|
|
1918
|
-
useWebSocket:
|
|
2110
|
+
useWebSocket: true,
|
|
1919
2111
|
additionalHeaders: {
|
|
1920
2112
|
"X-Schematic-Client-Version": `schematic-react@${version2}`
|
|
1921
2113
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schematichq/schematic-react",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.12",
|
|
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.12"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@eslint/js": "^9.39.1",
|