@schematichq/schematic-react 1.0.2 → 1.1.0

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.
@@ -28,11 +28,12 @@ var require_browser_polyfill = __commonJS({
28
28
  "node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
29
29
  (function(self2) {
30
30
  var irrelevant = function(exports2) {
31
- var global = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || typeof global !== "undefined" && global;
31
+ var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
32
+ typeof global !== "undefined" && global || {};
32
33
  var support = {
33
- searchParams: "URLSearchParams" in global,
34
- iterable: "Symbol" in global && "iterator" in Symbol,
35
- blob: "FileReader" in global && "Blob" in global && function() {
34
+ searchParams: "URLSearchParams" in g,
35
+ iterable: "Symbol" in g && "iterator" in Symbol,
36
+ blob: "FileReader" in g && "Blob" in g && function() {
36
37
  try {
37
38
  new Blob();
38
39
  return true;
@@ -40,8 +41,8 @@ var require_browser_polyfill = __commonJS({
40
41
  return false;
41
42
  }
42
43
  }(),
43
- formData: "FormData" in global,
44
- arrayBuffer: "ArrayBuffer" in global
44
+ formData: "FormData" in g,
45
+ arrayBuffer: "ArrayBuffer" in g
45
46
  };
46
47
  function isDataView(obj) {
47
48
  return obj && DataView.prototype.isPrototypeOf(obj);
@@ -99,6 +100,9 @@ var require_browser_polyfill = __commonJS({
99
100
  }, this);
100
101
  } else if (Array.isArray(headers)) {
101
102
  headers.forEach(function(header) {
103
+ if (header.length != 2) {
104
+ throw new TypeError("Headers constructor: expected name/value pair to be length 2, found" + header.length);
105
+ }
102
106
  this.append(header[0], header[1]);
103
107
  }, this);
104
108
  } else if (headers) {
@@ -158,6 +162,7 @@ var require_browser_polyfill = __commonJS({
158
162
  Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
159
163
  }
160
164
  function consumed(body) {
165
+ if (body._noBody) return;
161
166
  if (body.bodyUsed) {
162
167
  return Promise.reject(new TypeError("Already read"));
163
168
  }
@@ -182,14 +187,16 @@ var require_browser_polyfill = __commonJS({
182
187
  function readBlobAsText(blob) {
183
188
  var reader = new FileReader();
184
189
  var promise = fileReaderReady(reader);
185
- reader.readAsText(blob);
190
+ var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type);
191
+ var encoding = match ? match[1] : "utf-8";
192
+ reader.readAsText(blob, encoding);
186
193
  return promise;
187
194
  }
188
195
  function readArrayBufferAsText(buf) {
189
196
  var view = new Uint8Array(buf);
190
197
  var chars = new Array(view.length);
191
- for (var i2 = 0; i2 < view.length; i2++) {
192
- chars[i2] = String.fromCharCode(view[i2]);
198
+ for (var i = 0; i < view.length; i++) {
199
+ chars[i] = String.fromCharCode(view[i]);
193
200
  }
194
201
  return chars.join("");
195
202
  }
@@ -208,6 +215,7 @@ var require_browser_polyfill = __commonJS({
208
215
  this.bodyUsed = this.bodyUsed;
209
216
  this._bodyInit = body;
210
217
  if (!body) {
218
+ this._noBody = true;
211
219
  this._bodyText = "";
212
220
  } else if (typeof body === "string") {
213
221
  this._bodyText = body;
@@ -251,27 +259,28 @@ var require_browser_polyfill = __commonJS({
251
259
  return Promise.resolve(new Blob([this._bodyText]));
252
260
  }
253
261
  };
254
- this.arrayBuffer = function() {
255
- if (this._bodyArrayBuffer) {
256
- var isConsumed = consumed(this);
257
- if (isConsumed) {
258
- return isConsumed;
259
- }
260
- if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
261
- return Promise.resolve(
262
- this._bodyArrayBuffer.buffer.slice(
263
- this._bodyArrayBuffer.byteOffset,
264
- this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
265
- )
266
- );
267
- } else {
268
- return Promise.resolve(this._bodyArrayBuffer);
269
- }
262
+ }
263
+ this.arrayBuffer = function() {
264
+ if (this._bodyArrayBuffer) {
265
+ var isConsumed = consumed(this);
266
+ if (isConsumed) {
267
+ return isConsumed;
268
+ } else if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
269
+ return Promise.resolve(
270
+ this._bodyArrayBuffer.buffer.slice(
271
+ this._bodyArrayBuffer.byteOffset,
272
+ this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
273
+ )
274
+ );
270
275
  } else {
271
- return this.blob().then(readBlobAsArrayBuffer);
276
+ return Promise.resolve(this._bodyArrayBuffer);
272
277
  }
273
- };
274
- }
278
+ } else if (support.blob) {
279
+ return this.blob().then(readBlobAsArrayBuffer);
280
+ } else {
281
+ throw new Error("could not read as ArrayBuffer");
282
+ }
283
+ };
275
284
  this.text = function() {
276
285
  var rejected = consumed(this);
277
286
  if (rejected) {
@@ -297,7 +306,7 @@ var require_browser_polyfill = __commonJS({
297
306
  };
298
307
  return this;
299
308
  }
300
- var methods = ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"];
309
+ var methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"];
301
310
  function normalizeMethod(method) {
302
311
  var upcased = method.toUpperCase();
303
312
  return methods.indexOf(upcased) > -1 ? upcased : method;
@@ -333,7 +342,12 @@ var require_browser_polyfill = __commonJS({
333
342
  }
334
343
  this.method = normalizeMethod(options.method || this.method || "GET");
335
344
  this.mode = options.mode || this.mode || null;
336
- this.signal = options.signal || this.signal;
345
+ this.signal = options.signal || this.signal || function() {
346
+ if ("AbortController" in g) {
347
+ var ctrl = new AbortController();
348
+ return ctrl.signal;
349
+ }
350
+ }();
337
351
  this.referrer = null;
338
352
  if ((this.method === "GET" || this.method === "HEAD") && body) {
339
353
  throw new TypeError("Body not allowed for GET or HEAD requests");
@@ -376,7 +390,11 @@ var require_browser_polyfill = __commonJS({
376
390
  var key = parts.shift().trim();
377
391
  if (key) {
378
392
  var value = parts.join(":").trim();
379
- headers.append(key, value);
393
+ try {
394
+ headers.append(key, value);
395
+ } catch (error) {
396
+ console.warn("Response " + error.message);
397
+ }
380
398
  }
381
399
  });
382
400
  return headers;
@@ -391,6 +409,9 @@ var require_browser_polyfill = __commonJS({
391
409
  }
392
410
  this.type = "default";
393
411
  this.status = options.status === void 0 ? 200 : options.status;
412
+ if (this.status < 200 || this.status > 599) {
413
+ throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");
414
+ }
394
415
  this.ok = this.status >= 200 && this.status < 300;
395
416
  this.statusText = options.statusText === void 0 ? "" : "" + options.statusText;
396
417
  this.headers = new Headers(options.headers);
@@ -407,7 +428,9 @@ var require_browser_polyfill = __commonJS({
407
428
  });
408
429
  };
409
430
  Response.error = function() {
410
- var response = new Response(null, { status: 0, statusText: "" });
431
+ var response = new Response(null, { status: 200, statusText: "" });
432
+ response.ok = false;
433
+ response.status = 0;
411
434
  response.type = "error";
412
435
  return response;
413
436
  };
@@ -418,7 +441,7 @@ var require_browser_polyfill = __commonJS({
418
441
  }
419
442
  return new Response(null, { status, headers: { location: url } });
420
443
  };
421
- exports2.DOMException = global.DOMException;
444
+ exports2.DOMException = g.DOMException;
422
445
  try {
423
446
  new exports2.DOMException();
424
447
  } catch (err) {
@@ -443,10 +466,14 @@ var require_browser_polyfill = __commonJS({
443
466
  }
444
467
  xhr.onload = function() {
445
468
  var options = {
446
- status: xhr.status,
447
469
  statusText: xhr.statusText,
448
470
  headers: parseHeaders(xhr.getAllResponseHeaders() || "")
449
471
  };
472
+ if (request.url.indexOf("file://") === 0 && (xhr.status < 200 || xhr.status > 599)) {
473
+ options.status = 200;
474
+ } else {
475
+ options.status = xhr.status;
476
+ }
450
477
  options.url = "responseURL" in xhr ? xhr.responseURL : options.headers.get("X-Request-URL");
451
478
  var body = "response" in xhr ? xhr.response : xhr.responseText;
452
479
  setTimeout(function() {
@@ -460,7 +487,7 @@ var require_browser_polyfill = __commonJS({
460
487
  };
461
488
  xhr.ontimeout = function() {
462
489
  setTimeout(function() {
463
- reject(new TypeError("Network request failed"));
490
+ reject(new TypeError("Network request timed out"));
464
491
  }, 0);
465
492
  };
466
493
  xhr.onabort = function() {
@@ -470,7 +497,7 @@ var require_browser_polyfill = __commonJS({
470
497
  };
471
498
  function fixUrl(url) {
472
499
  try {
473
- return url === "" && global.location.href ? global.location.href : url;
500
+ return url === "" && g.location.href ? g.location.href : url;
474
501
  } catch (e) {
475
502
  return url;
476
503
  }
@@ -484,14 +511,21 @@ var require_browser_polyfill = __commonJS({
484
511
  if ("responseType" in xhr) {
485
512
  if (support.blob) {
486
513
  xhr.responseType = "blob";
487
- } else if (support.arrayBuffer && request.headers.get("Content-Type") && request.headers.get("Content-Type").indexOf("application/octet-stream") !== -1) {
514
+ } else if (support.arrayBuffer) {
488
515
  xhr.responseType = "arraybuffer";
489
516
  }
490
517
  }
491
- if (init && typeof init.headers === "object" && !(init.headers instanceof Headers)) {
518
+ if (init && typeof init.headers === "object" && !(init.headers instanceof Headers || g.Headers && init.headers instanceof g.Headers)) {
519
+ var names = [];
492
520
  Object.getOwnPropertyNames(init.headers).forEach(function(name) {
521
+ names.push(normalizeName(name));
493
522
  xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
494
523
  });
524
+ request.headers.forEach(function(value, name) {
525
+ if (names.indexOf(name) === -1) {
526
+ xhr.setRequestHeader(name, value);
527
+ }
528
+ });
495
529
  } else {
496
530
  request.headers.forEach(function(value, name) {
497
531
  xhr.setRequestHeader(name, value);
@@ -509,11 +543,11 @@ var require_browser_polyfill = __commonJS({
509
543
  });
510
544
  }
511
545
  fetch2.polyfill = true;
512
- if (!global.fetch) {
513
- global.fetch = fetch2;
514
- global.Headers = Headers;
515
- global.Request = Request;
516
- global.Response = Response;
546
+ if (!g.fetch) {
547
+ g.fetch = fetch2;
548
+ g.Headers = Headers;
549
+ g.Request = Request;
550
+ g.Response = Response;
517
551
  }
518
552
  exports2.Headers = Headers;
519
553
  exports2.Request = Request;
@@ -525,10 +559,9 @@ var require_browser_polyfill = __commonJS({
525
559
  }
526
560
  });
527
561
  var byteToHex = [];
528
- for (i = 0; i < 256; ++i) {
562
+ for (let i = 0; i < 256; ++i) {
529
563
  byteToHex.push((i + 256).toString(16).slice(1));
530
564
  }
531
- var i;
532
565
  function unsafeStringify(arr, offset = 0) {
533
566
  return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
534
567
  }
@@ -536,29 +569,33 @@ var getRandomValues;
536
569
  var rnds8 = new Uint8Array(16);
537
570
  function rng() {
538
571
  if (!getRandomValues) {
539
- getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto);
540
- if (!getRandomValues) {
572
+ if (typeof crypto === "undefined" || !crypto.getRandomValues) {
541
573
  throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
542
574
  }
575
+ getRandomValues = crypto.getRandomValues.bind(crypto);
543
576
  }
544
577
  return getRandomValues(rnds8);
545
578
  }
546
579
  var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
547
- var native_default = {
548
- randomUUID
549
- };
580
+ var native_default = { randomUUID };
550
581
  function v4(options, buf, offset) {
551
582
  if (native_default.randomUUID && !buf && !options) {
552
583
  return native_default.randomUUID();
553
584
  }
554
585
  options = options || {};
555
- var rnds = options.random || (options.rng || rng)();
586
+ const rnds = options.random ?? options.rng?.() ?? rng();
587
+ if (rnds.length < 16) {
588
+ throw new Error("Random bytes length must be >= 16");
589
+ }
556
590
  rnds[6] = rnds[6] & 15 | 64;
557
591
  rnds[8] = rnds[8] & 63 | 128;
558
592
  if (buf) {
559
593
  offset = offset || 0;
560
- for (var i2 = 0; i2 < 16; ++i2) {
561
- buf[offset + i2] = rnds[i2];
594
+ if (offset < 0 || offset + 16 > buf.length) {
595
+ throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
596
+ }
597
+ for (let i = 0; i < 16; ++i) {
598
+ buf[offset + i] = rnds[i];
562
599
  }
563
600
  return buf;
564
601
  }
@@ -566,6 +603,117 @@ function v4(options, buf, offset) {
566
603
  }
567
604
  var v4_default = v4;
568
605
  var import_polyfill = __toESM(require_browser_polyfill());
606
+ function CheckFlagResponseDataFromJSON(json) {
607
+ return CheckFlagResponseDataFromJSONTyped(json, false);
608
+ }
609
+ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
610
+ if (json == null) {
611
+ return json;
612
+ }
613
+ return {
614
+ companyId: json["company_id"] == null ? void 0 : json["company_id"],
615
+ error: json["error"] == null ? void 0 : json["error"],
616
+ featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
617
+ featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
618
+ featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
619
+ featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
620
+ flag: json["flag"],
621
+ flagId: json["flag_id"] == null ? void 0 : json["flag_id"],
622
+ reason: json["reason"],
623
+ ruleId: json["rule_id"] == null ? void 0 : json["rule_id"],
624
+ ruleType: json["rule_type"] == null ? void 0 : json["rule_type"],
625
+ userId: json["user_id"] == null ? void 0 : json["user_id"],
626
+ value: json["value"]
627
+ };
628
+ }
629
+ function CheckFlagResponseFromJSON(json) {
630
+ return CheckFlagResponseFromJSONTyped(json, false);
631
+ }
632
+ function CheckFlagResponseFromJSONTyped(json, ignoreDiscriminator) {
633
+ if (json == null) {
634
+ return json;
635
+ }
636
+ return {
637
+ data: CheckFlagResponseDataFromJSON(json["data"]),
638
+ params: json["params"]
639
+ };
640
+ }
641
+ function CheckFlagsResponseDataFromJSON(json) {
642
+ return CheckFlagsResponseDataFromJSONTyped(json, false);
643
+ }
644
+ function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
645
+ if (json == null) {
646
+ return json;
647
+ }
648
+ return {
649
+ flags: json["flags"].map(CheckFlagResponseDataFromJSON)
650
+ };
651
+ }
652
+ function CheckFlagsResponseFromJSON(json) {
653
+ return CheckFlagsResponseFromJSONTyped(json, false);
654
+ }
655
+ function CheckFlagsResponseFromJSONTyped(json, ignoreDiscriminator) {
656
+ if (json == null) {
657
+ return json;
658
+ }
659
+ return {
660
+ data: CheckFlagsResponseDataFromJSON(json["data"]),
661
+ params: json["params"]
662
+ };
663
+ }
664
+ var RuleType = /* @__PURE__ */ ((RuleType2) => {
665
+ RuleType2["GLOBAL_OVERRIDE"] = "global_override";
666
+ RuleType2["COMPANY_OVERRIDE"] = "company_override";
667
+ RuleType2["COMPANY_OVERRIDE_USAGE_EXCEEDED"] = "company_override_usage_exceeded";
668
+ RuleType2["PLAN_ENTITLEMENT"] = "plan_entitlement";
669
+ RuleType2["PLAN_ENTITLEMENT_USAGE_EXCEEDED"] = "plan_entitlement_usage_exceeded";
670
+ RuleType2["STANDARD"] = "standard";
671
+ RuleType2["DEFAULT"] = "default";
672
+ return RuleType2;
673
+ })(RuleType || {});
674
+ var UsagePeriod = /* @__PURE__ */ ((UsagePeriod2) => {
675
+ UsagePeriod2["ALL_TIME"] = "all_time";
676
+ UsagePeriod2["CURRENT_DAY"] = "current_day";
677
+ UsagePeriod2["CURRENT_MONTH"] = "current_month";
678
+ UsagePeriod2["CURRENT_WEEK"] = "current_week";
679
+ return UsagePeriod2;
680
+ })(UsagePeriod || {});
681
+ var CheckFlagReturnFromJSON = (json) => {
682
+ const {
683
+ companyId,
684
+ error,
685
+ featureAllocation,
686
+ featureUsage,
687
+ featureUsagePeriod,
688
+ featureUsageResetAt,
689
+ flag,
690
+ flagId,
691
+ reason,
692
+ ruleId,
693
+ ruleType,
694
+ userId,
695
+ value
696
+ } = CheckFlagResponseDataFromJSON(json);
697
+ const featureUsageExceeded = !value && // if flag is not false, then we haven't exceeded usage
698
+ (ruleType == "company_override_usage_exceeded" || // if the rule type is one of these, then we have exceeded usage
699
+ ruleType == "plan_entitlement_usage_exceeded");
700
+ return {
701
+ featureUsageExceeded,
702
+ companyId: companyId == null ? void 0 : companyId,
703
+ error: error == null ? void 0 : error,
704
+ featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
705
+ featureUsage: featureUsage == null ? void 0 : featureUsage,
706
+ featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
707
+ featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
708
+ flag,
709
+ flagId: flagId == null ? void 0 : flagId,
710
+ reason,
711
+ ruleId: ruleId == null ? void 0 : ruleId,
712
+ ruleType: ruleType == null ? void 0 : ruleType,
713
+ userId: userId == null ? void 0 : userId,
714
+ value
715
+ };
716
+ };
569
717
  function contextString(context) {
570
718
  const sortedContext = Object.keys(context).reduce((acc, key) => {
571
719
  const sortedKeys = Object.keys(
@@ -589,19 +737,18 @@ var Schematic = class {
589
737
  context = {};
590
738
  eventQueue;
591
739
  eventUrl = "https://c.schematichq.com";
592
- flagListener;
740
+ flagCheckListeners = {};
593
741
  flagValueListeners = {};
594
742
  isPending = true;
595
743
  isPendingListeners = /* @__PURE__ */ new Set();
596
744
  storage;
597
745
  useWebSocket = false;
598
- values = {};
746
+ checks = {};
599
747
  webSocketUrl = "wss://api.schematichq.com";
600
748
  constructor(apiKey, options) {
601
749
  this.apiKey = apiKey;
602
750
  this.eventQueue = [];
603
751
  this.useWebSocket = options?.useWebSocket ?? false;
604
- this.flagListener = options?.flagListener;
605
752
  if (options?.additionalHeaders) {
606
753
  this.additionalHeaders = options.additionalHeaders;
607
754
  }
@@ -625,37 +772,89 @@ var Schematic = class {
625
772
  });
626
773
  }
627
774
  }
628
- // Get value for a single flag
629
- // If in websocket mode, return the local value, otherwise make an API call
775
+ /**
776
+ * Get value for a single flag.
777
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
778
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
779
+ * connection fails.
780
+ * In REST mode, makes an API call for each check.
781
+ */
630
782
  async checkFlag(options) {
631
783
  const { fallback = false, key } = options;
632
784
  const context = options.context || this.context;
633
- if (this.useWebSocket) {
634
- const contextVals = this.values[contextString(context)] ?? {};
635
- return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
785
+ const contextStr = contextString(context);
786
+ if (!this.useWebSocket) {
787
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
788
+ return fetch(requestUrl, {
789
+ method: "POST",
790
+ headers: {
791
+ ...this.additionalHeaders ?? {},
792
+ "Content-Type": "application/json;charset=UTF-8",
793
+ "X-Schematic-Api-Key": this.apiKey
794
+ },
795
+ body: JSON.stringify(context)
796
+ }).then((response) => {
797
+ if (!response.ok) {
798
+ throw new Error("Network response was not ok");
799
+ }
800
+ return response.json();
801
+ }).then((response) => {
802
+ return CheckFlagResponseFromJSON(response).data.value;
803
+ }).catch((error) => {
804
+ console.error("There was a problem with the fetch operation:", error);
805
+ return fallback;
806
+ });
636
807
  }
637
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
638
- return fetch(requestUrl, {
639
- method: "POST",
640
- headers: {
641
- ...this.additionalHeaders ?? {},
642
- "Content-Type": "application/json;charset=UTF-8",
643
- "X-Schematic-Api-Key": this.apiKey
644
- },
645
- body: JSON.stringify(context)
646
- }).then((response) => {
808
+ try {
809
+ const existingVals = this.checks[contextStr];
810
+ if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
811
+ return existingVals[key].value;
812
+ }
813
+ try {
814
+ await this.setContext(context);
815
+ } catch (error) {
816
+ console.error(
817
+ "WebSocket connection failed, falling back to REST:",
818
+ error
819
+ );
820
+ return this.fallbackToRest(key, context, fallback);
821
+ }
822
+ const contextVals = this.checks[contextStr] ?? {};
823
+ return contextVals[key]?.value ?? fallback;
824
+ } catch (error) {
825
+ console.error("Unexpected error in checkFlag:", error);
826
+ return fallback;
827
+ }
828
+ }
829
+ /**
830
+ * Helper method for falling back to REST API when WebSocket connection fails
831
+ */
832
+ async fallbackToRest(key, context, fallback) {
833
+ try {
834
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
835
+ const response = await fetch(requestUrl, {
836
+ method: "POST",
837
+ headers: {
838
+ ...this.additionalHeaders ?? {},
839
+ "Content-Type": "application/json;charset=UTF-8",
840
+ "X-Schematic-Api-Key": this.apiKey
841
+ },
842
+ body: JSON.stringify(context)
843
+ });
647
844
  if (!response.ok) {
648
845
  throw new Error("Network response was not ok");
649
846
  }
650
- return response.json();
651
- }).then((data) => {
652
- return data.data.value;
653
- }).catch((error) => {
654
- console.error("There was a problem with the fetch operation:", error);
847
+ const data = CheckFlagResponseFromJSON(await response.json());
848
+ return data?.data?.value ?? false;
849
+ } catch (error) {
850
+ console.error("REST API call failed, using fallback value:", error);
655
851
  return fallback;
656
- });
852
+ }
657
853
  }
658
- // Make an API call to fetch all flag values for a given context (use if not in websocket mode)
854
+ /**
855
+ * Make an API call to fetch all flag values for a given context.
856
+ * Recommended for use in REST mode only.
857
+ */
659
858
  checkFlags = async (context) => {
660
859
  context = context || this.context;
661
860
  const requestUrl = `${this.apiUrl}/flags/check`;
@@ -673,8 +872,9 @@ var Schematic = class {
673
872
  throw new Error("Network response was not ok");
674
873
  }
675
874
  return response.json();
676
- }).then((data) => {
677
- return (data?.data?.flags ?? []).reduce(
875
+ }).then((responseJson) => {
876
+ const resp = CheckFlagsResponseFromJSON(responseJson);
877
+ return (resp?.data?.flags ?? []).reduce(
678
878
  (accum, flag) => {
679
879
  accum[flag.flag] = flag.value;
680
880
  return accum;
@@ -683,21 +883,33 @@ var Schematic = class {
683
883
  );
684
884
  }).catch((error) => {
685
885
  console.error("There was a problem with the fetch operation:", error);
686
- return false;
886
+ return {};
687
887
  });
688
888
  };
689
- // Send an identify event
889
+ /**
890
+ * Send an identify event.
891
+ * This will set the context for subsequent flag evaluation and events, and will also
892
+ * send an identify event to the Schematic API which will upsert a user and company.
893
+ */
690
894
  identify = (body) => {
691
- this.setContext({
692
- company: body.company?.keys,
693
- user: body.keys
694
- });
895
+ try {
896
+ this.setContext({
897
+ company: body.company?.keys,
898
+ user: body.keys
899
+ });
900
+ } catch (error) {
901
+ console.error("Error setting context:", error);
902
+ }
695
903
  return this.handleEvent("identify", body);
696
904
  };
697
- // Set the flag evaluation context; if the context has changed,
698
- // this will open a websocket connection (if not already open)
699
- // and submit this context. The promise will resolve when the
700
- // websocket sends back an initial set of flag values.
905
+ /**
906
+ * Set the flag evaluation context.
907
+ * In WebSocket mode, this will:
908
+ * 1. Open a websocket connection if not already open
909
+ * 2. Send the context to the server
910
+ * 3. Wait for initial flag values to be returned
911
+ * The promise resolves when initial flag values are received.
912
+ */
701
913
  setContext = async (context) => {
702
914
  if (!this.useWebSocket) {
703
915
  this.context = context;
@@ -711,10 +923,14 @@ var Schematic = class {
711
923
  const socket = await this.conn;
712
924
  await this.wsSendMessage(socket, context);
713
925
  } catch (error) {
714
- console.error("Error setting Schematic context:", error);
926
+ console.error("Failed to establish WebSocket connection:", error);
927
+ throw error;
715
928
  }
716
929
  };
717
- // Send track event
930
+ /**
931
+ * Send a track event
932
+ * Track usage for a company and/or user.
933
+ */
718
934
  track = (body) => {
719
935
  const { company, user, event, traits } = body;
720
936
  return this.handleEvent("track", {
@@ -786,6 +1002,9 @@ var Schematic = class {
786
1002
  /**
787
1003
  * Websocket management
788
1004
  */
1005
+ /**
1006
+ * If using websocket mode, close the connection when done.
1007
+ */
789
1008
  cleanup = async () => {
790
1009
  if (this.conn) {
791
1010
  try {
@@ -826,18 +1045,15 @@ var Schematic = class {
826
1045
  let resolved = false;
827
1046
  const messageHandler = (event) => {
828
1047
  const message = JSON.parse(event.data);
829
- if (!(contextString(context) in this.values)) {
830
- this.values[contextString(context)] = {};
831
- }
832
- (message.flags ?? []).forEach(
833
- (flag) => {
834
- this.values[contextString(context)][flag.flag] = flag.value;
835
- this.notifyFlagValueListeners(flag.flag, flag.value);
836
- }
837
- );
838
- if (this.flagListener) {
839
- this.flagListener(this.getFlagValues());
1048
+ if (!(contextString(context) in this.checks)) {
1049
+ this.checks[contextString(context)] = {};
840
1050
  }
1051
+ (message.flags ?? []).forEach((flag) => {
1052
+ const flagCheck = CheckFlagReturnFromJSON(flag);
1053
+ this.checks[contextString(context)][flagCheck.flag] = flagCheck;
1054
+ this.notifyFlagCheckListeners(flag.flag, flagCheck);
1055
+ this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1056
+ });
841
1057
  this.setIsPending(false);
842
1058
  if (!resolved) {
843
1059
  resolved = true;
@@ -877,18 +1093,21 @@ var Schematic = class {
877
1093
  setIsPending = (isPending) => {
878
1094
  this.isPending = isPending;
879
1095
  this.isPendingListeners.forEach(
880
- (listener) => notifyListener(listener, isPending)
1096
+ (listener) => notifyPendingListener(listener, isPending)
881
1097
  );
882
1098
  };
1099
+ // flag checks state
1100
+ getFlagCheck = (flagKey) => {
1101
+ const contextStr = contextString(this.context);
1102
+ const checks = this.checks[contextStr] ?? {};
1103
+ return checks[flagKey];
1104
+ };
883
1105
  // flagValues state
884
1106
  getFlagValue = (flagKey) => {
885
- const values = this.getFlagValues();
886
- return values[flagKey];
887
- };
888
- getFlagValues = () => {
889
- const contextStr = contextString(this.context);
890
- return this.values[contextStr] ?? {};
1107
+ const check = this.getFlagCheck(flagKey);
1108
+ return check?.value;
891
1109
  };
1110
+ /** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
892
1111
  addFlagValueListener = (flagKey, listener) => {
893
1112
  if (!(flagKey in this.flagValueListeners)) {
894
1113
  this.flagValueListeners[flagKey] = /* @__PURE__ */ new Set();
@@ -898,12 +1117,40 @@ var Schematic = class {
898
1117
  this.flagValueListeners[flagKey].delete(listener);
899
1118
  };
900
1119
  };
1120
+ /** Register an event listener that will be notified with the full flag check response for a given flag whenever this value changes */
1121
+ addFlagCheckListener = (flagKey, listener) => {
1122
+ if (!(flagKey in this.flagCheckListeners)) {
1123
+ this.flagCheckListeners[flagKey] = /* @__PURE__ */ new Set();
1124
+ }
1125
+ this.flagCheckListeners[flagKey].add(listener);
1126
+ return () => {
1127
+ this.flagCheckListeners[flagKey].delete(listener);
1128
+ };
1129
+ };
1130
+ notifyFlagCheckListeners = (flagKey, check) => {
1131
+ const listeners = this.flagCheckListeners?.[flagKey] ?? [];
1132
+ listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
1133
+ };
901
1134
  notifyFlagValueListeners = (flagKey, value) => {
902
1135
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
903
- listeners.forEach((listener) => notifyListener(listener, value));
1136
+ listeners.forEach((listener) => notifyFlagValueListener(listener, value));
904
1137
  };
905
1138
  };
906
- var notifyListener = (listener, value) => {
1139
+ var notifyPendingListener = (listener, value) => {
1140
+ if (listener.length > 0) {
1141
+ listener(value);
1142
+ } else {
1143
+ listener();
1144
+ }
1145
+ };
1146
+ var notifyFlagCheckListener = (listener, value) => {
1147
+ if (listener.length > 0) {
1148
+ listener(value);
1149
+ } else {
1150
+ listener();
1151
+ }
1152
+ };
1153
+ var notifyFlagValueListener = (listener, value) => {
907
1154
  if (listener.length > 0) {
908
1155
  listener(value);
909
1156
  } else {
@@ -912,7 +1159,7 @@ var notifyListener = (listener, value) => {
912
1159
  };
913
1160
 
914
1161
  // src/context/schematic.tsx
915
- import React, { createContext, useEffect, useMemo } from "react";
1162
+ import React, { createContext, useEffect, useMemo, useRef } from "react";
916
1163
  import { jsx } from "react/jsx-runtime";
917
1164
  var SchematicContext = createContext(
918
1165
  null
@@ -923,16 +1170,19 @@ var SchematicProvider = ({
923
1170
  publishableKey,
924
1171
  ...clientOpts
925
1172
  }) => {
1173
+ const initialOptsRef = useRef({
1174
+ publishableKey,
1175
+ useWebSocket: clientOpts.useWebSocket ?? true,
1176
+ ...clientOpts
1177
+ });
926
1178
  const client = useMemo(() => {
927
- const { useWebSocket = true } = clientOpts;
928
1179
  if (providedClient) {
929
1180
  return providedClient;
930
1181
  }
931
- return new Schematic(publishableKey, {
932
- useWebSocket,
933
- ...clientOpts
1182
+ return new Schematic(initialOptsRef.current.publishableKey, {
1183
+ ...initialOptsRef.current
934
1184
  });
935
- }, [providedClient, publishableKey, clientOpts]);
1185
+ }, [providedClient]);
936
1186
  useEffect(() => {
937
1187
  return () => {
938
1188
  if (!providedClient) {
@@ -1004,6 +1254,27 @@ var useSchematicFlag = (key, opts) => {
1004
1254
  }, [client, key, fallback]);
1005
1255
  return useSyncExternalStore(subscribe, getSnapshot);
1006
1256
  };
1257
+ var useSchematicFlagCheck = (key, opts) => {
1258
+ const client = useSchematicClient(opts);
1259
+ const fallback = opts?.fallback ?? false;
1260
+ const fallbackCheck = useMemo2(
1261
+ () => ({
1262
+ flag: key,
1263
+ reason: "Fallback",
1264
+ value: fallback
1265
+ }),
1266
+ [key, fallback]
1267
+ );
1268
+ const subscribe = useCallback(
1269
+ (callback) => client.addFlagCheckListener(key, callback),
1270
+ [client, key]
1271
+ );
1272
+ const getSnapshot = useCallback(() => {
1273
+ const check = client.getFlagCheck(key);
1274
+ return check ?? fallbackCheck;
1275
+ }, [client, key, fallbackCheck]);
1276
+ return useSyncExternalStore(subscribe, getSnapshot);
1277
+ };
1007
1278
  var useSchematicIsPending = (opts) => {
1008
1279
  const client = useSchematicClient(opts);
1009
1280
  const subscribe = useCallback(
@@ -1014,12 +1285,15 @@ var useSchematicIsPending = (opts) => {
1014
1285
  return useSyncExternalStore(subscribe, getSnapshot);
1015
1286
  };
1016
1287
  export {
1288
+ RuleType,
1017
1289
  Schematic,
1018
1290
  SchematicProvider,
1291
+ UsagePeriod,
1019
1292
  useSchematic,
1020
1293
  useSchematicContext,
1021
1294
  useSchematicEvents,
1022
1295
  useSchematicFlag,
1296
+ useSchematicFlagCheck,
1023
1297
  useSchematicIsPending
1024
1298
  };
1025
1299
  /*! Bundled license information: