@webority/mobile-support 0.0.13 → 0.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.
Files changed (45) hide show
  1. package/lib/commonjs/SupportProvider.js +1 -335
  2. package/lib/commonjs/client.js +1 -149
  3. package/lib/commonjs/clipboard.js +1 -10
  4. package/lib/commonjs/components/Attachments.js +1 -246
  5. package/lib/commonjs/components/NewSupportTicket.js +1 -210
  6. package/lib/commonjs/components/PhotoTray.js +1 -141
  7. package/lib/commonjs/components/SupportContact.js +1 -134
  8. package/lib/commonjs/components/SupportTicketDetail.js +1 -348
  9. package/lib/commonjs/components/SupportTicketList.js +1 -279
  10. package/lib/commonjs/device.js +1 -29
  11. package/lib/commonjs/files.js +1 -37
  12. package/lib/commonjs/format.js +1 -29
  13. package/lib/commonjs/index.js +1 -97
  14. package/lib/commonjs/photos.js +1 -67
  15. package/lib/commonjs/report/ReportIssueButton.js +1 -57
  16. package/lib/commonjs/report/captureAndReport.js +1 -34
  17. package/lib/commonjs/report/index.js +1 -20
  18. package/lib/commonjs/share/index.js +1 -44
  19. package/lib/commonjs/types.js +1 -2
  20. package/lib/commonjs/validation.js +1 -45
  21. package/lib/module/SupportProvider.js +1 -323
  22. package/lib/module/client.js +1 -143
  23. package/lib/module/clipboard.js +1 -5
  24. package/lib/module/components/Attachments.js +1 -240
  25. package/lib/module/components/NewSupportTicket.js +1 -205
  26. package/lib/module/components/PhotoTray.js +1 -136
  27. package/lib/module/components/SupportContact.js +1 -129
  28. package/lib/module/components/SupportTicketDetail.js +1 -343
  29. package/lib/module/components/SupportTicketList.js +1 -273
  30. package/lib/module/device.js +1 -24
  31. package/lib/module/files.js +1 -31
  32. package/lib/module/format.js +1 -23
  33. package/lib/module/index.js +1 -10
  34. package/lib/module/photos.js +1 -61
  35. package/lib/module/report/ReportIssueButton.js +1 -52
  36. package/lib/module/report/captureAndReport.js +1 -29
  37. package/lib/module/report/index.js +1 -12
  38. package/lib/module/share/index.js +1 -37
  39. package/lib/module/types.js +0 -2
  40. package/lib/module/validation.js +1 -38
  41. package/lib/typescript/commonjs/SupportProvider.d.ts +4 -0
  42. package/lib/typescript/commonjs/format.d.ts +6 -4
  43. package/lib/typescript/module/SupportProvider.d.ts +4 -0
  44. package/lib/typescript/module/format.d.ts +6 -4
  45. package/package.json +8 -6
@@ -1,335 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.useSupportTickets = exports.useSupportTicket = exports.useSupportConfig = exports.useSupportClient = exports.useCreateSupportTicket = exports.useAttachmentOpener = exports.useAttachmentLoader = exports.SupportProvider = void 0;
7
- var _react = require("react");
8
- var _client = require("./client.js");
9
- var _files = require("./files.js");
10
- var _jsxRuntime = require("react/jsx-runtime");
11
- /** A downloaded attachment, ready for the OS to open. */
12
-
13
- /** Hands a downloaded file to the OS. `shareSupportFile` from the `/share` entry is one. */
14
-
15
- const SupportContext = /*#__PURE__*/(0, _react.createContext)(null);
16
- const SupportProvider = ({
17
- client,
18
- openFile,
19
- children
20
- }) => {
21
- const [config, setConfig] = (0, _react.useState)(null);
22
- const [configError, setConfigError] = (0, _react.useState)(null);
23
- const [configLoading, setConfigLoading] = (0, _react.useState)(true);
24
- const [revision, setRevision] = (0, _react.useState)(0);
25
- // Attachments never change once stored, so each is fetched once per session.
26
- const attachments = (0, _react.useRef)(new Map());
27
- const reloadConfig = (0, _react.useCallback)(async () => {
28
- setConfigLoading(true);
29
- try {
30
- setConfig(await client.getConfig());
31
- setConfigError(null);
32
- } catch (error) {
33
- setConfigError((0, _client.toSupportApiError)(error));
34
- } finally {
35
- setConfigLoading(false);
36
- }
37
- }, [client]);
38
- (0, _react.useEffect)(() => {
39
- attachments.current.clear();
40
- void reloadConfig();
41
- }, [reloadConfig]);
42
- const loadAttachment = (0, _react.useCallback)(attachment => {
43
- const cached = attachments.current.get(attachment.id);
44
- if (cached) {
45
- return cached;
46
- }
47
- const pending = client.getAttachmentDataUri(attachment);
48
- attachments.current.set(attachment.id, pending);
49
- pending.catch(() => attachments.current.delete(attachment.id));
50
- return pending;
51
- }, [client]);
52
- const openAttachment = (0, _react.useMemo)(() => openFile ? async attachment => {
53
- const uri = await (0, _files.saveAttachmentToCache)(attachment, () => client.getAttachmentBytes(attachment.id));
54
- await openFile({
55
- uri,
56
- name: attachment.fileName,
57
- contentType: attachment.contentType
58
- });
59
- } : null, [client, openFile]);
60
- const bumpRevision = (0, _react.useCallback)(() => setRevision(r => r + 1), []);
61
- const value = (0, _react.useMemo)(() => ({
62
- client,
63
- config,
64
- configError,
65
- configLoading,
66
- reloadConfig,
67
- loadAttachment,
68
- openAttachment,
69
- revision,
70
- bumpRevision
71
- }), [client, config, configError, configLoading, reloadConfig, loadAttachment, openAttachment, revision, bumpRevision]);
72
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(SupportContext.Provider, {
73
- value: value,
74
- children: children
75
- });
76
- };
77
- exports.SupportProvider = SupportProvider;
78
- const useSupportContext = () => {
79
- const ctx = (0, _react.useContext)(SupportContext);
80
- if (!ctx) {
81
- throw new Error('[@webority/mobile-support] Support screens and hooks need a <SupportProvider client={...}> above them.');
82
- }
83
- return ctx;
84
- };
85
- const useSupportClient = () => useSupportContext().client;
86
-
87
- /** @internal Used by the attachment thumbnails. */
88
- exports.useSupportClient = useSupportClient;
89
- const useAttachmentLoader = () => useSupportContext().loadAttachment;
90
-
91
- /** @internal Used by the file rows; null when the app gave no `openFile`. */
92
- exports.useAttachmentLoader = useAttachmentLoader;
93
- const useAttachmentOpener = () => useSupportContext().openAttachment;
94
- exports.useAttachmentOpener = useAttachmentOpener;
95
- const useSupportConfig = () => {
96
- const {
97
- config,
98
- configLoading,
99
- configError,
100
- reloadConfig
101
- } = useSupportContext();
102
- return {
103
- config,
104
- loading: configLoading,
105
- error: configError,
106
- reload: reloadConfig
107
- };
108
- };
109
- exports.useSupportConfig = useSupportConfig;
110
- const DEFAULT_PAGE_SIZE = 20;
111
- const useSupportTickets = (options = {}) => {
112
- const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
113
- const {
114
- client,
115
- revision
116
- } = useSupportContext();
117
- const [status, setStatus] = (0, _react.useState)(null);
118
- const [search, setSearch] = (0, _react.useState)('');
119
- const [tickets, setTickets] = (0, _react.useState)([]);
120
- const [counts, setCounts] = (0, _react.useState)(null);
121
- const [page, setPage] = (0, _react.useState)(0);
122
- const [totalPages, setTotalPages] = (0, _react.useState)(0);
123
- const [loading, setLoading] = (0, _react.useState)(true);
124
- const [refreshing, setRefreshing] = (0, _react.useState)(false);
125
- const [loadingMore, setLoadingMore] = (0, _react.useState)(false);
126
- const [error, setError] = (0, _react.useState)(null);
127
- const [loadMoreError, setLoadMoreError] = (0, _react.useState)(null);
128
- const mounted = (0, _react.useRef)(true);
129
- // Each first-page load takes a new number; a response carrying an older one
130
- // belongs to a filter or search the person has already left, and is dropped.
131
- const sequence = (0, _react.useRef)(0);
132
- const moreInFlight = (0, _react.useRef)(false);
133
- // The client whose counts are on screen; null until a load with counts lands.
134
- const countsFrom = (0, _react.useRef)(null);
135
- (0, _react.useEffect)(() => {
136
- mounted.current = true;
137
- return () => {
138
- mounted.current = false;
139
- };
140
- }, []);
141
- const query = (0, _react.useCallback)(nextPage => ({
142
- status: status ?? undefined,
143
- search: search.trim() || undefined,
144
- page: nextPage,
145
- pageSize
146
- }), [status, search, pageSize]);
147
- const loadFirstPage = (0, _react.useCallback)(async mode => {
148
- sequence.current += 1;
149
- const seq = sequence.current;
150
- const current = () => mounted.current && seq === sequence.current;
151
- moreInFlight.current = false;
152
- setLoadingMore(false);
153
- if (mode === 'refresh') {
154
- setRefreshing(true);
155
- } else if (mode === 'replace') {
156
- setLoading(true);
157
- setTickets([]);
158
- }
159
- // Counts ignore the status and search, so a filter change keeps the ones it has.
160
- const withCounts = mode !== 'replace' || countsFrom.current !== client;
161
- try {
162
- const [first, nextCounts] = await Promise.all([client.listTickets(query(1)), withCounts ? client.getTicketCounts() : null]);
163
- if (current()) {
164
- setTickets(first.items);
165
- setPage(first.page);
166
- setTotalPages(first.totalPages);
167
- if (nextCounts) {
168
- setCounts(nextCounts);
169
- countsFrom.current = client;
170
- }
171
- setError(null);
172
- setLoadMoreError(null);
173
- }
174
- } catch (e) {
175
- if (current()) {
176
- setError((0, _client.toSupportApiError)(e));
177
- }
178
- } finally {
179
- if (current()) {
180
- setLoading(false);
181
- setRefreshing(false);
182
- }
183
- }
184
- }, [client, query]);
185
- const seenRevision = (0, _react.useRef)(revision);
186
- (0, _react.useEffect)(() => {
187
- // A write elsewhere refetches without blanking the rows on screen.
188
- const mode = seenRevision.current === revision ? 'replace' : 'quiet';
189
- seenRevision.current = revision;
190
- void loadFirstPage(mode);
191
- }, [loadFirstPage, revision]);
192
- const hasMore = page < totalPages;
193
- const loadMore = (0, _react.useCallback)(async () => {
194
- if (moreInFlight.current || loading || refreshing || !hasMore) {
195
- return;
196
- }
197
- const seq = sequence.current;
198
- const current = () => mounted.current && seq === sequence.current;
199
- moreInFlight.current = true;
200
- setLoadingMore(true);
201
- setLoadMoreError(null);
202
- try {
203
- const next = await client.listTickets(query(page + 1));
204
- if (current()) {
205
- // Order is by last update, so a ticket that changed between two pages
206
- // can arrive twice.
207
- setTickets(prev => {
208
- const ids = new Set(prev.map(t => t.id));
209
- return [...prev, ...next.items.filter(t => !ids.has(t.id))];
210
- });
211
- setPage(next.page);
212
- setTotalPages(next.totalPages);
213
- }
214
- } catch (e) {
215
- if (current()) {
216
- setLoadMoreError((0, _client.toSupportApiError)(e));
217
- }
218
- } finally {
219
- if (current()) {
220
- moreInFlight.current = false;
221
- setLoadingMore(false);
222
- }
223
- }
224
- }, [client, query, page, hasMore, loading, refreshing]);
225
- const refresh = (0, _react.useCallback)(() => loadFirstPage('refresh'), [loadFirstPage]);
226
- const createTicket = useCreateSupportTicket();
227
- return {
228
- tickets,
229
- counts,
230
- status,
231
- search,
232
- setStatus,
233
- setSearch,
234
- loading,
235
- refreshing,
236
- loadingMore,
237
- hasMore,
238
- error,
239
- loadMoreError,
240
- refresh,
241
- loadMore,
242
- createTicket
243
- };
244
- };
245
-
246
- /** Raises a ticket and tells every mounted ticket list to refetch. */
247
- exports.useSupportTickets = useSupportTickets;
248
- const useCreateSupportTicket = () => {
249
- const {
250
- client,
251
- bumpRevision
252
- } = useSupportContext();
253
- return (0, _react.useCallback)(async input => {
254
- const created = await client.createTicket(input);
255
- bumpRevision();
256
- return created;
257
- }, [client, bumpRevision]);
258
- };
259
- exports.useCreateSupportTicket = useCreateSupportTicket;
260
- const useSupportTicket = id => {
261
- const {
262
- client,
263
- bumpRevision
264
- } = useSupportContext();
265
- const [ticket, setTicket] = (0, _react.useState)(null);
266
- const [loading, setLoading] = (0, _react.useState)(true);
267
- const [error, setError] = (0, _react.useState)(null);
268
- const [actionError, setActionError] = (0, _react.useState)(null);
269
- const [busy, setBusy] = (0, _react.useState)(false);
270
- const mounted = (0, _react.useRef)(true);
271
- (0, _react.useEffect)(() => {
272
- mounted.current = true;
273
- return () => {
274
- mounted.current = false;
275
- };
276
- }, []);
277
- const reload = (0, _react.useCallback)(async () => {
278
- setLoading(true);
279
- try {
280
- const next = await client.getTicket(id);
281
- if (mounted.current) {
282
- setTicket(next);
283
- setError(null);
284
- }
285
- } catch (e) {
286
- if (mounted.current) {
287
- setError((0, _client.toSupportApiError)(e));
288
- }
289
- } finally {
290
- if (mounted.current) {
291
- setLoading(false);
292
- }
293
- }
294
- }, [client, id]);
295
- (0, _react.useEffect)(() => {
296
- void reload();
297
- }, [reload]);
298
- const act = (0, _react.useCallback)(async run => {
299
- setBusy(true);
300
- setActionError(null);
301
- try {
302
- const next = await run();
303
- if (mounted.current) {
304
- setTicket(next);
305
- }
306
- bumpRevision();
307
- return true;
308
- } catch (e) {
309
- if (mounted.current) {
310
- setActionError((0, _client.toSupportApiError)(e));
311
- }
312
- return false;
313
- } finally {
314
- if (mounted.current) {
315
- setBusy(false);
316
- }
317
- }
318
- }, [bumpRevision]);
319
- const reply = (0, _react.useCallback)(input => act(() => client.reply(id, input)), [act, client, id]);
320
- const reopen = (0, _react.useCallback)(() => act(() => client.reopen(id)), [act, client, id]);
321
- const rate = (0, _react.useCallback)(input => act(() => client.rate(id, input)), [act, client, id]);
322
- return {
323
- ticket,
324
- loading,
325
- error,
326
- actionError,
327
- busy,
328
- reload,
329
- reply,
330
- reopen,
331
- rate
332
- };
333
- };
334
- exports.useSupportTicket = useSupportTicket;
335
- //# sourceMappingURL=SupportProvider.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.useSupportTimeZone=exports.useSupportTickets=exports.useSupportTicket=exports.useSupportConfig=exports.useSupportClient=exports.useCreateSupportTicket=exports.useAttachmentOpener=exports.useAttachmentLoader=exports.SupportProvider=void 0;var _react=require("react"),_client=require("./client.js"),_files=require("./files.js"),_format=require("./format.js"),_jsxRuntime=require("react/jsx-runtime");const SupportContext=(0,_react.createContext)(null),SupportProvider=({client:e,openFile:t,timeZone:r=_format.DEFAULT_TIME_ZONE,children:c})=>{const[u,o]=(0,_react.useState)(null),[a,n]=(0,_react.useState)(null),[s,p]=(0,_react.useState)(!0),[i,l]=(0,_react.useState)(0),S=(0,_react.useRef)(new Map),_=(0,_react.useCallback)(async()=>{p(!0);try{o(await e.getConfig()),n(null)}catch(e){n((0,_client.toSupportApiError)(e))}finally{p(!1)}},[e]);(0,_react.useEffect)(()=>{S.current.clear(),_()},[_]);const f=(0,_react.useCallback)(t=>{const r=S.current.get(t.id);if(r)return r;const c=e.getAttachmentDataUri(t);return S.current.set(t.id,c),c.catch(()=>S.current.delete(t.id)),c},[e]),C=(0,_react.useMemo)(()=>t?async r=>{const c=await(0,_files.saveAttachmentToCache)(r,()=>e.getAttachmentBytes(r.id));await t({uri:c,name:r.fileName,contentType:r.contentType})}:null,[e,t]),m=(0,_react.useCallback)(()=>l(e=>e+1),[]),d=(0,_react.useMemo)(()=>({client:e,config:u,configError:a,configLoading:s,reloadConfig:_,loadAttachment:f,openAttachment:C,timeZone:r,revision:i,bumpRevision:m}),[e,u,a,s,_,f,C,r,i,m]);return(0,_jsxRuntime.jsx)(SupportContext.Provider,{value:d,children:c})};exports.SupportProvider=SupportProvider;const useSupportContext=()=>{const e=(0,_react.useContext)(SupportContext);if(!e)throw new Error("[@webority/mobile-support] Support screens and hooks need a <SupportProvider client={...}> above them.");return e},useSupportClient=()=>useSupportContext().client;exports.useSupportClient=useSupportClient;const useAttachmentLoader=()=>useSupportContext().loadAttachment;exports.useAttachmentLoader=useAttachmentLoader;const useAttachmentOpener=()=>useSupportContext().openAttachment;exports.useAttachmentOpener=useAttachmentOpener;const useSupportTimeZone=()=>useSupportContext().timeZone;exports.useSupportTimeZone=useSupportTimeZone;const useSupportConfig=()=>{const{config:e,configLoading:t,configError:r,reloadConfig:c}=useSupportContext();return{config:e,loading:t,error:r,reload:c}};exports.useSupportConfig=useSupportConfig;const DEFAULT_PAGE_SIZE=20,useSupportTickets=(e={})=>{const t=e.pageSize??20,{client:r,revision:c}=useSupportContext(),[u,o]=(0,_react.useState)(null),[a,n]=(0,_react.useState)(""),[s,p]=(0,_react.useState)([]),[i,l]=(0,_react.useState)(null),[S,_]=(0,_react.useState)(0),[f,C]=(0,_react.useState)(0),[m,d]=(0,_react.useState)(!0),[x,h]=(0,_react.useState)(!1),[k,g]=(0,_react.useState)(!1),[T,y]=(0,_react.useState)(null),[A,b]=(0,_react.useState)(null),E=(0,_react.useRef)(!0),v=(0,_react.useRef)(0),w=(0,_react.useRef)(!1),R=(0,_react.useRef)(null);(0,_react.useEffect)(()=>(E.current=!0,()=>{E.current=!1}),[]);const P=(0,_react.useCallback)(e=>({status:u??void 0,search:a.trim()||void 0,page:e,pageSize:t}),[u,a,t]),M=(0,_react.useCallback)(async e=>{v.current+=1;const t=v.current,current=()=>E.current&&t===v.current;w.current=!1,g(!1),"refresh"===e?h(!0):"replace"===e&&(d(!0),p([]));const c="replace"!==e||R.current!==r;try{const[e,t]=await Promise.all([r.listTickets(P(1)),c?r.getTicketCounts():null]);current()&&(p(e.items),_(e.page),C(e.totalPages),t&&(l(t),R.current=r),y(null),b(null))}catch(e){current()&&y((0,_client.toSupportApiError)(e))}finally{current()&&(d(!1),h(!1))}},[r,P]),Z=(0,_react.useRef)(c);(0,_react.useEffect)(()=>{const e=Z.current===c?"replace":"quiet";Z.current=c,M(e)},[M,c]);const j=S<f,L=(0,_react.useCallback)(async()=>{if(w.current||m||x||!j)return;const e=v.current,current=()=>E.current&&e===v.current;w.current=!0,g(!0),b(null);try{const e=await r.listTickets(P(S+1));current()&&(p(t=>{const r=new Set(t.map(e=>e.id));return[...t,...e.items.filter(e=>!r.has(e.id))]}),_(e.page),C(e.totalPages))}catch(e){current()&&b((0,_client.toSupportApiError)(e))}finally{current()&&(w.current=!1,g(!1))}},[r,P,S,j,m,x]),q=(0,_react.useCallback)(()=>M("refresh"),[M]),O=useCreateSupportTicket();return{tickets:s,counts:i,status:u,search:a,setStatus:o,setSearch:n,loading:m,refreshing:x,loadingMore:k,hasMore:j,error:T,loadMoreError:A,refresh:q,loadMore:L,createTicket:O}};exports.useSupportTickets=useSupportTickets;const useCreateSupportTicket=()=>{const{client:e,bumpRevision:t}=useSupportContext();return(0,_react.useCallback)(async r=>{const c=await e.createTicket(r);return t(),c},[e,t])};exports.useCreateSupportTicket=useCreateSupportTicket;const useSupportTicket=e=>{const{client:t,bumpRevision:r}=useSupportContext(),[c,u]=(0,_react.useState)(null),[o,a]=(0,_react.useState)(!0),[n,s]=(0,_react.useState)(null),[p,i]=(0,_react.useState)(null),[l,S]=(0,_react.useState)(!1),_=(0,_react.useRef)(!0);(0,_react.useEffect)(()=>(_.current=!0,()=>{_.current=!1}),[]);const f=(0,_react.useCallback)(async()=>{a(!0);try{const r=await t.getTicket(e);_.current&&(u(r),s(null))}catch(e){_.current&&s((0,_client.toSupportApiError)(e))}finally{_.current&&a(!1)}},[t,e]);(0,_react.useEffect)(()=>{f()},[f]);const C=(0,_react.useCallback)(async e=>{S(!0),i(null);try{const t=await e();return _.current&&u(t),r(),!0}catch(e){return _.current&&i((0,_client.toSupportApiError)(e)),!1}finally{_.current&&S(!1)}},[r]),m=(0,_react.useCallback)(r=>C(()=>t.reply(e,r)),[C,t,e]),d=(0,_react.useCallback)(()=>C(()=>t.reopen(e)),[C,t,e]),x=(0,_react.useCallback)(r=>C(()=>t.rate(e,r)),[C,t,e]);return{ticket:c,loading:o,error:n,actionError:p,busy:l,reload:f,reply:m,reopen:d,rate:x}};exports.useSupportTicket=useSupportTicket;
@@ -1,149 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.toSupportApiError = exports.createSupportClient = exports.SupportApiError = exports.OFFLINE_MESSAGE = exports.GENERIC_MESSAGE = void 0;
7
- var _mobileCore = require("@webority/mobile-core");
8
- /**
9
- * The slice of an HTTP client the support client needs. The app's signed-in
10
- * axios instance (mobile-core's `userClient`, or its own) satisfies it as is, so
11
- * whatever it adds to a request (bearer token, tenant header, refresh on 401)
12
- * applies to support calls too. Declared structurally so no axios type leaks into
13
- * this package's declarations.
14
- */
15
-
16
- const OFFLINE_MESSAGE = exports.OFFLINE_MESSAGE = 'You are offline. Check your connection and try again.';
17
- const GENERIC_MESSAGE = exports.GENERIC_MESSAGE = 'Something went wrong. Try again in a moment.';
18
-
19
- /**
20
- * A failed support call. `message` is written for the person and is shown as is;
21
- * `code` is the backend's stable code, for branching only.
22
- */
23
- class SupportApiError extends Error {
24
- constructor(code, message, status) {
25
- super(message);
26
- this.name = 'SupportApiError';
27
- this.code = code;
28
- this.status = status;
29
- }
30
- }
31
- exports.SupportApiError = SupportApiError;
32
- /** Turns whatever the HTTP client threw into a SupportApiError. */
33
- const toSupportApiError = error => {
34
- if (error instanceof SupportApiError) {
35
- return error;
36
- }
37
- const e = error ?? {};
38
- // By name, not instanceof: a second copy of mobile-core would fail instanceof.
39
- if (e.name === 'RequestOfflineError') {
40
- return new SupportApiError('offline', OFFLINE_MESSAGE, null);
41
- }
42
- const status = typeof e.response?.status === 'number' ? e.response.status : null;
43
- const data = e.response?.data;
44
- if (data && typeof data.message === 'string' && data.message.trim()) {
45
- const code = typeof data.code === 'string' ? data.code : 'unknown';
46
- return new SupportApiError(code, data.message, status);
47
- }
48
- if (!e.response) {
49
- return new SupportApiError('network', OFFLINE_MESSAGE, null);
50
- }
51
- return new SupportApiError('unknown', GENERIC_MESSAGE, status);
52
- };
53
-
54
- // Photo uploads get the fleet's multipart timeout, not the 30 s JSON one.
55
- exports.toSupportApiError = toSupportApiError;
56
- const UPLOAD_TIMEOUT_MS = 300000;
57
- const MULTIPART = {
58
- 'Content-Type': 'multipart/form-data'
59
- };
60
- const appendParts = (form, field, parts) => {
61
- for (const part of parts ?? []) {
62
- // React Native's FormData takes a { uri, name, type } object as a file part.
63
- form.append(field, {
64
- uri: part.uri,
65
- name: part.name,
66
- type: part.type
67
- });
68
- }
69
- };
70
- const appendText = (form, field, value) => {
71
- if (value !== undefined && value !== '') {
72
- form.append(field, value);
73
- }
74
- };
75
- const queryString = query => {
76
- const pairs = [];
77
- const add = (key, value) => {
78
- if (value !== undefined && value !== '') {
79
- pairs.push(`${key}=${encodeURIComponent(String(value))}`);
80
- }
81
- };
82
- add('status', query.status);
83
- add('search', query.search?.trim());
84
- add('page', query.page);
85
- add('pageSize', query.pageSize);
86
- return pairs.length > 0 ? `?${pairs.join('&')}` : '';
87
- };
88
- const createSupportClient = (http, basePath = '/api/support') => {
89
- const base = basePath.replace(/\/+$/, '');
90
- const ticketUrl = id => `${base}/tickets/${encodeURIComponent(id)}`;
91
- const call = async request => {
92
- try {
93
- const response = await request();
94
- return response.data;
95
- } catch (error) {
96
- throw toSupportApiError(error);
97
- }
98
- };
99
- const getBytes = id => call(() => http.get(`${base}/attachments/${encodeURIComponent(id)}`, {
100
- responseType: 'arraybuffer'
101
- }));
102
- return {
103
- basePath: base,
104
- getConfig: () => call(() => http.get(`${base}/settings`)),
105
- listTickets: (query = {}) => call(() => http.get(`${base}/tickets${queryString(query)}`)),
106
- getTicketCounts: () => call(() => http.get(`${base}/tickets/counts`)),
107
- getTicket: id => call(() => http.get(ticketUrl(id))),
108
- createTicket: input => {
109
- const form = new FormData();
110
- form.append('category', input.category);
111
- form.append('subject', input.subject);
112
- form.append('body', input.body);
113
- appendText(form, 'pageUrl', input.pageUrl);
114
- appendText(form, 'userAgent', input.userAgent);
115
- appendText(form, 'appVersion', input.appVersion);
116
- appendParts(form, 'files', input.files);
117
- appendParts(form, 'screenshots', input.screenshots);
118
- return call(() => http.post(`${base}/tickets`, form, {
119
- headers: MULTIPART,
120
- timeout: UPLOAD_TIMEOUT_MS
121
- }));
122
- },
123
- reply: (id, input) => {
124
- const form = new FormData();
125
- form.append('body', input.body);
126
- appendParts(form, 'files', input.files);
127
- appendParts(form, 'screenshots', input.screenshots);
128
- return call(() => http.post(`${ticketUrl(id)}/replies`, form, {
129
- headers: MULTIPART,
130
- timeout: UPLOAD_TIMEOUT_MS
131
- }));
132
- },
133
- reopen: id => call(() => http.post(`${ticketUrl(id)}/reopen`)),
134
- rate: (id, input) => call(() => http.post(`${ticketUrl(id)}/rating`, {
135
- satisfaction: input.satisfaction,
136
- comment: input.comment?.trim() ? input.comment.trim() : null
137
- })),
138
- getAttachmentBytes: getBytes,
139
- getAttachmentDataUri: async attachment => {
140
- const bytes = await getBytes(attachment.id);
141
- return `data:${attachment.contentType};base64,${(0, _mobileCore.encodeBase64)(bytes)}`;
142
- },
143
- confirmAction: token => call(() => http.post(`${base}/actions/confirm`, {
144
- token
145
- }))
146
- };
147
- };
148
- exports.createSupportClient = createSupportClient;
149
- //# sourceMappingURL=client.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.toSupportApiError=exports.createSupportClient=exports.SupportApiError=exports.OFFLINE_MESSAGE=exports.GENERIC_MESSAGE=void 0;var _mobileCore=require("@webority/mobile-core");const OFFLINE_MESSAGE=exports.OFFLINE_MESSAGE="You are offline. Check your connection and try again.",GENERIC_MESSAGE=exports.GENERIC_MESSAGE="Something went wrong. Try again in a moment.";class SupportApiError extends Error{constructor(e,t,r){super(t),this.name="SupportApiError",this.code=e,this.status=r}}exports.SupportApiError=SupportApiError;const toSupportApiError=e=>{if(e instanceof SupportApiError)return e;const t=e??{};if("RequestOfflineError"===t.name)return new SupportApiError("offline",OFFLINE_MESSAGE,null);const r="number"==typeof t.response?.status?t.response.status:null,o=t.response?.data;if(o&&"string"==typeof o.message&&o.message.trim()){const e="string"==typeof o.code?o.code:"unknown";return new SupportApiError(e,o.message,r)}return t.response?new SupportApiError("unknown",GENERIC_MESSAGE,r):new SupportApiError("network",OFFLINE_MESSAGE,null)};exports.toSupportApiError=toSupportApiError;const UPLOAD_TIMEOUT_MS=3e5,MULTIPART={"Content-Type":"multipart/form-data"},appendParts=(e,t,r)=>{for(const o of r??[])e.append(t,{uri:o.uri,name:o.name,type:o.type})},appendText=(e,t,r)=>{void 0!==r&&""!==r&&e.append(t,r)},queryString=e=>{const t=[],add=(e,r)=>{void 0!==r&&""!==r&&t.push(`${e}=${encodeURIComponent(String(r))}`)};return add("status",e.status),add("search",e.search?.trim()),add("page",e.page),add("pageSize",e.pageSize),t.length>0?`?${t.join("&")}`:""},createSupportClient=(e,t="/api/support")=>{const r=t.replace(/\/+$/,""),ticketUrl=e=>`${r}/tickets/${encodeURIComponent(e)}`,call=async e=>{try{return(await e()).data}catch(e){throw toSupportApiError(e)}},getBytes=t=>call(()=>e.get(`${r}/attachments/${encodeURIComponent(t)}`,{responseType:"arraybuffer"}));return{basePath:r,getConfig:()=>call(()=>e.get(`${r}/settings`)),listTickets:(t={})=>call(()=>e.get(`${r}/tickets${queryString(t)}`)),getTicketCounts:()=>call(()=>e.get(`${r}/tickets/counts`)),getTicket:t=>call(()=>e.get(ticketUrl(t))),createTicket:t=>{const o=new FormData;return o.append("category",t.category),o.append("subject",t.subject),o.append("body",t.body),appendText(o,"pageUrl",t.pageUrl),appendText(o,"userAgent",t.userAgent),appendText(o,"appVersion",t.appVersion),appendParts(o,"files",t.files),appendParts(o,"screenshots",t.screenshots),call(()=>e.post(`${r}/tickets`,o,{headers:MULTIPART,timeout:3e5}))},reply:(t,r)=>{const o=new FormData;return o.append("body",r.body),appendParts(o,"files",r.files),appendParts(o,"screenshots",r.screenshots),call(()=>e.post(`${ticketUrl(t)}/replies`,o,{headers:MULTIPART,timeout:3e5}))},reopen:t=>call(()=>e.post(`${ticketUrl(t)}/reopen`)),rate:(t,r)=>call(()=>e.post(`${ticketUrl(t)}/rating`,{satisfaction:r.satisfaction,comment:r.comment?.trim()?r.comment.trim():null})),getAttachmentBytes:getBytes,getAttachmentDataUri:async e=>{const t=await getBytes(e.id);return`data:${e.contentType};base64,${(0,_mobileCore.encodeBase64)(t)}`},confirmAction:t=>call(()=>e.post(`${r}/actions/confirm`,{token:t}))}};exports.createSupportClient=createSupportClient;
@@ -1,10 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.copyText = void 0;
7
- /** The one place `expo-clipboard` is named. */
8
- const copyText = text => require('expo-clipboard').setStringAsync(text);
9
- exports.copyText = copyText;
10
- //# sourceMappingURL=clipboard.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.copyText=void 0;const copyText=e=>require("expo-clipboard").setStringAsync(e);exports.copyText=copyText;