@sendoracloud/sdk-react-native 1.3.0 → 1.5.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.
@@ -31,7 +31,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var contact_widget_exports = {};
32
32
  __export(contact_widget_exports, {
33
33
  ContactWidget: () => ContactWidget,
34
- useContactWidget: () => useContactWidget
34
+ TicketHistory: () => TicketHistory,
35
+ useContactWidget: () => useContactWidget,
36
+ useTicketHistory: () => useTicketHistory
35
37
  });
36
38
  module.exports = __toCommonJS(contact_widget_exports);
37
39
  var React = __toESM(require("react"), 1);
@@ -134,8 +136,87 @@ function useContactWidget(widgetId, defaults) {
134
136
  );
135
137
  return { open, close, View: View2, lastResult };
136
138
  }
139
+ function TicketHistory({
140
+ visible,
141
+ widgetId,
142
+ accessToken,
143
+ workerHost = "https://go.sendoracloud.com",
144
+ theme = "auto",
145
+ onClose
146
+ }) {
147
+ const didFire = React.useRef(false);
148
+ React.useEffect(() => {
149
+ if (visible) didFire.current = false;
150
+ }, [visible]);
151
+ if (!widgetId || !accessToken) return null;
152
+ const params = new URLSearchParams({ widgetId, theme });
153
+ const uri = `${workerHost}/embed/tickets?${params.toString()}#access=${encodeURIComponent(accessToken)}`;
154
+ const handleClose = (result) => {
155
+ if (didFire.current) return;
156
+ didFire.current = true;
157
+ onClose(result);
158
+ };
159
+ const onNav = (req) => {
160
+ const url = req.url || "";
161
+ if (url.startsWith("sendora://close")) {
162
+ handleClose({ ticketId: "", portalUrl: null, submitted: false });
163
+ return false;
164
+ }
165
+ if (!url.startsWith("https://") && !url.startsWith("about:")) return false;
166
+ return true;
167
+ };
168
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
169
+ import_react_native.Modal,
170
+ {
171
+ visible,
172
+ animationType: "slide",
173
+ presentationStyle: "pageSheet",
174
+ onRequestClose: () => handleClose({ ticketId: "", portalUrl: null, submitted: false }),
175
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
176
+ import_react_native_webview.WebView,
177
+ {
178
+ source: { uri },
179
+ onShouldStartLoadWithRequest: onNav,
180
+ originWhitelist: ["https://*", "about:*", "sendora://*"],
181
+ setSupportMultipleWindows: false,
182
+ javaScriptEnabled: true,
183
+ domStorageEnabled: true,
184
+ allowsBackForwardNavigationGestures: false
185
+ }
186
+ ) })
187
+ }
188
+ );
189
+ }
190
+ function useTicketHistory(widgetId, getAccessToken, defaults) {
191
+ const [visible, setVisible] = React.useState(false);
192
+ const [token, setToken] = React.useState(null);
193
+ const open = React.useCallback(async () => {
194
+ const t = await getAccessToken();
195
+ if (!t) return;
196
+ setToken(t);
197
+ setVisible(true);
198
+ }, [getAccessToken]);
199
+ const close = React.useCallback(() => setVisible(false), []);
200
+ const View2 = React.useCallback(
201
+ () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
202
+ TicketHistory,
203
+ {
204
+ widgetId,
205
+ visible,
206
+ accessToken: token,
207
+ workerHost: defaults?.workerHost,
208
+ theme: defaults?.theme,
209
+ onClose: () => setVisible(false)
210
+ }
211
+ ),
212
+ [widgetId, visible, token, defaults]
213
+ );
214
+ return { open, close, View: View2 };
215
+ }
137
216
  // Annotate the CommonJS export names for ESM import in node:
138
217
  0 && (module.exports = {
139
218
  ContactWidget,
140
- useContactWidget
219
+ TicketHistory,
220
+ useContactWidget,
221
+ useTicketHistory
141
222
  });
@@ -99,5 +99,40 @@ declare function useContactWidget(widgetId: string, defaults?: Omit<ContactWidge
99
99
  View: () => React.ReactElement | null;
100
100
  lastResult: ContactWidgetResult | null;
101
101
  };
102
+ interface TicketHistoryProps {
103
+ /** Whether the modal sheet is visible. */
104
+ visible: boolean;
105
+ /** Widget UUID that scopes the org. */
106
+ widgetId: string;
107
+ /**
108
+ * Current SDK access token (from `SendoraCloud.auth.getAccessToken()`).
109
+ * Passed in the URL hash fragment so it never appears in server
110
+ * logs / referer headers. Embed scrubs the hash on load.
111
+ * The widget is no-op when this is null/empty.
112
+ */
113
+ accessToken: string | null | undefined;
114
+ workerHost?: string;
115
+ theme?: "auto" | "light" | "dark";
116
+ onClose: (result: ContactWidgetResult) => void;
117
+ }
118
+ declare function TicketHistory({ visible, widgetId, accessToken, workerHost, theme, onClose, }: TicketHistoryProps): React.ReactElement | null;
119
+ /**
120
+ * Convenience hook for the ticket history view. Takes the SDK's
121
+ * `getAccessToken` helper (deferred — called lazily on open) so the
122
+ * host doesn't have to thread token state by hand.
123
+ *
124
+ * import SendoraCloud from "@sendoracloud/sdk-react-native";
125
+ * const history = useTicketHistory("widget-uuid", () => SendoraCloud.auth.getAccessToken());
126
+ * <Button onPress={history.open} title="My tickets" />
127
+ * <history.View />
128
+ */
129
+ declare function useTicketHistory(widgetId: string, getAccessToken: () => Promise<string | null>, defaults?: {
130
+ workerHost?: string;
131
+ theme?: "auto" | "light" | "dark";
132
+ }): {
133
+ open: () => void;
134
+ close: () => void;
135
+ View: () => React.ReactElement | null;
136
+ };
102
137
 
103
- export { ContactWidget, type ContactWidgetProps, type ContactWidgetResult, useContactWidget };
138
+ export { ContactWidget, type ContactWidgetProps, type ContactWidgetResult, TicketHistory, type TicketHistoryProps, useContactWidget, useTicketHistory };
@@ -99,5 +99,40 @@ declare function useContactWidget(widgetId: string, defaults?: Omit<ContactWidge
99
99
  View: () => React.ReactElement | null;
100
100
  lastResult: ContactWidgetResult | null;
101
101
  };
102
+ interface TicketHistoryProps {
103
+ /** Whether the modal sheet is visible. */
104
+ visible: boolean;
105
+ /** Widget UUID that scopes the org. */
106
+ widgetId: string;
107
+ /**
108
+ * Current SDK access token (from `SendoraCloud.auth.getAccessToken()`).
109
+ * Passed in the URL hash fragment so it never appears in server
110
+ * logs / referer headers. Embed scrubs the hash on load.
111
+ * The widget is no-op when this is null/empty.
112
+ */
113
+ accessToken: string | null | undefined;
114
+ workerHost?: string;
115
+ theme?: "auto" | "light" | "dark";
116
+ onClose: (result: ContactWidgetResult) => void;
117
+ }
118
+ declare function TicketHistory({ visible, widgetId, accessToken, workerHost, theme, onClose, }: TicketHistoryProps): React.ReactElement | null;
119
+ /**
120
+ * Convenience hook for the ticket history view. Takes the SDK's
121
+ * `getAccessToken` helper (deferred — called lazily on open) so the
122
+ * host doesn't have to thread token state by hand.
123
+ *
124
+ * import SendoraCloud from "@sendoracloud/sdk-react-native";
125
+ * const history = useTicketHistory("widget-uuid", () => SendoraCloud.auth.getAccessToken());
126
+ * <Button onPress={history.open} title="My tickets" />
127
+ * <history.View />
128
+ */
129
+ declare function useTicketHistory(widgetId: string, getAccessToken: () => Promise<string | null>, defaults?: {
130
+ workerHost?: string;
131
+ theme?: "auto" | "light" | "dark";
132
+ }): {
133
+ open: () => void;
134
+ close: () => void;
135
+ View: () => React.ReactElement | null;
136
+ };
102
137
 
103
- export { ContactWidget, type ContactWidgetProps, type ContactWidgetResult, useContactWidget };
138
+ export { ContactWidget, type ContactWidgetProps, type ContactWidgetResult, TicketHistory, type TicketHistoryProps, useContactWidget, useTicketHistory };
@@ -99,7 +99,86 @@ function useContactWidget(widgetId, defaults) {
99
99
  );
100
100
  return { open, close, View: View2, lastResult };
101
101
  }
102
+ function TicketHistory({
103
+ visible,
104
+ widgetId,
105
+ accessToken,
106
+ workerHost = "https://go.sendoracloud.com",
107
+ theme = "auto",
108
+ onClose
109
+ }) {
110
+ const didFire = React.useRef(false);
111
+ React.useEffect(() => {
112
+ if (visible) didFire.current = false;
113
+ }, [visible]);
114
+ if (!widgetId || !accessToken) return null;
115
+ const params = new URLSearchParams({ widgetId, theme });
116
+ const uri = `${workerHost}/embed/tickets?${params.toString()}#access=${encodeURIComponent(accessToken)}`;
117
+ const handleClose = (result) => {
118
+ if (didFire.current) return;
119
+ didFire.current = true;
120
+ onClose(result);
121
+ };
122
+ const onNav = (req) => {
123
+ const url = req.url || "";
124
+ if (url.startsWith("sendora://close")) {
125
+ handleClose({ ticketId: "", portalUrl: null, submitted: false });
126
+ return false;
127
+ }
128
+ if (!url.startsWith("https://") && !url.startsWith("about:")) return false;
129
+ return true;
130
+ };
131
+ return /* @__PURE__ */ jsx(
132
+ Modal,
133
+ {
134
+ visible,
135
+ animationType: "slide",
136
+ presentationStyle: "pageSheet",
137
+ onRequestClose: () => handleClose({ ticketId: "", portalUrl: null, submitted: false }),
138
+ children: /* @__PURE__ */ jsx(View, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(
139
+ WebView,
140
+ {
141
+ source: { uri },
142
+ onShouldStartLoadWithRequest: onNav,
143
+ originWhitelist: ["https://*", "about:*", "sendora://*"],
144
+ setSupportMultipleWindows: false,
145
+ javaScriptEnabled: true,
146
+ domStorageEnabled: true,
147
+ allowsBackForwardNavigationGestures: false
148
+ }
149
+ ) })
150
+ }
151
+ );
152
+ }
153
+ function useTicketHistory(widgetId, getAccessToken, defaults) {
154
+ const [visible, setVisible] = React.useState(false);
155
+ const [token, setToken] = React.useState(null);
156
+ const open = React.useCallback(async () => {
157
+ const t = await getAccessToken();
158
+ if (!t) return;
159
+ setToken(t);
160
+ setVisible(true);
161
+ }, [getAccessToken]);
162
+ const close = React.useCallback(() => setVisible(false), []);
163
+ const View2 = React.useCallback(
164
+ () => /* @__PURE__ */ jsx(
165
+ TicketHistory,
166
+ {
167
+ widgetId,
168
+ visible,
169
+ accessToken: token,
170
+ workerHost: defaults?.workerHost,
171
+ theme: defaults?.theme,
172
+ onClose: () => setVisible(false)
173
+ }
174
+ ),
175
+ [widgetId, visible, token, defaults]
176
+ );
177
+ return { open, close, View: View2 };
178
+ }
102
179
  export {
103
180
  ContactWidget,
104
- useContactWidget
181
+ TicketHistory,
182
+ useContactWidget,
183
+ useTicketHistory
105
184
  };
package/dist/index.cjs CHANGED
@@ -2308,6 +2308,22 @@ var SendoraSDK = class {
2308
2308
  const parsed = await res.json();
2309
2309
  if (!parsed.success || !parsed.data) throw new Error("[sendoracloud] support.submitCsat: bad response");
2310
2310
  return parsed.data;
2311
+ },
2312
+ getUnreadCount: async (widgetId) => {
2313
+ if (!self.config || !widgetId) return 0;
2314
+ const token = await self.auth.getAccessToken();
2315
+ if (!token) return 0;
2316
+ try {
2317
+ const res = await fetch(
2318
+ `${self.config.apiUrl}/api/v1/widgets/${encodeURIComponent(widgetId)}/my-tickets/unread-count`,
2319
+ { headers: { Authorization: `Bearer ${token}` } }
2320
+ );
2321
+ if (!res.ok) return 0;
2322
+ const parsed = await res.json();
2323
+ return parsed?.data?.count ?? 0;
2324
+ } catch {
2325
+ return 0;
2326
+ }
2311
2327
  }
2312
2328
  };
2313
2329
  }
package/dist/index.d.cts CHANGED
@@ -1008,6 +1008,13 @@ declare class SendoraSDK {
1008
1008
  rating: number;
1009
1009
  comment: string | null;
1010
1010
  }>;
1011
+ /**
1012
+ * Wave 73 — unread support-ticket count for the in-app badge.
1013
+ * Bearer JWT auth via the SDK's current end-user session.
1014
+ * Returns 0 if no auth (anon or identified) is available.
1015
+ * Recommended cadence: AppState foreground + every 60s.
1016
+ */
1017
+ getUnreadCount: (widgetId: string) => Promise<number>;
1011
1018
  };
1012
1019
  /**
1013
1020
  * Knowledge-base search namespace. Mirrors backend `GET /kb/search`
package/dist/index.d.ts CHANGED
@@ -1008,6 +1008,13 @@ declare class SendoraSDK {
1008
1008
  rating: number;
1009
1009
  comment: string | null;
1010
1010
  }>;
1011
+ /**
1012
+ * Wave 73 — unread support-ticket count for the in-app badge.
1013
+ * Bearer JWT auth via the SDK's current end-user session.
1014
+ * Returns 0 if no auth (anon or identified) is available.
1015
+ * Recommended cadence: AppState foreground + every 60s.
1016
+ */
1017
+ getUnreadCount: (widgetId: string) => Promise<number>;
1011
1018
  };
1012
1019
  /**
1013
1020
  * Knowledge-base search namespace. Mirrors backend `GET /kb/search`
package/dist/index.js CHANGED
@@ -2270,6 +2270,22 @@ var SendoraSDK = class {
2270
2270
  const parsed = await res.json();
2271
2271
  if (!parsed.success || !parsed.data) throw new Error("[sendoracloud] support.submitCsat: bad response");
2272
2272
  return parsed.data;
2273
+ },
2274
+ getUnreadCount: async (widgetId) => {
2275
+ if (!self.config || !widgetId) return 0;
2276
+ const token = await self.auth.getAccessToken();
2277
+ if (!token) return 0;
2278
+ try {
2279
+ const res = await fetch(
2280
+ `${self.config.apiUrl}/api/v1/widgets/${encodeURIComponent(widgetId)}/my-tickets/unread-count`,
2281
+ { headers: { Authorization: `Bearer ${token}` } }
2282
+ );
2283
+ if (!res.ok) return 0;
2284
+ const parsed = await res.json();
2285
+ return parsed?.data?.count ?? 0;
2286
+ } catch {
2287
+ return 0;
2288
+ }
2273
2289
  }
2274
2290
  };
2275
2291
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendoracloud/sdk-react-native",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
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",