@schematichq/schematic-react 1.2.5 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/dist/schematic-react.cjs.js +230 -13
- package/dist/schematic-react.d.ts +3 -0
- package/dist/schematic-react.esm.js +230 -13
- package/package.json +25 -22
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2025 Schematic, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -134,6 +134,8 @@ const MyComponent = () => {
|
|
|
134
134
|
};
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
+
*Note: `useSchematicIsPending` is checking if entitlement data has been loaded, typically via `identify`. It should, therefore, be used to wrap flag and entitlement checks, but never the initial call to `identify`.*
|
|
138
|
+
|
|
137
139
|
## Troubleshooting
|
|
138
140
|
|
|
139
141
|
For debugging and development, Schematic supports two special modes:
|
|
@@ -72,20 +72,20 @@ var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__
|
|
|
72
72
|
var require_browser_polyfill = __commonJS({
|
|
73
73
|
"node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
|
|
74
74
|
(function(self2) {
|
|
75
|
-
var irrelevant = function(exports2) {
|
|
75
|
+
var irrelevant = (function(exports2) {
|
|
76
76
|
var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
|
|
77
77
|
typeof global !== "undefined" && global || {};
|
|
78
78
|
var support = {
|
|
79
79
|
searchParams: "URLSearchParams" in g,
|
|
80
80
|
iterable: "Symbol" in g && "iterator" in Symbol,
|
|
81
|
-
blob: "FileReader" in g && "Blob" in g && function() {
|
|
81
|
+
blob: "FileReader" in g && "Blob" in g && (function() {
|
|
82
82
|
try {
|
|
83
83
|
new Blob();
|
|
84
84
|
return true;
|
|
85
85
|
} catch (e) {
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
|
-
}(),
|
|
88
|
+
})(),
|
|
89
89
|
formData: "FormData" in g,
|
|
90
90
|
arrayBuffer: "ArrayBuffer" in g
|
|
91
91
|
};
|
|
@@ -387,12 +387,12 @@ var require_browser_polyfill = __commonJS({
|
|
|
387
387
|
}
|
|
388
388
|
this.method = normalizeMethod(options.method || this.method || "GET");
|
|
389
389
|
this.mode = options.mode || this.mode || null;
|
|
390
|
-
this.signal = options.signal || this.signal || function() {
|
|
390
|
+
this.signal = options.signal || this.signal || (function() {
|
|
391
391
|
if ("AbortController" in g) {
|
|
392
392
|
var ctrl = new AbortController();
|
|
393
393
|
return ctrl.signal;
|
|
394
394
|
}
|
|
395
|
-
}();
|
|
395
|
+
})();
|
|
396
396
|
this.referrer = null;
|
|
397
397
|
if ((this.method === "GET" || this.method === "HEAD") && body) {
|
|
398
398
|
throw new TypeError("Body not allowed for GET or HEAD requests");
|
|
@@ -599,7 +599,7 @@ var require_browser_polyfill = __commonJS({
|
|
|
599
599
|
exports2.Response = Response;
|
|
600
600
|
exports2.fetch = fetch2;
|
|
601
601
|
return exports2;
|
|
602
|
-
}({});
|
|
602
|
+
})({});
|
|
603
603
|
})(typeof self !== "undefined" ? self : exports);
|
|
604
604
|
}
|
|
605
605
|
});
|
|
@@ -623,10 +623,7 @@ function rng() {
|
|
|
623
623
|
}
|
|
624
624
|
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
|
625
625
|
var native_default = { randomUUID };
|
|
626
|
-
function
|
|
627
|
-
if (native_default.randomUUID && !buf && !options) {
|
|
628
|
-
return native_default.randomUUID();
|
|
629
|
-
}
|
|
626
|
+
function _v4(options, buf, offset) {
|
|
630
627
|
options = options || {};
|
|
631
628
|
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
632
629
|
if (rnds.length < 16) {
|
|
@@ -646,6 +643,12 @@ function v4(options, buf, offset) {
|
|
|
646
643
|
}
|
|
647
644
|
return unsafeStringify(rnds);
|
|
648
645
|
}
|
|
646
|
+
function v4(options, buf, offset) {
|
|
647
|
+
if (native_default.randomUUID && !buf && !options) {
|
|
648
|
+
return native_default.randomUUID();
|
|
649
|
+
}
|
|
650
|
+
return _v4(options, buf, offset);
|
|
651
|
+
}
|
|
649
652
|
var v4_default = v4;
|
|
650
653
|
var import_polyfill = __toESM2(require_browser_polyfill());
|
|
651
654
|
function CheckFlagResponseDataFromJSON(json) {
|
|
@@ -796,7 +799,7 @@ function contextString(context) {
|
|
|
796
799
|
}, {});
|
|
797
800
|
return JSON.stringify(sortedContext);
|
|
798
801
|
}
|
|
799
|
-
var version = "1.2.
|
|
802
|
+
var version = "1.2.7";
|
|
800
803
|
var anonymousIdKey = "schematicId";
|
|
801
804
|
var Schematic = class {
|
|
802
805
|
additionalHeaders = {};
|
|
@@ -807,6 +810,7 @@ var Schematic = class {
|
|
|
807
810
|
debugEnabled = false;
|
|
808
811
|
offlineEnabled = false;
|
|
809
812
|
eventQueue;
|
|
813
|
+
contextDependentEventQueue;
|
|
810
814
|
eventUrl = "https://c.schematichq.com";
|
|
811
815
|
flagCheckListeners = {};
|
|
812
816
|
flagValueListeners = {};
|
|
@@ -817,9 +821,18 @@ var Schematic = class {
|
|
|
817
821
|
checks = {};
|
|
818
822
|
featureUsageEventMap = {};
|
|
819
823
|
webSocketUrl = "wss://api.schematichq.com";
|
|
824
|
+
webSocketConnectionTimeout = 1e4;
|
|
825
|
+
webSocketReconnect = true;
|
|
826
|
+
webSocketMaxReconnectAttempts = 7;
|
|
827
|
+
webSocketInitialRetryDelay = 1e3;
|
|
828
|
+
webSocketMaxRetryDelay = 3e4;
|
|
829
|
+
wsReconnectAttempts = 0;
|
|
830
|
+
wsReconnectTimer = null;
|
|
831
|
+
wsIntentionalDisconnect = false;
|
|
820
832
|
constructor(apiKey, options) {
|
|
821
833
|
this.apiKey = apiKey;
|
|
822
834
|
this.eventQueue = [];
|
|
835
|
+
this.contextDependentEventQueue = [];
|
|
823
836
|
this.useWebSocket = options?.useWebSocket ?? false;
|
|
824
837
|
this.debugEnabled = options?.debug ?? false;
|
|
825
838
|
this.offlineEnabled = options?.offline ?? false;
|
|
@@ -859,10 +872,36 @@ var Schematic = class {
|
|
|
859
872
|
if (options?.webSocketUrl !== void 0) {
|
|
860
873
|
this.webSocketUrl = options.webSocketUrl;
|
|
861
874
|
}
|
|
875
|
+
if (options?.webSocketConnectionTimeout !== void 0) {
|
|
876
|
+
this.webSocketConnectionTimeout = options.webSocketConnectionTimeout;
|
|
877
|
+
}
|
|
878
|
+
if (options?.webSocketReconnect !== void 0) {
|
|
879
|
+
this.webSocketReconnect = options.webSocketReconnect;
|
|
880
|
+
}
|
|
881
|
+
if (options?.webSocketMaxReconnectAttempts !== void 0) {
|
|
882
|
+
this.webSocketMaxReconnectAttempts = options.webSocketMaxReconnectAttempts;
|
|
883
|
+
}
|
|
884
|
+
if (options?.webSocketInitialRetryDelay !== void 0) {
|
|
885
|
+
this.webSocketInitialRetryDelay = options.webSocketInitialRetryDelay;
|
|
886
|
+
}
|
|
887
|
+
if (options?.webSocketMaxRetryDelay !== void 0) {
|
|
888
|
+
this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
|
|
889
|
+
}
|
|
862
890
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
863
891
|
window.addEventListener("beforeunload", () => {
|
|
864
892
|
this.flushEventQueue();
|
|
893
|
+
this.flushContextDependentEventQueue();
|
|
865
894
|
});
|
|
895
|
+
if (this.useWebSocket) {
|
|
896
|
+
window.addEventListener("offline", () => {
|
|
897
|
+
this.debug("Browser went offline, closing WebSocket connection");
|
|
898
|
+
this.handleNetworkOffline();
|
|
899
|
+
});
|
|
900
|
+
window.addEventListener("online", () => {
|
|
901
|
+
this.debug("Browser came online, attempting to reconnect WebSocket");
|
|
902
|
+
this.handleNetworkOnline();
|
|
903
|
+
});
|
|
904
|
+
}
|
|
866
905
|
}
|
|
867
906
|
if (this.offlineEnabled) {
|
|
868
907
|
this.debug(
|
|
@@ -1120,12 +1159,20 @@ var Schematic = class {
|
|
|
1120
1159
|
setContext = async (context) => {
|
|
1121
1160
|
if (this.isOffline() || !this.useWebSocket) {
|
|
1122
1161
|
this.context = context;
|
|
1162
|
+
this.flushContextDependentEventQueue();
|
|
1123
1163
|
this.setIsPending(false);
|
|
1124
1164
|
return Promise.resolve();
|
|
1125
1165
|
}
|
|
1126
1166
|
try {
|
|
1127
1167
|
this.setIsPending(true);
|
|
1128
1168
|
if (!this.conn) {
|
|
1169
|
+
if (this.wsReconnectTimer !== null) {
|
|
1170
|
+
this.debug(
|
|
1171
|
+
`Cancelling scheduled reconnection, connecting immediately`
|
|
1172
|
+
);
|
|
1173
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1174
|
+
this.wsReconnectTimer = null;
|
|
1175
|
+
}
|
|
1129
1176
|
this.conn = this.wsConnect();
|
|
1130
1177
|
}
|
|
1131
1178
|
const socket = await this.conn;
|
|
@@ -1142,6 +1189,25 @@ var Schematic = class {
|
|
|
1142
1189
|
*/
|
|
1143
1190
|
track = (body) => {
|
|
1144
1191
|
const { company, user, event, traits, quantity = 1 } = body;
|
|
1192
|
+
if (!this.hasContext(company, user)) {
|
|
1193
|
+
this.debug(`track: queuing event "${event}" until context is available`);
|
|
1194
|
+
const queuedEvent = {
|
|
1195
|
+
api_key: this.apiKey,
|
|
1196
|
+
body: {
|
|
1197
|
+
company,
|
|
1198
|
+
event,
|
|
1199
|
+
traits: traits ?? {},
|
|
1200
|
+
user,
|
|
1201
|
+
quantity
|
|
1202
|
+
},
|
|
1203
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1204
|
+
tracker_event_id: v4_default(),
|
|
1205
|
+
tracker_user_id: this.getAnonymousId(),
|
|
1206
|
+
type: "track"
|
|
1207
|
+
};
|
|
1208
|
+
this.contextDependentEventQueue.push(queuedEvent);
|
|
1209
|
+
return Promise.resolve();
|
|
1210
|
+
}
|
|
1145
1211
|
const trackData = {
|
|
1146
1212
|
company: company ?? this.context.company,
|
|
1147
1213
|
event,
|
|
@@ -1204,6 +1270,38 @@ var Schematic = class {
|
|
|
1204
1270
|
/**
|
|
1205
1271
|
* Event processing
|
|
1206
1272
|
*/
|
|
1273
|
+
hasContext = (company, user) => {
|
|
1274
|
+
const hasProvidedContext = company !== void 0 && company !== null && Object.keys(company).length > 0 || user !== void 0 && user !== null && Object.keys(user).length > 0;
|
|
1275
|
+
const hasInstanceContext = this.context.company !== void 0 && this.context.company !== null && Object.keys(this.context.company).length > 0 || this.context.user !== void 0 && this.context.user !== null && Object.keys(this.context.user).length > 0;
|
|
1276
|
+
return hasProvidedContext || hasInstanceContext;
|
|
1277
|
+
};
|
|
1278
|
+
flushContextDependentEventQueue = () => {
|
|
1279
|
+
this.debug(
|
|
1280
|
+
`flushing ${this.contextDependentEventQueue.length} context-dependent events`
|
|
1281
|
+
);
|
|
1282
|
+
while (this.contextDependentEventQueue.length > 0) {
|
|
1283
|
+
const event = this.contextDependentEventQueue.shift();
|
|
1284
|
+
if (event) {
|
|
1285
|
+
if (event.type === "track" && typeof event.body === "object" && event.body !== null) {
|
|
1286
|
+
const trackBody = event.body;
|
|
1287
|
+
const updatedBody = {
|
|
1288
|
+
...trackBody,
|
|
1289
|
+
company: trackBody.company ?? this.context.company,
|
|
1290
|
+
user: trackBody.user ?? this.context.user
|
|
1291
|
+
};
|
|
1292
|
+
const updatedEvent = {
|
|
1293
|
+
...event,
|
|
1294
|
+
body: updatedBody,
|
|
1295
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1296
|
+
// Update timestamp to actual send time
|
|
1297
|
+
};
|
|
1298
|
+
this.sendEvent(updatedEvent);
|
|
1299
|
+
} else {
|
|
1300
|
+
this.sendEvent(event);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1207
1305
|
flushEventQueue = () => {
|
|
1208
1306
|
while (this.eventQueue.length > 0) {
|
|
1209
1307
|
const event = this.eventQueue.shift();
|
|
@@ -1233,7 +1331,7 @@ var Schematic = class {
|
|
|
1233
1331
|
tracker_user_id: this.getAnonymousId(),
|
|
1234
1332
|
type: eventType
|
|
1235
1333
|
};
|
|
1236
|
-
if (document?.hidden) {
|
|
1334
|
+
if (typeof document !== "undefined" && document?.hidden) {
|
|
1237
1335
|
return this.storeEvent(event);
|
|
1238
1336
|
} else {
|
|
1239
1337
|
return this.sendEvent(event);
|
|
@@ -1281,6 +1379,11 @@ var Schematic = class {
|
|
|
1281
1379
|
this.debug("cleanup: skipped (offline mode)");
|
|
1282
1380
|
return Promise.resolve();
|
|
1283
1381
|
}
|
|
1382
|
+
this.wsIntentionalDisconnect = true;
|
|
1383
|
+
if (this.wsReconnectTimer !== null) {
|
|
1384
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1385
|
+
this.wsReconnectTimer = null;
|
|
1386
|
+
}
|
|
1284
1387
|
if (this.conn) {
|
|
1285
1388
|
try {
|
|
1286
1389
|
const socket = await this.conn;
|
|
@@ -1292,6 +1395,91 @@ var Schematic = class {
|
|
|
1292
1395
|
}
|
|
1293
1396
|
}
|
|
1294
1397
|
};
|
|
1398
|
+
/**
|
|
1399
|
+
* Calculate the delay for the next reconnection attempt using exponential backoff with jitter.
|
|
1400
|
+
* This helps prevent dogpiling when the server recovers from an outage.
|
|
1401
|
+
*/
|
|
1402
|
+
calculateReconnectDelay = () => {
|
|
1403
|
+
const exponentialDelay = this.webSocketInitialRetryDelay * Math.pow(2, this.wsReconnectAttempts);
|
|
1404
|
+
const cappedDelay = Math.min(exponentialDelay, this.webSocketMaxRetryDelay);
|
|
1405
|
+
const jitter = Math.random() * cappedDelay * 0.5;
|
|
1406
|
+
const totalDelay = cappedDelay + jitter;
|
|
1407
|
+
this.debug(
|
|
1408
|
+
`Reconnect delay calculated: ${totalDelay.toFixed(0)}ms (attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts})`
|
|
1409
|
+
);
|
|
1410
|
+
return totalDelay;
|
|
1411
|
+
};
|
|
1412
|
+
/**
|
|
1413
|
+
* Handle browser going offline
|
|
1414
|
+
*/
|
|
1415
|
+
handleNetworkOffline = async () => {
|
|
1416
|
+
if (this.conn !== null) {
|
|
1417
|
+
try {
|
|
1418
|
+
const socket = await this.conn;
|
|
1419
|
+
socket.close();
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
this.debug("Error closing connection on offline:", error);
|
|
1422
|
+
}
|
|
1423
|
+
this.conn = null;
|
|
1424
|
+
}
|
|
1425
|
+
if (this.wsReconnectTimer !== null) {
|
|
1426
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1427
|
+
this.wsReconnectTimer = null;
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
/**
|
|
1431
|
+
* Handle browser coming back online
|
|
1432
|
+
*/
|
|
1433
|
+
handleNetworkOnline = () => {
|
|
1434
|
+
if (this.context.company === void 0 && this.context.user === void 0) {
|
|
1435
|
+
this.debug("No context set, skipping reconnection");
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
this.wsReconnectAttempts = 0;
|
|
1439
|
+
if (this.wsReconnectTimer !== null) {
|
|
1440
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1441
|
+
this.wsReconnectTimer = null;
|
|
1442
|
+
}
|
|
1443
|
+
this.debug("Network online, reconnecting immediately");
|
|
1444
|
+
this.attemptReconnect();
|
|
1445
|
+
};
|
|
1446
|
+
/**
|
|
1447
|
+
* Attempt to reconnect the WebSocket connection with exponential backoff.
|
|
1448
|
+
* Called automatically when the connection closes unexpectedly.
|
|
1449
|
+
*/
|
|
1450
|
+
attemptReconnect = () => {
|
|
1451
|
+
if (this.wsReconnectAttempts >= this.webSocketMaxReconnectAttempts) {
|
|
1452
|
+
this.debug(
|
|
1453
|
+
`Maximum reconnection attempts (${this.webSocketMaxReconnectAttempts}) reached, giving up`
|
|
1454
|
+
);
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
if (this.wsReconnectTimer !== null) {
|
|
1458
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1459
|
+
}
|
|
1460
|
+
const delay = this.calculateReconnectDelay();
|
|
1461
|
+
this.debug(
|
|
1462
|
+
`Scheduling reconnection attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts} in ${delay.toFixed(0)}ms`
|
|
1463
|
+
);
|
|
1464
|
+
this.wsReconnectTimer = setTimeout(async () => {
|
|
1465
|
+
this.wsReconnectTimer = null;
|
|
1466
|
+
this.wsReconnectAttempts++;
|
|
1467
|
+
this.debug(
|
|
1468
|
+
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1469
|
+
);
|
|
1470
|
+
try {
|
|
1471
|
+
this.conn = this.wsConnect();
|
|
1472
|
+
const socket = await this.conn;
|
|
1473
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1474
|
+
this.debug(`Reconnected, re-sending context`);
|
|
1475
|
+
await this.wsSendMessage(socket, this.context);
|
|
1476
|
+
}
|
|
1477
|
+
this.debug(`Reconnection successful`);
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
this.debug(`Reconnection attempt failed:`, error);
|
|
1480
|
+
}
|
|
1481
|
+
}, delay);
|
|
1482
|
+
};
|
|
1295
1483
|
// Open a websocket connection
|
|
1296
1484
|
wsConnect = () => {
|
|
1297
1485
|
if (this.isOffline()) {
|
|
@@ -1304,17 +1492,45 @@ var Schematic = class {
|
|
|
1304
1492
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1305
1493
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1306
1494
|
const webSocket = new WebSocket(wsUrl);
|
|
1495
|
+
let timeoutId = null;
|
|
1496
|
+
let isResolved = false;
|
|
1497
|
+
timeoutId = setTimeout(() => {
|
|
1498
|
+
if (!isResolved) {
|
|
1499
|
+
this.debug(
|
|
1500
|
+
`WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`
|
|
1501
|
+
);
|
|
1502
|
+
webSocket.close();
|
|
1503
|
+
reject(new Error("WebSocket connection timeout"));
|
|
1504
|
+
}
|
|
1505
|
+
}, this.webSocketConnectionTimeout);
|
|
1307
1506
|
webSocket.onopen = () => {
|
|
1507
|
+
isResolved = true;
|
|
1508
|
+
if (timeoutId !== null) {
|
|
1509
|
+
clearTimeout(timeoutId);
|
|
1510
|
+
}
|
|
1511
|
+
this.wsReconnectAttempts = 0;
|
|
1512
|
+
this.wsIntentionalDisconnect = false;
|
|
1308
1513
|
this.debug(`WebSocket connection opened`);
|
|
1309
1514
|
resolve(webSocket);
|
|
1310
1515
|
};
|
|
1311
1516
|
webSocket.onerror = (error) => {
|
|
1517
|
+
isResolved = true;
|
|
1518
|
+
if (timeoutId !== null) {
|
|
1519
|
+
clearTimeout(timeoutId);
|
|
1520
|
+
}
|
|
1312
1521
|
this.debug(`WebSocket connection error:`, error);
|
|
1313
1522
|
reject(error);
|
|
1314
1523
|
};
|
|
1315
1524
|
webSocket.onclose = () => {
|
|
1525
|
+
isResolved = true;
|
|
1526
|
+
if (timeoutId !== null) {
|
|
1527
|
+
clearTimeout(timeoutId);
|
|
1528
|
+
}
|
|
1316
1529
|
this.debug(`WebSocket connection closed`);
|
|
1317
1530
|
this.conn = null;
|
|
1531
|
+
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1532
|
+
this.attemptReconnect();
|
|
1533
|
+
}
|
|
1318
1534
|
};
|
|
1319
1535
|
});
|
|
1320
1536
|
};
|
|
@@ -1362,6 +1578,7 @@ var Schematic = class {
|
|
|
1362
1578
|
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1363
1579
|
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1364
1580
|
});
|
|
1581
|
+
this.flushContextDependentEventQueue();
|
|
1365
1582
|
this.setIsPending(false);
|
|
1366
1583
|
if (!resolved) {
|
|
1367
1584
|
resolved = true;
|
|
@@ -1505,7 +1722,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1505
1722
|
var import_react = __toESM(require("react"));
|
|
1506
1723
|
|
|
1507
1724
|
// src/version.ts
|
|
1508
|
-
var version2 = "1.2.
|
|
1725
|
+
var version2 = "1.2.7";
|
|
1509
1726
|
|
|
1510
1727
|
// src/context/schematic.tsx
|
|
1511
1728
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -11,6 +11,7 @@ import { Schematic } from '@schematichq/schematic-js';
|
|
|
11
11
|
import { SchematicContext } from '@schematichq/schematic-js';
|
|
12
12
|
import * as SchematicJS from '@schematichq/schematic-js';
|
|
13
13
|
import { SchematicOptions } from '@schematichq/schematic-js';
|
|
14
|
+
import { StoragePersister } from '@schematichq/schematic-js';
|
|
14
15
|
import { Traits } from '@schematichq/schematic-js';
|
|
15
16
|
import { UsagePeriod } from '@schematichq/schematic-js';
|
|
16
17
|
|
|
@@ -62,6 +63,8 @@ declare type SchematicProviderPropsWithPublishableKey = BaseSchematicProviderPro
|
|
|
62
63
|
publishableKey: string;
|
|
63
64
|
};
|
|
64
65
|
|
|
66
|
+
export { StoragePersister }
|
|
67
|
+
|
|
65
68
|
export { Traits }
|
|
66
69
|
|
|
67
70
|
export { UsagePeriod }
|
|
@@ -27,20 +27,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var require_browser_polyfill = __commonJS({
|
|
28
28
|
"node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
|
|
29
29
|
(function(self2) {
|
|
30
|
-
var irrelevant = function(exports2) {
|
|
30
|
+
var irrelevant = (function(exports2) {
|
|
31
31
|
var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
|
|
32
32
|
typeof global !== "undefined" && global || {};
|
|
33
33
|
var support = {
|
|
34
34
|
searchParams: "URLSearchParams" in g,
|
|
35
35
|
iterable: "Symbol" in g && "iterator" in Symbol,
|
|
36
|
-
blob: "FileReader" in g && "Blob" in g && function() {
|
|
36
|
+
blob: "FileReader" in g && "Blob" in g && (function() {
|
|
37
37
|
try {
|
|
38
38
|
new Blob();
|
|
39
39
|
return true;
|
|
40
40
|
} catch (e) {
|
|
41
41
|
return false;
|
|
42
42
|
}
|
|
43
|
-
}(),
|
|
43
|
+
})(),
|
|
44
44
|
formData: "FormData" in g,
|
|
45
45
|
arrayBuffer: "ArrayBuffer" in g
|
|
46
46
|
};
|
|
@@ -342,12 +342,12 @@ var require_browser_polyfill = __commonJS({
|
|
|
342
342
|
}
|
|
343
343
|
this.method = normalizeMethod(options.method || this.method || "GET");
|
|
344
344
|
this.mode = options.mode || this.mode || null;
|
|
345
|
-
this.signal = options.signal || this.signal || function() {
|
|
345
|
+
this.signal = options.signal || this.signal || (function() {
|
|
346
346
|
if ("AbortController" in g) {
|
|
347
347
|
var ctrl = new AbortController();
|
|
348
348
|
return ctrl.signal;
|
|
349
349
|
}
|
|
350
|
-
}();
|
|
350
|
+
})();
|
|
351
351
|
this.referrer = null;
|
|
352
352
|
if ((this.method === "GET" || this.method === "HEAD") && body) {
|
|
353
353
|
throw new TypeError("Body not allowed for GET or HEAD requests");
|
|
@@ -554,7 +554,7 @@ var require_browser_polyfill = __commonJS({
|
|
|
554
554
|
exports2.Response = Response;
|
|
555
555
|
exports2.fetch = fetch2;
|
|
556
556
|
return exports2;
|
|
557
|
-
}({});
|
|
557
|
+
})({});
|
|
558
558
|
})(typeof self !== "undefined" ? self : exports);
|
|
559
559
|
}
|
|
560
560
|
});
|
|
@@ -578,10 +578,7 @@ function rng() {
|
|
|
578
578
|
}
|
|
579
579
|
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
|
580
580
|
var native_default = { randomUUID };
|
|
581
|
-
function
|
|
582
|
-
if (native_default.randomUUID && !buf && !options) {
|
|
583
|
-
return native_default.randomUUID();
|
|
584
|
-
}
|
|
581
|
+
function _v4(options, buf, offset) {
|
|
585
582
|
options = options || {};
|
|
586
583
|
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
587
584
|
if (rnds.length < 16) {
|
|
@@ -601,6 +598,12 @@ function v4(options, buf, offset) {
|
|
|
601
598
|
}
|
|
602
599
|
return unsafeStringify(rnds);
|
|
603
600
|
}
|
|
601
|
+
function v4(options, buf, offset) {
|
|
602
|
+
if (native_default.randomUUID && !buf && !options) {
|
|
603
|
+
return native_default.randomUUID();
|
|
604
|
+
}
|
|
605
|
+
return _v4(options, buf, offset);
|
|
606
|
+
}
|
|
604
607
|
var v4_default = v4;
|
|
605
608
|
var import_polyfill = __toESM(require_browser_polyfill());
|
|
606
609
|
function CheckFlagResponseDataFromJSON(json) {
|
|
@@ -751,7 +754,7 @@ function contextString(context) {
|
|
|
751
754
|
}, {});
|
|
752
755
|
return JSON.stringify(sortedContext);
|
|
753
756
|
}
|
|
754
|
-
var version = "1.2.
|
|
757
|
+
var version = "1.2.7";
|
|
755
758
|
var anonymousIdKey = "schematicId";
|
|
756
759
|
var Schematic = class {
|
|
757
760
|
additionalHeaders = {};
|
|
@@ -762,6 +765,7 @@ var Schematic = class {
|
|
|
762
765
|
debugEnabled = false;
|
|
763
766
|
offlineEnabled = false;
|
|
764
767
|
eventQueue;
|
|
768
|
+
contextDependentEventQueue;
|
|
765
769
|
eventUrl = "https://c.schematichq.com";
|
|
766
770
|
flagCheckListeners = {};
|
|
767
771
|
flagValueListeners = {};
|
|
@@ -772,9 +776,18 @@ var Schematic = class {
|
|
|
772
776
|
checks = {};
|
|
773
777
|
featureUsageEventMap = {};
|
|
774
778
|
webSocketUrl = "wss://api.schematichq.com";
|
|
779
|
+
webSocketConnectionTimeout = 1e4;
|
|
780
|
+
webSocketReconnect = true;
|
|
781
|
+
webSocketMaxReconnectAttempts = 7;
|
|
782
|
+
webSocketInitialRetryDelay = 1e3;
|
|
783
|
+
webSocketMaxRetryDelay = 3e4;
|
|
784
|
+
wsReconnectAttempts = 0;
|
|
785
|
+
wsReconnectTimer = null;
|
|
786
|
+
wsIntentionalDisconnect = false;
|
|
775
787
|
constructor(apiKey, options) {
|
|
776
788
|
this.apiKey = apiKey;
|
|
777
789
|
this.eventQueue = [];
|
|
790
|
+
this.contextDependentEventQueue = [];
|
|
778
791
|
this.useWebSocket = options?.useWebSocket ?? false;
|
|
779
792
|
this.debugEnabled = options?.debug ?? false;
|
|
780
793
|
this.offlineEnabled = options?.offline ?? false;
|
|
@@ -814,10 +827,36 @@ var Schematic = class {
|
|
|
814
827
|
if (options?.webSocketUrl !== void 0) {
|
|
815
828
|
this.webSocketUrl = options.webSocketUrl;
|
|
816
829
|
}
|
|
830
|
+
if (options?.webSocketConnectionTimeout !== void 0) {
|
|
831
|
+
this.webSocketConnectionTimeout = options.webSocketConnectionTimeout;
|
|
832
|
+
}
|
|
833
|
+
if (options?.webSocketReconnect !== void 0) {
|
|
834
|
+
this.webSocketReconnect = options.webSocketReconnect;
|
|
835
|
+
}
|
|
836
|
+
if (options?.webSocketMaxReconnectAttempts !== void 0) {
|
|
837
|
+
this.webSocketMaxReconnectAttempts = options.webSocketMaxReconnectAttempts;
|
|
838
|
+
}
|
|
839
|
+
if (options?.webSocketInitialRetryDelay !== void 0) {
|
|
840
|
+
this.webSocketInitialRetryDelay = options.webSocketInitialRetryDelay;
|
|
841
|
+
}
|
|
842
|
+
if (options?.webSocketMaxRetryDelay !== void 0) {
|
|
843
|
+
this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
|
|
844
|
+
}
|
|
817
845
|
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
818
846
|
window.addEventListener("beforeunload", () => {
|
|
819
847
|
this.flushEventQueue();
|
|
848
|
+
this.flushContextDependentEventQueue();
|
|
820
849
|
});
|
|
850
|
+
if (this.useWebSocket) {
|
|
851
|
+
window.addEventListener("offline", () => {
|
|
852
|
+
this.debug("Browser went offline, closing WebSocket connection");
|
|
853
|
+
this.handleNetworkOffline();
|
|
854
|
+
});
|
|
855
|
+
window.addEventListener("online", () => {
|
|
856
|
+
this.debug("Browser came online, attempting to reconnect WebSocket");
|
|
857
|
+
this.handleNetworkOnline();
|
|
858
|
+
});
|
|
859
|
+
}
|
|
821
860
|
}
|
|
822
861
|
if (this.offlineEnabled) {
|
|
823
862
|
this.debug(
|
|
@@ -1075,12 +1114,20 @@ var Schematic = class {
|
|
|
1075
1114
|
setContext = async (context) => {
|
|
1076
1115
|
if (this.isOffline() || !this.useWebSocket) {
|
|
1077
1116
|
this.context = context;
|
|
1117
|
+
this.flushContextDependentEventQueue();
|
|
1078
1118
|
this.setIsPending(false);
|
|
1079
1119
|
return Promise.resolve();
|
|
1080
1120
|
}
|
|
1081
1121
|
try {
|
|
1082
1122
|
this.setIsPending(true);
|
|
1083
1123
|
if (!this.conn) {
|
|
1124
|
+
if (this.wsReconnectTimer !== null) {
|
|
1125
|
+
this.debug(
|
|
1126
|
+
`Cancelling scheduled reconnection, connecting immediately`
|
|
1127
|
+
);
|
|
1128
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1129
|
+
this.wsReconnectTimer = null;
|
|
1130
|
+
}
|
|
1084
1131
|
this.conn = this.wsConnect();
|
|
1085
1132
|
}
|
|
1086
1133
|
const socket = await this.conn;
|
|
@@ -1097,6 +1144,25 @@ var Schematic = class {
|
|
|
1097
1144
|
*/
|
|
1098
1145
|
track = (body) => {
|
|
1099
1146
|
const { company, user, event, traits, quantity = 1 } = body;
|
|
1147
|
+
if (!this.hasContext(company, user)) {
|
|
1148
|
+
this.debug(`track: queuing event "${event}" until context is available`);
|
|
1149
|
+
const queuedEvent = {
|
|
1150
|
+
api_key: this.apiKey,
|
|
1151
|
+
body: {
|
|
1152
|
+
company,
|
|
1153
|
+
event,
|
|
1154
|
+
traits: traits ?? {},
|
|
1155
|
+
user,
|
|
1156
|
+
quantity
|
|
1157
|
+
},
|
|
1158
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1159
|
+
tracker_event_id: v4_default(),
|
|
1160
|
+
tracker_user_id: this.getAnonymousId(),
|
|
1161
|
+
type: "track"
|
|
1162
|
+
};
|
|
1163
|
+
this.contextDependentEventQueue.push(queuedEvent);
|
|
1164
|
+
return Promise.resolve();
|
|
1165
|
+
}
|
|
1100
1166
|
const trackData = {
|
|
1101
1167
|
company: company ?? this.context.company,
|
|
1102
1168
|
event,
|
|
@@ -1159,6 +1225,38 @@ var Schematic = class {
|
|
|
1159
1225
|
/**
|
|
1160
1226
|
* Event processing
|
|
1161
1227
|
*/
|
|
1228
|
+
hasContext = (company, user) => {
|
|
1229
|
+
const hasProvidedContext = company !== void 0 && company !== null && Object.keys(company).length > 0 || user !== void 0 && user !== null && Object.keys(user).length > 0;
|
|
1230
|
+
const hasInstanceContext = this.context.company !== void 0 && this.context.company !== null && Object.keys(this.context.company).length > 0 || this.context.user !== void 0 && this.context.user !== null && Object.keys(this.context.user).length > 0;
|
|
1231
|
+
return hasProvidedContext || hasInstanceContext;
|
|
1232
|
+
};
|
|
1233
|
+
flushContextDependentEventQueue = () => {
|
|
1234
|
+
this.debug(
|
|
1235
|
+
`flushing ${this.contextDependentEventQueue.length} context-dependent events`
|
|
1236
|
+
);
|
|
1237
|
+
while (this.contextDependentEventQueue.length > 0) {
|
|
1238
|
+
const event = this.contextDependentEventQueue.shift();
|
|
1239
|
+
if (event) {
|
|
1240
|
+
if (event.type === "track" && typeof event.body === "object" && event.body !== null) {
|
|
1241
|
+
const trackBody = event.body;
|
|
1242
|
+
const updatedBody = {
|
|
1243
|
+
...trackBody,
|
|
1244
|
+
company: trackBody.company ?? this.context.company,
|
|
1245
|
+
user: trackBody.user ?? this.context.user
|
|
1246
|
+
};
|
|
1247
|
+
const updatedEvent = {
|
|
1248
|
+
...event,
|
|
1249
|
+
body: updatedBody,
|
|
1250
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1251
|
+
// Update timestamp to actual send time
|
|
1252
|
+
};
|
|
1253
|
+
this.sendEvent(updatedEvent);
|
|
1254
|
+
} else {
|
|
1255
|
+
this.sendEvent(event);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1162
1260
|
flushEventQueue = () => {
|
|
1163
1261
|
while (this.eventQueue.length > 0) {
|
|
1164
1262
|
const event = this.eventQueue.shift();
|
|
@@ -1188,7 +1286,7 @@ var Schematic = class {
|
|
|
1188
1286
|
tracker_user_id: this.getAnonymousId(),
|
|
1189
1287
|
type: eventType
|
|
1190
1288
|
};
|
|
1191
|
-
if (document?.hidden) {
|
|
1289
|
+
if (typeof document !== "undefined" && document?.hidden) {
|
|
1192
1290
|
return this.storeEvent(event);
|
|
1193
1291
|
} else {
|
|
1194
1292
|
return this.sendEvent(event);
|
|
@@ -1236,6 +1334,11 @@ var Schematic = class {
|
|
|
1236
1334
|
this.debug("cleanup: skipped (offline mode)");
|
|
1237
1335
|
return Promise.resolve();
|
|
1238
1336
|
}
|
|
1337
|
+
this.wsIntentionalDisconnect = true;
|
|
1338
|
+
if (this.wsReconnectTimer !== null) {
|
|
1339
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1340
|
+
this.wsReconnectTimer = null;
|
|
1341
|
+
}
|
|
1239
1342
|
if (this.conn) {
|
|
1240
1343
|
try {
|
|
1241
1344
|
const socket = await this.conn;
|
|
@@ -1247,6 +1350,91 @@ var Schematic = class {
|
|
|
1247
1350
|
}
|
|
1248
1351
|
}
|
|
1249
1352
|
};
|
|
1353
|
+
/**
|
|
1354
|
+
* Calculate the delay for the next reconnection attempt using exponential backoff with jitter.
|
|
1355
|
+
* This helps prevent dogpiling when the server recovers from an outage.
|
|
1356
|
+
*/
|
|
1357
|
+
calculateReconnectDelay = () => {
|
|
1358
|
+
const exponentialDelay = this.webSocketInitialRetryDelay * Math.pow(2, this.wsReconnectAttempts);
|
|
1359
|
+
const cappedDelay = Math.min(exponentialDelay, this.webSocketMaxRetryDelay);
|
|
1360
|
+
const jitter = Math.random() * cappedDelay * 0.5;
|
|
1361
|
+
const totalDelay = cappedDelay + jitter;
|
|
1362
|
+
this.debug(
|
|
1363
|
+
`Reconnect delay calculated: ${totalDelay.toFixed(0)}ms (attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts})`
|
|
1364
|
+
);
|
|
1365
|
+
return totalDelay;
|
|
1366
|
+
};
|
|
1367
|
+
/**
|
|
1368
|
+
* Handle browser going offline
|
|
1369
|
+
*/
|
|
1370
|
+
handleNetworkOffline = async () => {
|
|
1371
|
+
if (this.conn !== null) {
|
|
1372
|
+
try {
|
|
1373
|
+
const socket = await this.conn;
|
|
1374
|
+
socket.close();
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
this.debug("Error closing connection on offline:", error);
|
|
1377
|
+
}
|
|
1378
|
+
this.conn = null;
|
|
1379
|
+
}
|
|
1380
|
+
if (this.wsReconnectTimer !== null) {
|
|
1381
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1382
|
+
this.wsReconnectTimer = null;
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
/**
|
|
1386
|
+
* Handle browser coming back online
|
|
1387
|
+
*/
|
|
1388
|
+
handleNetworkOnline = () => {
|
|
1389
|
+
if (this.context.company === void 0 && this.context.user === void 0) {
|
|
1390
|
+
this.debug("No context set, skipping reconnection");
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
this.wsReconnectAttempts = 0;
|
|
1394
|
+
if (this.wsReconnectTimer !== null) {
|
|
1395
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1396
|
+
this.wsReconnectTimer = null;
|
|
1397
|
+
}
|
|
1398
|
+
this.debug("Network online, reconnecting immediately");
|
|
1399
|
+
this.attemptReconnect();
|
|
1400
|
+
};
|
|
1401
|
+
/**
|
|
1402
|
+
* Attempt to reconnect the WebSocket connection with exponential backoff.
|
|
1403
|
+
* Called automatically when the connection closes unexpectedly.
|
|
1404
|
+
*/
|
|
1405
|
+
attemptReconnect = () => {
|
|
1406
|
+
if (this.wsReconnectAttempts >= this.webSocketMaxReconnectAttempts) {
|
|
1407
|
+
this.debug(
|
|
1408
|
+
`Maximum reconnection attempts (${this.webSocketMaxReconnectAttempts}) reached, giving up`
|
|
1409
|
+
);
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (this.wsReconnectTimer !== null) {
|
|
1413
|
+
clearTimeout(this.wsReconnectTimer);
|
|
1414
|
+
}
|
|
1415
|
+
const delay = this.calculateReconnectDelay();
|
|
1416
|
+
this.debug(
|
|
1417
|
+
`Scheduling reconnection attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts} in ${delay.toFixed(0)}ms`
|
|
1418
|
+
);
|
|
1419
|
+
this.wsReconnectTimer = setTimeout(async () => {
|
|
1420
|
+
this.wsReconnectTimer = null;
|
|
1421
|
+
this.wsReconnectAttempts++;
|
|
1422
|
+
this.debug(
|
|
1423
|
+
`Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
|
|
1424
|
+
);
|
|
1425
|
+
try {
|
|
1426
|
+
this.conn = this.wsConnect();
|
|
1427
|
+
const socket = await this.conn;
|
|
1428
|
+
if (this.context.company !== void 0 || this.context.user !== void 0) {
|
|
1429
|
+
this.debug(`Reconnected, re-sending context`);
|
|
1430
|
+
await this.wsSendMessage(socket, this.context);
|
|
1431
|
+
}
|
|
1432
|
+
this.debug(`Reconnection successful`);
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
this.debug(`Reconnection attempt failed:`, error);
|
|
1435
|
+
}
|
|
1436
|
+
}, delay);
|
|
1437
|
+
};
|
|
1250
1438
|
// Open a websocket connection
|
|
1251
1439
|
wsConnect = () => {
|
|
1252
1440
|
if (this.isOffline()) {
|
|
@@ -1259,17 +1447,45 @@ var Schematic = class {
|
|
|
1259
1447
|
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1260
1448
|
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1261
1449
|
const webSocket = new WebSocket(wsUrl);
|
|
1450
|
+
let timeoutId = null;
|
|
1451
|
+
let isResolved = false;
|
|
1452
|
+
timeoutId = setTimeout(() => {
|
|
1453
|
+
if (!isResolved) {
|
|
1454
|
+
this.debug(
|
|
1455
|
+
`WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`
|
|
1456
|
+
);
|
|
1457
|
+
webSocket.close();
|
|
1458
|
+
reject(new Error("WebSocket connection timeout"));
|
|
1459
|
+
}
|
|
1460
|
+
}, this.webSocketConnectionTimeout);
|
|
1262
1461
|
webSocket.onopen = () => {
|
|
1462
|
+
isResolved = true;
|
|
1463
|
+
if (timeoutId !== null) {
|
|
1464
|
+
clearTimeout(timeoutId);
|
|
1465
|
+
}
|
|
1466
|
+
this.wsReconnectAttempts = 0;
|
|
1467
|
+
this.wsIntentionalDisconnect = false;
|
|
1263
1468
|
this.debug(`WebSocket connection opened`);
|
|
1264
1469
|
resolve(webSocket);
|
|
1265
1470
|
};
|
|
1266
1471
|
webSocket.onerror = (error) => {
|
|
1472
|
+
isResolved = true;
|
|
1473
|
+
if (timeoutId !== null) {
|
|
1474
|
+
clearTimeout(timeoutId);
|
|
1475
|
+
}
|
|
1267
1476
|
this.debug(`WebSocket connection error:`, error);
|
|
1268
1477
|
reject(error);
|
|
1269
1478
|
};
|
|
1270
1479
|
webSocket.onclose = () => {
|
|
1480
|
+
isResolved = true;
|
|
1481
|
+
if (timeoutId !== null) {
|
|
1482
|
+
clearTimeout(timeoutId);
|
|
1483
|
+
}
|
|
1271
1484
|
this.debug(`WebSocket connection closed`);
|
|
1272
1485
|
this.conn = null;
|
|
1486
|
+
if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
|
|
1487
|
+
this.attemptReconnect();
|
|
1488
|
+
}
|
|
1273
1489
|
};
|
|
1274
1490
|
});
|
|
1275
1491
|
};
|
|
@@ -1317,6 +1533,7 @@ var Schematic = class {
|
|
|
1317
1533
|
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1318
1534
|
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1319
1535
|
});
|
|
1536
|
+
this.flushContextDependentEventQueue();
|
|
1320
1537
|
this.setIsPending(false);
|
|
1321
1538
|
if (!resolved) {
|
|
1322
1539
|
resolved = true;
|
|
@@ -1460,7 +1677,7 @@ var notifyFlagValueListener = (listener, value) => {
|
|
|
1460
1677
|
import React, { createContext, useEffect, useMemo, useRef } from "react";
|
|
1461
1678
|
|
|
1462
1679
|
// src/version.ts
|
|
1463
|
-
var version2 = "1.2.
|
|
1680
|
+
var version2 = "1.2.7";
|
|
1464
1681
|
|
|
1465
1682
|
// src/context/schematic.tsx
|
|
1466
1683
|
import { jsx } from "react/jsx-runtime";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schematichq/schematic-react",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"main": "dist/schematic-react.cjs.js",
|
|
5
5
|
"module": "dist/schematic-react.esm.js",
|
|
6
6
|
"types": "dist/schematic-react.d.ts",
|
|
@@ -24,35 +24,38 @@
|
|
|
24
24
|
"clean": "rm -rf dist",
|
|
25
25
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
26
26
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --fix",
|
|
27
|
-
"test": "
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:reactnative": "vitest run --config vitest.config.reactnative.ts",
|
|
29
|
+
"test:watch": "vitest",
|
|
28
30
|
"tsc": "npx tsc",
|
|
29
31
|
"prepare": "husky"
|
|
30
32
|
},
|
|
31
33
|
"dependencies": {
|
|
32
|
-
"@schematichq/schematic-js": "^1.2.
|
|
34
|
+
"@schematichq/schematic-js": "^1.2.7"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
|
-
"@eslint/js": "^9.
|
|
36
|
-
"@microsoft/api-extractor": "^7.
|
|
37
|
-
"@
|
|
38
|
-
"@
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
37
|
+
"@eslint/js": "^9.39.1",
|
|
38
|
+
"@microsoft/api-extractor": "^7.55.0",
|
|
39
|
+
"@testing-library/dom": "^10.4.1",
|
|
40
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
41
|
+
"@testing-library/react": "^16.3.0",
|
|
42
|
+
"@types/react": "^19.2.3",
|
|
43
|
+
"@vitest/browser": "^4.0.8",
|
|
44
|
+
"esbuild": "^0.27.0",
|
|
45
|
+
"eslint": "^9.39.1",
|
|
46
|
+
"eslint-plugin-import": "^2.32.0",
|
|
43
47
|
"eslint-plugin-react": "^7.37.5",
|
|
44
|
-
"eslint-plugin-react-hooks": "^
|
|
45
|
-
"globals": "^16.
|
|
48
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
49
|
+
"globals": "^16.5.0",
|
|
50
|
+
"happy-dom": "^20.0.10",
|
|
46
51
|
"husky": "^9.1.7",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"typescript": "^5.7.3",
|
|
55
|
-
"typescript-eslint": "^8.29.1"
|
|
52
|
+
"jsdom": "^27.2.0",
|
|
53
|
+
"prettier": "^3.6.2",
|
|
54
|
+
"react": "^19.2.0",
|
|
55
|
+
"react-dom": "^19.2.0",
|
|
56
|
+
"typescript": "^5.9.3",
|
|
57
|
+
"typescript-eslint": "^8.46.4",
|
|
58
|
+
"vitest": "^4.0.8"
|
|
56
59
|
},
|
|
57
60
|
"peerDependencies": {
|
|
58
61
|
"react": ">=18"
|