@schematichq/schematic-react 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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",