@sendoracloud/sdk-react-native 0.18.3 → 0.18.5

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/dist/index.cjs CHANGED
@@ -654,6 +654,29 @@ var Auth = class {
654
654
  }
655
655
  return { authorizationUrl: parsed.data.authorizationUrl };
656
656
  }
657
+ /**
658
+ * Kick off a SAML 2.0 SSO flow. Mirrors `startSso` for SAML —
659
+ * backend POST `/auth-service/sso/saml/start` returns the IdP
660
+ * AuthnRequest URL. Caller opens it with
661
+ * ASWebAuthenticationSession (iOS) / Custom Tabs (Android) /
662
+ * Linking, captures the callback URL via the standard
663
+ * `Linking.addEventListener` flow, then passes the captured URL to
664
+ * `consumeSsoFragment(url)`. consumeSsoFragment handles both OIDC
665
+ * and SAML fragments transparently.
666
+ */
667
+ async startSaml(returnTo) {
668
+ const res = await post(
669
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
670
+ "/api/v1/auth-service/sso/saml/start",
671
+ { returnTo }
672
+ );
673
+ if (!res || !res.ok) throw new AuthError("SAML_START_FAILED", `HTTP ${res?.status ?? "network"}`);
674
+ const parsed = await res.json();
675
+ if (!parsed.success || !parsed.data?.authorizationUrl) {
676
+ throw new AuthError("SAML_START_FAILED", "Missing authorizationUrl");
677
+ }
678
+ return { authorizationUrl: parsed.data.authorizationUrl };
679
+ }
657
680
  /**
658
681
  * Consume an OIDC callback URL captured via React Native's
659
682
  * `Linking.addEventListener('url', ...)`. Reads the fragment for
@@ -666,10 +689,10 @@ var Auth = class {
666
689
  const hashIdx = callbackUrl.indexOf("#");
667
690
  const fragment = hashIdx >= 0 ? callbackUrl.slice(hashIdx + 1) : "";
668
691
  const params = new URLSearchParams(fragment);
669
- const err = params.get("sendora_oidc_error");
692
+ const err = params.get("sendora_oidc_error") ?? params.get("sendora_saml_error");
670
693
  if (err) throw new AuthError("SSO_FAILED", decodeURIComponent(err));
671
- const refresh = params.get("sendora_oidc_token");
672
- if (!refresh) throw new AuthError("SSO_NO_TOKEN", "Callback URL has no sendora_oidc_token");
694
+ const refresh = params.get("sendora_oidc_token") ?? params.get("sendora_saml_token");
695
+ if (!refresh) throw new AuthError("SSO_NO_TOKEN", "Callback URL has no SSO token");
673
696
  const res = await post(
674
697
  { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
675
698
  "/api/v1/auth-service/token/refresh",
@@ -2020,6 +2043,57 @@ var SendoraSDK = class {
2020
2043
  }
2021
2044
  };
2022
2045
  }
2046
+ /**
2047
+ * Surveys namespace — fetch + submit NPS / CSAT / custom surveys
2048
+ * from inside the app. Mirrors backend `/surveys/:id/config` +
2049
+ * `/surveys/responses` SDK routes + the web SDK's
2050
+ * `sendora.surveys.showSurvey()` + `submitResponse()` shape.
2051
+ *
2052
+ * Typical use:
2053
+ * const survey = await SendoraCloud.surveys.fetchConfig("survey_nps_2026");
2054
+ * if (survey) {
2055
+ * // render survey.questions[] in your UI
2056
+ * await SendoraCloud.surveys.submitResponse({
2057
+ * surveyId: survey.id,
2058
+ * answers: { q1: 9, q2: "Loving it" },
2059
+ * completed: true,
2060
+ * });
2061
+ * }
2062
+ *
2063
+ * fetchConfig returns null when the survey is inactive / not found
2064
+ * (backend returns 404; SDK normalizes to null so callers can `if (!survey) return`).
2065
+ */
2066
+ get surveys() {
2067
+ const self = this;
2068
+ return {
2069
+ fetchConfig: async (surveyId) => {
2070
+ if (!self.config) throw new Error("[sendoracloud] init() must complete before surveys.fetchConfig().");
2071
+ if (typeof surveyId !== "string" || surveyId.length === 0) return null;
2072
+ const url = `${self.config.apiUrl}/api/v1/surveys/${encodeURIComponent(surveyId)}/config`;
2073
+ const res = await fetch(url, {
2074
+ method: "GET",
2075
+ headers: { "x-api-key": self.config.publicKey }
2076
+ });
2077
+ if (res.status === 404) return null;
2078
+ if (!res.ok) throw new Error(`[sendoracloud] surveys.fetchConfig failed (${res.status})`);
2079
+ const parsed = await res.json();
2080
+ if (!parsed.success || !parsed.data) return null;
2081
+ return parsed.data;
2082
+ },
2083
+ submitResponse: async (input) => {
2084
+ if (!self.config) throw new Error("[sendoracloud] init() must complete before surveys.submitResponse().");
2085
+ const res = await post(
2086
+ { apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
2087
+ "/surveys/responses",
2088
+ { completed: true, ...input }
2089
+ );
2090
+ if (!res || !res.ok) throw new Error(`[sendoracloud] surveys.submitResponse failed (${res?.status ?? "network"})`);
2091
+ const parsed = await res.json();
2092
+ if (!parsed.success || !parsed.data) throw new Error("[sendoracloud] surveys.submitResponse: bad response");
2093
+ return parsed.data;
2094
+ }
2095
+ };
2096
+ }
2023
2097
  /**
2024
2098
  * Clear identity + rotate the anonymous id. Call on logout. Awaits
2025
2099
  * the AsyncStorage writes so a caller who kills the app immediately
package/dist/index.d.cts CHANGED
@@ -268,6 +268,19 @@ declare class Auth {
268
268
  startSso(returnTo: string): Promise<{
269
269
  authorizationUrl: string;
270
270
  }>;
271
+ /**
272
+ * Kick off a SAML 2.0 SSO flow. Mirrors `startSso` for SAML —
273
+ * backend POST `/auth-service/sso/saml/start` returns the IdP
274
+ * AuthnRequest URL. Caller opens it with
275
+ * ASWebAuthenticationSession (iOS) / Custom Tabs (Android) /
276
+ * Linking, captures the callback URL via the standard
277
+ * `Linking.addEventListener` flow, then passes the captured URL to
278
+ * `consumeSsoFragment(url)`. consumeSsoFragment handles both OIDC
279
+ * and SAML fragments transparently.
280
+ */
281
+ startSaml(returnTo: string): Promise<{
282
+ authorizationUrl: string;
283
+ }>;
271
284
  /**
272
285
  * Consume an OIDC callback URL captured via React Native's
273
286
  * `Linking.addEventListener('url', ...)`. Reads the fragment for
@@ -934,6 +947,55 @@ declare class SendoraSDK {
934
947
  createdAt: string;
935
948
  }>;
936
949
  };
950
+ /**
951
+ * Surveys namespace — fetch + submit NPS / CSAT / custom surveys
952
+ * from inside the app. Mirrors backend `/surveys/:id/config` +
953
+ * `/surveys/responses` SDK routes + the web SDK's
954
+ * `sendora.surveys.showSurvey()` + `submitResponse()` shape.
955
+ *
956
+ * Typical use:
957
+ * const survey = await SendoraCloud.surveys.fetchConfig("survey_nps_2026");
958
+ * if (survey) {
959
+ * // render survey.questions[] in your UI
960
+ * await SendoraCloud.surveys.submitResponse({
961
+ * surveyId: survey.id,
962
+ * answers: { q1: 9, q2: "Loving it" },
963
+ * completed: true,
964
+ * });
965
+ * }
966
+ *
967
+ * fetchConfig returns null when the survey is inactive / not found
968
+ * (backend returns 404; SDK normalizes to null so callers can `if (!survey) return`).
969
+ */
970
+ get surveys(): {
971
+ fetchConfig: (surveyId: string) => Promise<{
972
+ id: string;
973
+ name: string;
974
+ questions: Array<{
975
+ id: string;
976
+ type: string;
977
+ label: string;
978
+ required?: boolean;
979
+ options?: string[];
980
+ scale?: {
981
+ min: number;
982
+ max: number;
983
+ };
984
+ }>;
985
+ [key: string]: unknown;
986
+ } | null>;
987
+ submitResponse: (input: {
988
+ surveyId: string;
989
+ answers: Record<string, unknown>;
990
+ completed?: boolean;
991
+ entityId?: string;
992
+ userId?: string;
993
+ metadata?: Record<string, unknown>;
994
+ }) => Promise<{
995
+ id: string;
996
+ submittedAt: string;
997
+ }>;
998
+ };
937
999
  /**
938
1000
  * Clear identity + rotate the anonymous id. Call on logout. Awaits
939
1001
  * the AsyncStorage writes so a caller who kills the app immediately
package/dist/index.d.ts CHANGED
@@ -268,6 +268,19 @@ declare class Auth {
268
268
  startSso(returnTo: string): Promise<{
269
269
  authorizationUrl: string;
270
270
  }>;
271
+ /**
272
+ * Kick off a SAML 2.0 SSO flow. Mirrors `startSso` for SAML —
273
+ * backend POST `/auth-service/sso/saml/start` returns the IdP
274
+ * AuthnRequest URL. Caller opens it with
275
+ * ASWebAuthenticationSession (iOS) / Custom Tabs (Android) /
276
+ * Linking, captures the callback URL via the standard
277
+ * `Linking.addEventListener` flow, then passes the captured URL to
278
+ * `consumeSsoFragment(url)`. consumeSsoFragment handles both OIDC
279
+ * and SAML fragments transparently.
280
+ */
281
+ startSaml(returnTo: string): Promise<{
282
+ authorizationUrl: string;
283
+ }>;
271
284
  /**
272
285
  * Consume an OIDC callback URL captured via React Native's
273
286
  * `Linking.addEventListener('url', ...)`. Reads the fragment for
@@ -934,6 +947,55 @@ declare class SendoraSDK {
934
947
  createdAt: string;
935
948
  }>;
936
949
  };
950
+ /**
951
+ * Surveys namespace — fetch + submit NPS / CSAT / custom surveys
952
+ * from inside the app. Mirrors backend `/surveys/:id/config` +
953
+ * `/surveys/responses` SDK routes + the web SDK's
954
+ * `sendora.surveys.showSurvey()` + `submitResponse()` shape.
955
+ *
956
+ * Typical use:
957
+ * const survey = await SendoraCloud.surveys.fetchConfig("survey_nps_2026");
958
+ * if (survey) {
959
+ * // render survey.questions[] in your UI
960
+ * await SendoraCloud.surveys.submitResponse({
961
+ * surveyId: survey.id,
962
+ * answers: { q1: 9, q2: "Loving it" },
963
+ * completed: true,
964
+ * });
965
+ * }
966
+ *
967
+ * fetchConfig returns null when the survey is inactive / not found
968
+ * (backend returns 404; SDK normalizes to null so callers can `if (!survey) return`).
969
+ */
970
+ get surveys(): {
971
+ fetchConfig: (surveyId: string) => Promise<{
972
+ id: string;
973
+ name: string;
974
+ questions: Array<{
975
+ id: string;
976
+ type: string;
977
+ label: string;
978
+ required?: boolean;
979
+ options?: string[];
980
+ scale?: {
981
+ min: number;
982
+ max: number;
983
+ };
984
+ }>;
985
+ [key: string]: unknown;
986
+ } | null>;
987
+ submitResponse: (input: {
988
+ surveyId: string;
989
+ answers: Record<string, unknown>;
990
+ completed?: boolean;
991
+ entityId?: string;
992
+ userId?: string;
993
+ metadata?: Record<string, unknown>;
994
+ }) => Promise<{
995
+ id: string;
996
+ submittedAt: string;
997
+ }>;
998
+ };
937
999
  /**
938
1000
  * Clear identity + rotate the anonymous id. Call on logout. Awaits
939
1001
  * the AsyncStorage writes so a caller who kills the app immediately
package/dist/index.js CHANGED
@@ -611,6 +611,29 @@ var Auth = class {
611
611
  }
612
612
  return { authorizationUrl: parsed.data.authorizationUrl };
613
613
  }
614
+ /**
615
+ * Kick off a SAML 2.0 SSO flow. Mirrors `startSso` for SAML —
616
+ * backend POST `/auth-service/sso/saml/start` returns the IdP
617
+ * AuthnRequest URL. Caller opens it with
618
+ * ASWebAuthenticationSession (iOS) / Custom Tabs (Android) /
619
+ * Linking, captures the callback URL via the standard
620
+ * `Linking.addEventListener` flow, then passes the captured URL to
621
+ * `consumeSsoFragment(url)`. consumeSsoFragment handles both OIDC
622
+ * and SAML fragments transparently.
623
+ */
624
+ async startSaml(returnTo) {
625
+ const res = await post(
626
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
627
+ "/api/v1/auth-service/sso/saml/start",
628
+ { returnTo }
629
+ );
630
+ if (!res || !res.ok) throw new AuthError("SAML_START_FAILED", `HTTP ${res?.status ?? "network"}`);
631
+ const parsed = await res.json();
632
+ if (!parsed.success || !parsed.data?.authorizationUrl) {
633
+ throw new AuthError("SAML_START_FAILED", "Missing authorizationUrl");
634
+ }
635
+ return { authorizationUrl: parsed.data.authorizationUrl };
636
+ }
614
637
  /**
615
638
  * Consume an OIDC callback URL captured via React Native's
616
639
  * `Linking.addEventListener('url', ...)`. Reads the fragment for
@@ -623,10 +646,10 @@ var Auth = class {
623
646
  const hashIdx = callbackUrl.indexOf("#");
624
647
  const fragment = hashIdx >= 0 ? callbackUrl.slice(hashIdx + 1) : "";
625
648
  const params = new URLSearchParams(fragment);
626
- const err = params.get("sendora_oidc_error");
649
+ const err = params.get("sendora_oidc_error") ?? params.get("sendora_saml_error");
627
650
  if (err) throw new AuthError("SSO_FAILED", decodeURIComponent(err));
628
- const refresh = params.get("sendora_oidc_token");
629
- if (!refresh) throw new AuthError("SSO_NO_TOKEN", "Callback URL has no sendora_oidc_token");
651
+ const refresh = params.get("sendora_oidc_token") ?? params.get("sendora_saml_token");
652
+ if (!refresh) throw new AuthError("SSO_NO_TOKEN", "Callback URL has no SSO token");
630
653
  const res = await post(
631
654
  { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
632
655
  "/api/v1/auth-service/token/refresh",
@@ -1982,6 +2005,57 @@ var SendoraSDK = class {
1982
2005
  }
1983
2006
  };
1984
2007
  }
2008
+ /**
2009
+ * Surveys namespace — fetch + submit NPS / CSAT / custom surveys
2010
+ * from inside the app. Mirrors backend `/surveys/:id/config` +
2011
+ * `/surveys/responses` SDK routes + the web SDK's
2012
+ * `sendora.surveys.showSurvey()` + `submitResponse()` shape.
2013
+ *
2014
+ * Typical use:
2015
+ * const survey = await SendoraCloud.surveys.fetchConfig("survey_nps_2026");
2016
+ * if (survey) {
2017
+ * // render survey.questions[] in your UI
2018
+ * await SendoraCloud.surveys.submitResponse({
2019
+ * surveyId: survey.id,
2020
+ * answers: { q1: 9, q2: "Loving it" },
2021
+ * completed: true,
2022
+ * });
2023
+ * }
2024
+ *
2025
+ * fetchConfig returns null when the survey is inactive / not found
2026
+ * (backend returns 404; SDK normalizes to null so callers can `if (!survey) return`).
2027
+ */
2028
+ get surveys() {
2029
+ const self = this;
2030
+ return {
2031
+ fetchConfig: async (surveyId) => {
2032
+ if (!self.config) throw new Error("[sendoracloud] init() must complete before surveys.fetchConfig().");
2033
+ if (typeof surveyId !== "string" || surveyId.length === 0) return null;
2034
+ const url = `${self.config.apiUrl}/api/v1/surveys/${encodeURIComponent(surveyId)}/config`;
2035
+ const res = await fetch(url, {
2036
+ method: "GET",
2037
+ headers: { "x-api-key": self.config.publicKey }
2038
+ });
2039
+ if (res.status === 404) return null;
2040
+ if (!res.ok) throw new Error(`[sendoracloud] surveys.fetchConfig failed (${res.status})`);
2041
+ const parsed = await res.json();
2042
+ if (!parsed.success || !parsed.data) return null;
2043
+ return parsed.data;
2044
+ },
2045
+ submitResponse: async (input) => {
2046
+ if (!self.config) throw new Error("[sendoracloud] init() must complete before surveys.submitResponse().");
2047
+ const res = await post(
2048
+ { apiUrl: self.config.apiUrl, publicKey: self.config.publicKey, debug: self.debug },
2049
+ "/surveys/responses",
2050
+ { completed: true, ...input }
2051
+ );
2052
+ if (!res || !res.ok) throw new Error(`[sendoracloud] surveys.submitResponse failed (${res?.status ?? "network"})`);
2053
+ const parsed = await res.json();
2054
+ if (!parsed.success || !parsed.data) throw new Error("[sendoracloud] surveys.submitResponse: bad response");
2055
+ return parsed.data;
2056
+ }
2057
+ };
2058
+ }
1985
2059
  /**
1986
2060
  * Clear identity + rotate the anonymous id. Call on logout. Awaits
1987
2061
  * the AsyncStorage writes so a caller who kills the app immediately
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendoracloud/sdk-react-native",
3
- "version": "0.18.3",
3
+ "version": "0.18.5",
4
4
  "description": "Sendora Cloud React Native + Expo SDK — analytics, identity, push-token registration, auth, deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push token registration requires a Dev Client (EAS Build or `npx expo prebuild`).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",