@schematichq/schematic-react 1.0.2 → 1.0.3

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.
@@ -667,37 +667,89 @@ var Schematic = class {
667
667
  });
668
668
  }
669
669
  }
670
- // Get value for a single flag
671
- // If in websocket mode, return the local value, otherwise make an API call
670
+ /**
671
+ * Get value for a single flag.
672
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
673
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
674
+ * connection fails.
675
+ * In REST mode, makes an API call for each check.
676
+ */
672
677
  async checkFlag(options) {
673
678
  const { fallback = false, key } = options;
674
679
  const context = options.context || this.context;
675
- if (this.useWebSocket) {
676
- const contextVals = this.values[contextString(context)] ?? {};
680
+ const contextStr = contextString(context);
681
+ if (!this.useWebSocket) {
682
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
683
+ return fetch(requestUrl, {
684
+ method: "POST",
685
+ headers: {
686
+ ...this.additionalHeaders ?? {},
687
+ "Content-Type": "application/json;charset=UTF-8",
688
+ "X-Schematic-Api-Key": this.apiKey
689
+ },
690
+ body: JSON.stringify(context)
691
+ }).then((response) => {
692
+ if (!response.ok) {
693
+ throw new Error("Network response was not ok");
694
+ }
695
+ return response.json();
696
+ }).then((data) => {
697
+ return data.data.value;
698
+ }).catch((error) => {
699
+ console.error("There was a problem with the fetch operation:", error);
700
+ return fallback;
701
+ });
702
+ }
703
+ try {
704
+ const existingVals = this.values[contextStr];
705
+ if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
706
+ return existingVals[key];
707
+ }
708
+ try {
709
+ await this.setContext(context);
710
+ } catch (error) {
711
+ console.error(
712
+ "WebSocket connection failed, falling back to REST:",
713
+ error
714
+ );
715
+ return this.fallbackToRest(key, context, fallback);
716
+ }
717
+ const contextVals = this.values[contextStr] ?? {};
677
718
  return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
719
+ } catch (error) {
720
+ console.error("Unexpected error in checkFlag:", error);
721
+ return fallback;
678
722
  }
679
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
680
- return fetch(requestUrl, {
681
- method: "POST",
682
- headers: {
683
- ...this.additionalHeaders ?? {},
684
- "Content-Type": "application/json;charset=UTF-8",
685
- "X-Schematic-Api-Key": this.apiKey
686
- },
687
- body: JSON.stringify(context)
688
- }).then((response) => {
723
+ }
724
+ /**
725
+ * Helper method for falling back to REST API when WebSocket connection fails
726
+ */
727
+ async fallbackToRest(key, context, fallback) {
728
+ try {
729
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
730
+ const response = await fetch(requestUrl, {
731
+ method: "POST",
732
+ headers: {
733
+ ...this.additionalHeaders ?? {},
734
+ "Content-Type": "application/json;charset=UTF-8",
735
+ "X-Schematic-Api-Key": this.apiKey
736
+ },
737
+ body: JSON.stringify(context)
738
+ });
689
739
  if (!response.ok) {
690
740
  throw new Error("Network response was not ok");
691
741
  }
692
- return response.json();
693
- }).then((data) => {
742
+ const data = await response.json();
694
743
  return data.data.value;
695
- }).catch((error) => {
696
- console.error("There was a problem with the fetch operation:", error);
744
+ } catch (error) {
745
+ console.error("REST API call failed, using fallback value:", error);
697
746
  return fallback;
698
- });
747
+ }
699
748
  }
700
- // Make an API call to fetch all flag values for a given context (use if not in websocket mode)
749
+ /**
750
+ * Make an API call to fetch all flag values for a given context.
751
+ * Recommended for use in REST mode only.
752
+ */
701
753
  checkFlags = async (context) => {
702
754
  context = context || this.context;
703
755
  const requestUrl = `${this.apiUrl}/flags/check`;
@@ -728,18 +780,30 @@ var Schematic = class {
728
780
  return false;
729
781
  });
730
782
  };
731
- // Send an identify event
783
+ /**
784
+ * Send an identify event.
785
+ * This will set the context for subsequent flag evaluation and events, and will also
786
+ * send an identify event to the Schematic API which will upsert a user and company.
787
+ */
732
788
  identify = (body) => {
733
- this.setContext({
734
- company: body.company?.keys,
735
- user: body.keys
736
- });
789
+ try {
790
+ this.setContext({
791
+ company: body.company?.keys,
792
+ user: body.keys
793
+ });
794
+ } catch (error) {
795
+ console.error("Error setting context:", error);
796
+ }
737
797
  return this.handleEvent("identify", body);
738
798
  };
739
- // Set the flag evaluation context; if the context has changed,
740
- // this will open a websocket connection (if not already open)
741
- // and submit this context. The promise will resolve when the
742
- // websocket sends back an initial set of flag values.
799
+ /**
800
+ * Set the flag evaluation context.
801
+ * In WebSocket mode, this will:
802
+ * 1. Open a websocket connection if not already open
803
+ * 2. Send the context to the server
804
+ * 3. Wait for initial flag values to be returned
805
+ * The promise resolves when initial flag values are received.
806
+ */
743
807
  setContext = async (context) => {
744
808
  if (!this.useWebSocket) {
745
809
  this.context = context;
@@ -753,10 +817,14 @@ var Schematic = class {
753
817
  const socket = await this.conn;
754
818
  await this.wsSendMessage(socket, context);
755
819
  } catch (error) {
756
- console.error("Error setting Schematic context:", error);
820
+ console.error("Failed to establish WebSocket connection:", error);
821
+ throw error;
757
822
  }
758
823
  };
759
- // Send track event
824
+ /**
825
+ * Send a track event
826
+ * Track usage for a company and/or user.
827
+ */
760
828
  track = (body) => {
761
829
  const { company, user, event, traits } = body;
762
830
  return this.handleEvent("track", {
@@ -828,6 +896,9 @@ var Schematic = class {
828
896
  /**
829
897
  * Websocket management
830
898
  */
899
+ /**
900
+ * If using websocket mode, close the connection when done.
901
+ */
831
902
  cleanup = async () => {
832
903
  if (this.conn) {
833
904
  try {
@@ -965,16 +1036,19 @@ var SchematicProvider = ({
965
1036
  publishableKey,
966
1037
  ...clientOpts
967
1038
  }) => {
1039
+ const initialOptsRef = (0, import_react.useRef)({
1040
+ publishableKey,
1041
+ useWebSocket: clientOpts.useWebSocket ?? true,
1042
+ ...clientOpts
1043
+ });
968
1044
  const client = (0, import_react.useMemo)(() => {
969
- const { useWebSocket = true } = clientOpts;
970
1045
  if (providedClient) {
971
1046
  return providedClient;
972
1047
  }
973
- return new Schematic(publishableKey, {
974
- useWebSocket,
975
- ...clientOpts
1048
+ return new Schematic(initialOptsRef.current.publishableKey, {
1049
+ ...initialOptsRef.current
976
1050
  });
977
- }, [providedClient, publishableKey, clientOpts]);
1051
+ }, [providedClient]);
978
1052
  (0, import_react.useEffect)(() => {
979
1053
  return () => {
980
1054
  if (!providedClient) {
@@ -625,37 +625,89 @@ var Schematic = class {
625
625
  });
626
626
  }
627
627
  }
628
- // Get value for a single flag
629
- // If in websocket mode, return the local value, otherwise make an API call
628
+ /**
629
+ * Get value for a single flag.
630
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
631
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
632
+ * connection fails.
633
+ * In REST mode, makes an API call for each check.
634
+ */
630
635
  async checkFlag(options) {
631
636
  const { fallback = false, key } = options;
632
637
  const context = options.context || this.context;
633
- if (this.useWebSocket) {
634
- const contextVals = this.values[contextString(context)] ?? {};
638
+ const contextStr = contextString(context);
639
+ if (!this.useWebSocket) {
640
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
641
+ return fetch(requestUrl, {
642
+ method: "POST",
643
+ headers: {
644
+ ...this.additionalHeaders ?? {},
645
+ "Content-Type": "application/json;charset=UTF-8",
646
+ "X-Schematic-Api-Key": this.apiKey
647
+ },
648
+ body: JSON.stringify(context)
649
+ }).then((response) => {
650
+ if (!response.ok) {
651
+ throw new Error("Network response was not ok");
652
+ }
653
+ return response.json();
654
+ }).then((data) => {
655
+ return data.data.value;
656
+ }).catch((error) => {
657
+ console.error("There was a problem with the fetch operation:", error);
658
+ return fallback;
659
+ });
660
+ }
661
+ try {
662
+ const existingVals = this.values[contextStr];
663
+ if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
664
+ return existingVals[key];
665
+ }
666
+ try {
667
+ await this.setContext(context);
668
+ } catch (error) {
669
+ console.error(
670
+ "WebSocket connection failed, falling back to REST:",
671
+ error
672
+ );
673
+ return this.fallbackToRest(key, context, fallback);
674
+ }
675
+ const contextVals = this.values[contextStr] ?? {};
635
676
  return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
677
+ } catch (error) {
678
+ console.error("Unexpected error in checkFlag:", error);
679
+ return fallback;
636
680
  }
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) => {
681
+ }
682
+ /**
683
+ * Helper method for falling back to REST API when WebSocket connection fails
684
+ */
685
+ async fallbackToRest(key, context, fallback) {
686
+ try {
687
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
688
+ const response = await fetch(requestUrl, {
689
+ method: "POST",
690
+ headers: {
691
+ ...this.additionalHeaders ?? {},
692
+ "Content-Type": "application/json;charset=UTF-8",
693
+ "X-Schematic-Api-Key": this.apiKey
694
+ },
695
+ body: JSON.stringify(context)
696
+ });
647
697
  if (!response.ok) {
648
698
  throw new Error("Network response was not ok");
649
699
  }
650
- return response.json();
651
- }).then((data) => {
700
+ const data = await response.json();
652
701
  return data.data.value;
653
- }).catch((error) => {
654
- console.error("There was a problem with the fetch operation:", error);
702
+ } catch (error) {
703
+ console.error("REST API call failed, using fallback value:", error);
655
704
  return fallback;
656
- });
705
+ }
657
706
  }
658
- // Make an API call to fetch all flag values for a given context (use if not in websocket mode)
707
+ /**
708
+ * Make an API call to fetch all flag values for a given context.
709
+ * Recommended for use in REST mode only.
710
+ */
659
711
  checkFlags = async (context) => {
660
712
  context = context || this.context;
661
713
  const requestUrl = `${this.apiUrl}/flags/check`;
@@ -686,18 +738,30 @@ var Schematic = class {
686
738
  return false;
687
739
  });
688
740
  };
689
- // Send an identify event
741
+ /**
742
+ * Send an identify event.
743
+ * This will set the context for subsequent flag evaluation and events, and will also
744
+ * send an identify event to the Schematic API which will upsert a user and company.
745
+ */
690
746
  identify = (body) => {
691
- this.setContext({
692
- company: body.company?.keys,
693
- user: body.keys
694
- });
747
+ try {
748
+ this.setContext({
749
+ company: body.company?.keys,
750
+ user: body.keys
751
+ });
752
+ } catch (error) {
753
+ console.error("Error setting context:", error);
754
+ }
695
755
  return this.handleEvent("identify", body);
696
756
  };
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.
757
+ /**
758
+ * Set the flag evaluation context.
759
+ * In WebSocket mode, this will:
760
+ * 1. Open a websocket connection if not already open
761
+ * 2. Send the context to the server
762
+ * 3. Wait for initial flag values to be returned
763
+ * The promise resolves when initial flag values are received.
764
+ */
701
765
  setContext = async (context) => {
702
766
  if (!this.useWebSocket) {
703
767
  this.context = context;
@@ -711,10 +775,14 @@ var Schematic = class {
711
775
  const socket = await this.conn;
712
776
  await this.wsSendMessage(socket, context);
713
777
  } catch (error) {
714
- console.error("Error setting Schematic context:", error);
778
+ console.error("Failed to establish WebSocket connection:", error);
779
+ throw error;
715
780
  }
716
781
  };
717
- // Send track event
782
+ /**
783
+ * Send a track event
784
+ * Track usage for a company and/or user.
785
+ */
718
786
  track = (body) => {
719
787
  const { company, user, event, traits } = body;
720
788
  return this.handleEvent("track", {
@@ -786,6 +854,9 @@ var Schematic = class {
786
854
  /**
787
855
  * Websocket management
788
856
  */
857
+ /**
858
+ * If using websocket mode, close the connection when done.
859
+ */
789
860
  cleanup = async () => {
790
861
  if (this.conn) {
791
862
  try {
@@ -912,7 +983,7 @@ var notifyListener = (listener, value) => {
912
983
  };
913
984
 
914
985
  // src/context/schematic.tsx
915
- import React, { createContext, useEffect, useMemo } from "react";
986
+ import React, { createContext, useEffect, useMemo, useRef } from "react";
916
987
  import { jsx } from "react/jsx-runtime";
917
988
  var SchematicContext = createContext(
918
989
  null
@@ -923,16 +994,19 @@ var SchematicProvider = ({
923
994
  publishableKey,
924
995
  ...clientOpts
925
996
  }) => {
997
+ const initialOptsRef = useRef({
998
+ publishableKey,
999
+ useWebSocket: clientOpts.useWebSocket ?? true,
1000
+ ...clientOpts
1001
+ });
926
1002
  const client = useMemo(() => {
927
- const { useWebSocket = true } = clientOpts;
928
1003
  if (providedClient) {
929
1004
  return providedClient;
930
1005
  }
931
- return new Schematic(publishableKey, {
932
- useWebSocket,
933
- ...clientOpts
1006
+ return new Schematic(initialOptsRef.current.publishableKey, {
1007
+ ...initialOptsRef.current
934
1008
  });
935
- }, [providedClient, publishableKey, clientOpts]);
1009
+ }, [providedClient]);
936
1010
  useEffect(() => {
937
1011
  return () => {
938
1012
  if (!providedClient) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-react",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "main": "dist/schematic-react.cjs.js",
5
5
  "module": "dist/schematic-react.esm.js",
6
6
  "types": "dist/schematic-react.d.ts",
@@ -40,7 +40,7 @@
40
40
  "esbuild-jest": "^0.5.0",
41
41
  "eslint": "^8.57.1",
42
42
  "eslint-plugin-import": "^2.30.0",
43
- "eslint-plugin-react-hooks": "^4.6.2",
43
+ "eslint-plugin-react-hooks": "^5.0.0",
44
44
  "jest": "^29.7.0",
45
45
  "jest-environment-jsdom": "^29.7.0",
46
46
  "jest-esbuild": "^0.3.0",