@planningcenter/chat-react-native 3.37.0-rc.2 → 3.37.0-rc.4

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 (46) hide show
  1. package/build/components/conversation/message.d.ts.map +1 -1
  2. package/build/components/conversation/message.js +3 -3
  3. package/build/components/conversation/message.js.map +1 -1
  4. package/build/components/index.d.ts +2 -0
  5. package/build/components/index.d.ts.map +1 -1
  6. package/build/components/index.js +2 -0
  7. package/build/components/index.js.map +1 -1
  8. package/build/components/page/component_error_boundary.d.ts +4 -0
  9. package/build/components/page/component_error_boundary.d.ts.map +1 -0
  10. package/build/components/page/component_error_boundary.js +8 -0
  11. package/build/components/page/component_error_boundary.js.map +1 -0
  12. package/build/components/page/error_boundary.d.ts +13 -10
  13. package/build/components/page/error_boundary.d.ts.map +1 -1
  14. package/build/components/page/error_boundary.js +20 -90
  15. package/build/components/page/error_boundary.js.map +1 -1
  16. package/build/components/page/page_error_boundary.d.ts +4 -0
  17. package/build/components/page/page_error_boundary.d.ts.map +1 -0
  18. package/build/components/page/page_error_boundary.js +80 -0
  19. package/build/components/page/page_error_boundary.js.map +1 -0
  20. package/build/navigation/screenLayout.d.ts.map +1 -1
  21. package/build/navigation/screenLayout.js +5 -3
  22. package/build/navigation/screenLayout.js.map +1 -1
  23. package/build/utils/client/instrumented_fetch.d.ts +2 -0
  24. package/build/utils/client/instrumented_fetch.d.ts.map +1 -0
  25. package/build/utils/client/instrumented_fetch.js +64 -0
  26. package/build/utils/client/instrumented_fetch.js.map +1 -0
  27. package/build/utils/client/request_helpers.d.ts.map +1 -1
  28. package/build/utils/client/request_helpers.js +2 -1
  29. package/build/utils/client/request_helpers.js.map +1 -1
  30. package/build/utils/native_adapters/log.d.ts +1 -1
  31. package/build/utils/native_adapters/log.d.ts.map +1 -1
  32. package/build/utils/native_adapters/log.js.map +1 -1
  33. package/package.json +2 -2
  34. package/src/components/conversation/message.tsx +6 -4
  35. package/src/components/index.tsx +2 -0
  36. package/src/components/page/__tests__/component_error_boundary.test.tsx +46 -0
  37. package/src/components/page/__tests__/error_boundary.test.tsx +93 -0
  38. package/src/components/page/__tests__/page_error_boundary.test.tsx +77 -0
  39. package/src/components/page/component_error_boundary.tsx +13 -0
  40. package/src/components/page/error_boundary.tsx +34 -118
  41. package/src/components/page/page_error_boundary.tsx +112 -0
  42. package/src/navigation/screenLayout.tsx +6 -3
  43. package/src/utils/client/__tests__/instrumented_fetch.test.ts +84 -0
  44. package/src/utils/client/instrumented_fetch.ts +69 -0
  45. package/src/utils/client/request_helpers.ts +2 -1
  46. package/src/utils/native_adapters/log.ts +1 -1
@@ -0,0 +1,64 @@
1
+ import { Log } from '../native_adapters/configuration';
2
+ const SHARED_TAGS = {
3
+ team: 'chat',
4
+ package: 'chat-react-native',
5
+ };
6
+ const SKIPPED_STATUSES = [401, 403, 404];
7
+ export async function instrumentedFetch(url, init) {
8
+ const method = init.method ?? 'GET';
9
+ try {
10
+ const response = await fetch(url, init);
11
+ if (!response.ok)
12
+ reportHttpError(response, method, url);
13
+ return response;
14
+ }
15
+ catch (networkError) {
16
+ reportNetworkError(networkError, method, url);
17
+ throw networkError;
18
+ }
19
+ }
20
+ function reportHttpError(response, method, url) {
21
+ const { status } = response;
22
+ if (SKIPPED_STATUSES.includes(status))
23
+ return;
24
+ const path = templatePath(url);
25
+ const error = new Error(`HTTP ${status} ${method} ${path}`);
26
+ error.name = `HTTPError${status}`;
27
+ Log.reportError(error, {
28
+ scope: 'http',
29
+ tags: {
30
+ ...SHARED_TAGS,
31
+ 'http.status': String(status),
32
+ 'http.method': method,
33
+ 'http.path': path,
34
+ },
35
+ });
36
+ }
37
+ function reportNetworkError(networkError, method, url) {
38
+ const path = templatePath(url);
39
+ const error = new Error(`Network failure ${method} ${path}: ${networkError.message}`);
40
+ error.name = 'NetworkError';
41
+ Log.reportError(error, {
42
+ scope: 'http',
43
+ tags: {
44
+ ...SHARED_TAGS,
45
+ 'http.method': method,
46
+ 'http.path': path,
47
+ 'http.error': 'network',
48
+ },
49
+ });
50
+ }
51
+ function templatePath(url) {
52
+ let path;
53
+ try {
54
+ path = new URL(url).pathname;
55
+ }
56
+ catch {
57
+ path = url;
58
+ }
59
+ return path
60
+ .split('/')
61
+ .map(segment => (/^\d+$/.test(segment) ? ':id' : segment))
62
+ .join('/');
63
+ }
64
+ //# sourceMappingURL=instrumented_fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumented_fetch.js","sourceRoot":"","sources":["../../../src/utils/client/instrumented_fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAA;AAEtD,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,mBAAmB;CACpB,CAAA;AAEV,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAExC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAiB;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACvC,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACxD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,OAAO,YAAY,EAAE,CAAC;QACtB,kBAAkB,CAAC,YAAqB,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACtD,MAAM,YAAY,CAAA;IACpB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,QAAkB,EAAE,MAAc,EAAE,GAAW;IACtE,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAA;IAC3B,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAM;IAE7C,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC,CAAA;IAC3D,KAAK,CAAC,IAAI,GAAG,YAAY,MAAM,EAAE,CAAA;IAEjC,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM;QACb,IAAI,EAAE;YACJ,GAAG,WAAW;YACd,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC;YAC7B,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,IAAI;SAClB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,YAAmB,EAAE,MAAc,EAAE,GAAW;IAC1E,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,mBAAmB,MAAM,IAAI,IAAI,KAAK,YAAY,CAAC,OAAO,EAAE,CAAC,CAAA;IACrF,KAAK,CAAC,IAAI,GAAG,cAAc,CAAA;IAE3B,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM;QACb,IAAI,EAAE;YACJ,GAAG,WAAW;YACd,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,SAAS;SACxB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,GAAG,CAAA;IACZ,CAAC;IAED,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;SACzD,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC","sourcesContent":["import { Log } from '../native_adapters/configuration'\n\nconst SHARED_TAGS = {\n team: 'chat',\n package: 'chat-react-native',\n} as const\n\nconst SKIPPED_STATUSES = [401, 403, 404]\n\nexport async function instrumentedFetch(url: string, init: RequestInit): Promise<Response> {\n const method = init.method ?? 'GET'\n try {\n const response = await fetch(url, init)\n if (!response.ok) reportHttpError(response, method, url)\n return response\n } catch (networkError) {\n reportNetworkError(networkError as Error, method, url)\n throw networkError\n }\n}\n\nfunction reportHttpError(response: Response, method: string, url: string) {\n const { status } = response\n if (SKIPPED_STATUSES.includes(status)) return\n\n const path = templatePath(url)\n const error = new Error(`HTTP ${status} ${method} ${path}`)\n error.name = `HTTPError${status}`\n\n Log.reportError(error, {\n scope: 'http',\n tags: {\n ...SHARED_TAGS,\n 'http.status': String(status),\n 'http.method': method,\n 'http.path': path,\n },\n })\n}\n\nfunction reportNetworkError(networkError: Error, method: string, url: string) {\n const path = templatePath(url)\n const error = new Error(`Network failure ${method} ${path}: ${networkError.message}`)\n error.name = 'NetworkError'\n\n Log.reportError(error, {\n scope: 'http',\n tags: {\n ...SHARED_TAGS,\n 'http.method': method,\n 'http.path': path,\n 'http.error': 'network',\n },\n })\n}\n\nfunction templatePath(url: string): string {\n let path: string\n try {\n path = new URL(url).pathname\n } catch {\n path = url\n }\n\n return path\n .split('/')\n .map(segment => (/^\\d+$/.test(segment) ? ':id' : segment))\n .join('/')\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"request_helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/client/request_helpers.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAExF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;IAC3C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,gCAAkD,eAAe,iBAgC5F,CAAA;AAcD,eAAO,MAAM,aAAa,GAAI,SAAS,WAAW,EAAE,aAAa,WAAW;;;;CAM1E,CAAA;AAEF,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;AACjE,KAAK,QAAQ,GAAG,eAAe,GAAG;IAChC,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAC7B,CAAA;CACF,CAAA;AAED,eAAO,MAAM,yBAAyB,GAAU,MAAM,IAAI,EAAE,MAAM,QAAQ,iBAuBzE,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,MAAM,IAAI,EAAE,MAAM,QAAQ,iBAG7B,CAAA;AAE5B,eAAO,MAAM,2BAA2B,GAAI,SAAS,OAAO,WAAW,EAAE,MAAM,QAAQ,iBAG3D,CAAA;AAK5B,eAAO,MAAM,iBAAiB,SAbO,IAAI,QAAQ,QAAQ,iBAac,CAAA;AAEvE,eAAO,MAAM,wBAAwB,YAVgB,OAAO,WAAW,QAAQ,QAAQ,iBAUI,CAAA;AAE3F,eAAO,MAAM,uBAAuB,GAAI,KAAK,MAAM,kBAMlD,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,MAAM,UAAU,KAAG,WAkBrD,CAAA"}
1
+ {"version":3,"file":"request_helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/client/request_helpers.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAExF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;IAC3C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,gCAAkD,eAAe,iBAgC5F,CAAA;AAcD,eAAO,MAAM,aAAa,GAAI,SAAS,WAAW,EAAE,aAAa,WAAW;;;;CAM1E,CAAA;AAEF,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;AACjE,KAAK,QAAQ,GAAG,eAAe,GAAG;IAChC,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAC7B,CAAA;CACF,CAAA;AAED,eAAO,MAAM,yBAAyB,GAAU,MAAM,IAAI,EAAE,MAAM,QAAQ,iBAuBzE,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,MAAM,IAAI,EAAE,MAAM,QAAQ,iBAG7B,CAAA;AAE5B,eAAO,MAAM,2BAA2B,GAAI,SAAS,OAAO,WAAW,EAAE,MAAM,QAAQ,iBAG3D,CAAA;AAK5B,eAAO,MAAM,iBAAiB,SAbO,IAAI,QAAQ,QAAQ,iBAac,CAAA;AAEvE,eAAO,MAAM,wBAAwB,YAVgB,OAAO,WAAW,QAAQ,QAAQ,iBAUI,CAAA;AAE3F,eAAO,MAAM,uBAAuB,GAAI,KAAK,MAAM,kBAMlD,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,MAAM,UAAU,KAAG,WAkBrD,CAAA"}
@@ -1,5 +1,6 @@
1
1
  import _ from 'lodash';
2
2
  import URI from 'urijs';
3
+ import { instrumentedFetch } from './instrumented_fetch';
3
4
  import { transformRequestData } from './transform_request_data';
4
5
  import transformResponse from './transform_response';
5
6
  export const makeRequest = ({ action = 'GET', url, data = {}, headers = {} }) => {
@@ -19,7 +20,7 @@ export const makeRequest = ({ action = 'GET', url, data = {}, headers = {} }) =>
19
20
  uri = uri.query(combinedQuery);
20
21
  }
21
22
  const body = ['POST', 'PATCH'].includes(action) ? JSON.stringify(data) : undefined;
22
- return fetch(decodeURIComponent(uri.toString()), {
23
+ return instrumentedFetch(decodeURIComponent(uri.toString()), {
23
24
  method: action,
24
25
  headers,
25
26
  body,
@@ -1 +1 @@
1
- {"version":3,"file":"request_helpers.js","sourceRoot":"","sources":["../../../src/utils/client/request_helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import _ from 'lodash'\nimport URI from 'urijs'\nimport { transformRequestData } from './transform_request_data'\nimport transformResponse from './transform_response'\nimport { Accumulator, GetRequest, PostRequest, RequestData, WalkRequest } from './types'\n\nexport type MakeRequestArgs = {\n action: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n url: string\n data?: Partial<RequestData>\n headers: Record<string, string>\n}\n\nexport const makeRequest = ({ action = 'GET', url, data = {}, headers = {} }: MakeRequestArgs) => {\n // break apart url so we can reconstruct the url with the query params\n let uri = new URI(url)\n const query = transformRequestData(data)\n const urlQuery = uri.query(true)\n\n // This likely doesn't matter but will enforce consistent ordering of query params\n const combinedQuery = Object.entries({ ...query, ...urlQuery })\n .sort()\n .reduce((obj, [key, value]) => {\n // @ts-ignore\n obj[key] = value\n return obj\n }, {})\n\n if (action === 'GET') {\n uri = uri.query(combinedQuery)\n }\n\n const body = ['POST', 'PATCH'].includes(action) ? JSON.stringify(data) : undefined\n\n return fetch(decodeURIComponent(uri.toString()), {\n method: action,\n headers,\n body,\n }).then(response => {\n if (response.ok) {\n return response.text().then(t => (t === '' ? '' : JSON.parse(t)))\n } else {\n return Promise.reject(response)\n }\n })\n}\n\nconst STANDARD_META_ATTRIBUTES = [\n 'total_count',\n 'count',\n 'prev',\n 'next',\n 'can_order_by',\n 'can_query_by',\n 'can_include',\n 'can_filter',\n 'parent',\n]\n\nexport const concatRecords = (records: Accumulator, moreRecords: Accumulator) => ({\n data: [...records.data, ...moreRecords.data],\n included: [...records.included, ...moreRecords.included],\n // Because custom meta can be computed on a per-page bases,\n // it is safer to remove all custom meta when concatenating multiple pages of data\n meta: _.pick(moreRecords.meta, STANDARD_META_ATTRIBUTES),\n})\n\ntype Walk = (args: MakeRequestArgs | WalkRequest) => Promise<any>\ntype WalkArgs = MakeRequestArgs & {\n data?: {\n fields?: Record<string, any>\n }\n}\n\nexport const throwErrorIfFieldsMissing = async (walk: Walk, args: WalkArgs) => {\n const fields = args?.data?.fields\n\n if (!fields) {\n throw new Error(`Must pass fields for request: ${args.url}`)\n }\n\n const fieldTypes = Object.keys(fields)\n\n const response = await walk(args)\n\n const dataTypes = _(response.data).castArray().map('type').value()\n const includedTypes = _.map(response.included, 'type')\n const responseTypes = _.uniq(dataTypes.concat(includedTypes))\n const missingFields = _.difference(responseTypes, fieldTypes).filter(t => t)\n\n if (missingFields.length > 0) {\n throw new Error(\n `Must include fields for types: ${JSON.stringify(missingFields)} for request ${args.url}`\n )\n }\n\n return transformResponse(response)\n}\n\nexport const friendlyErrors = (walk: Walk, args: WalkArgs) =>\n throwErrorIfQueryParams(args.url)\n .then(() => throwErrorIfFieldsMissing(walk, args))\n .then(transformResponse)\n\nexport const noQueryParamsFriendlyErrors = (request: typeof makeRequest, args: WalkArgs) =>\n throwErrorIfQueryParams(args.url)\n .then(() => request(args))\n .then(transformResponse)\n\nconst passthrough = async <T, R>(walk: (args: T) => Promise<R>, args: T) =>\n Promise.resolve(transformResponse(await walk(args)))\n\nexport const ensureFieldsInDev = __DEV__ ? friendlyErrors : passthrough\n\nexport const ensureNoQueryParamsInDev = __DEV__ ? noQueryParamsFriendlyErrors : passthrough\n\nexport const throwErrorIfQueryParams = (url: string) => {\n if (new URI(url).search().length) {\n throw new Error('Must pass query params as data arg')\n }\n\n return Promise.resolve()\n}\n\nexport const transformGetToPost = (args: GetRequest): PostRequest => {\n const { data, ...rest } = args\n const { fields, include } = data\n const fieldsArray = Object.entries(fields).map(([key, value]) => {\n if (Array.isArray(value)) {\n return [key, value.join(',')]\n }\n\n return [key, value]\n })\n\n return {\n ...rest,\n data: {\n fields: Object.fromEntries(fieldsArray),\n include,\n },\n }\n}\n"]}
1
+ {"version":3,"file":"request_helpers.js","sourceRoot":"","sources":["../../../src/utils/client/request_helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import _ from 'lodash'\nimport URI from 'urijs'\nimport { instrumentedFetch } from './instrumented_fetch'\nimport { transformRequestData } from './transform_request_data'\nimport transformResponse from './transform_response'\nimport { Accumulator, GetRequest, PostRequest, RequestData, WalkRequest } from './types'\n\nexport type MakeRequestArgs = {\n action: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n url: string\n data?: Partial<RequestData>\n headers: Record<string, string>\n}\n\nexport const makeRequest = ({ action = 'GET', url, data = {}, headers = {} }: MakeRequestArgs) => {\n // break apart url so we can reconstruct the url with the query params\n let uri = new URI(url)\n const query = transformRequestData(data)\n const urlQuery = uri.query(true)\n\n // This likely doesn't matter but will enforce consistent ordering of query params\n const combinedQuery = Object.entries({ ...query, ...urlQuery })\n .sort()\n .reduce((obj, [key, value]) => {\n // @ts-ignore\n obj[key] = value\n return obj\n }, {})\n\n if (action === 'GET') {\n uri = uri.query(combinedQuery)\n }\n\n const body = ['POST', 'PATCH'].includes(action) ? JSON.stringify(data) : undefined\n\n return instrumentedFetch(decodeURIComponent(uri.toString()), {\n method: action,\n headers,\n body,\n }).then(response => {\n if (response.ok) {\n return response.text().then(t => (t === '' ? '' : JSON.parse(t)))\n } else {\n return Promise.reject(response)\n }\n })\n}\n\nconst STANDARD_META_ATTRIBUTES = [\n 'total_count',\n 'count',\n 'prev',\n 'next',\n 'can_order_by',\n 'can_query_by',\n 'can_include',\n 'can_filter',\n 'parent',\n]\n\nexport const concatRecords = (records: Accumulator, moreRecords: Accumulator) => ({\n data: [...records.data, ...moreRecords.data],\n included: [...records.included, ...moreRecords.included],\n // Because custom meta can be computed on a per-page bases,\n // it is safer to remove all custom meta when concatenating multiple pages of data\n meta: _.pick(moreRecords.meta, STANDARD_META_ATTRIBUTES),\n})\n\ntype Walk = (args: MakeRequestArgs | WalkRequest) => Promise<any>\ntype WalkArgs = MakeRequestArgs & {\n data?: {\n fields?: Record<string, any>\n }\n}\n\nexport const throwErrorIfFieldsMissing = async (walk: Walk, args: WalkArgs) => {\n const fields = args?.data?.fields\n\n if (!fields) {\n throw new Error(`Must pass fields for request: ${args.url}`)\n }\n\n const fieldTypes = Object.keys(fields)\n\n const response = await walk(args)\n\n const dataTypes = _(response.data).castArray().map('type').value()\n const includedTypes = _.map(response.included, 'type')\n const responseTypes = _.uniq(dataTypes.concat(includedTypes))\n const missingFields = _.difference(responseTypes, fieldTypes).filter(t => t)\n\n if (missingFields.length > 0) {\n throw new Error(\n `Must include fields for types: ${JSON.stringify(missingFields)} for request ${args.url}`\n )\n }\n\n return transformResponse(response)\n}\n\nexport const friendlyErrors = (walk: Walk, args: WalkArgs) =>\n throwErrorIfQueryParams(args.url)\n .then(() => throwErrorIfFieldsMissing(walk, args))\n .then(transformResponse)\n\nexport const noQueryParamsFriendlyErrors = (request: typeof makeRequest, args: WalkArgs) =>\n throwErrorIfQueryParams(args.url)\n .then(() => request(args))\n .then(transformResponse)\n\nconst passthrough = async <T, R>(walk: (args: T) => Promise<R>, args: T) =>\n Promise.resolve(transformResponse(await walk(args)))\n\nexport const ensureFieldsInDev = __DEV__ ? friendlyErrors : passthrough\n\nexport const ensureNoQueryParamsInDev = __DEV__ ? noQueryParamsFriendlyErrors : passthrough\n\nexport const throwErrorIfQueryParams = (url: string) => {\n if (new URI(url).search().length) {\n throw new Error('Must pass query params as data arg')\n }\n\n return Promise.resolve()\n}\n\nexport const transformGetToPost = (args: GetRequest): PostRequest => {\n const { data, ...rest } = args\n const { fields, include } = data\n const fieldsArray = Object.entries(fields).map(([key, value]) => {\n if (Array.isArray(value)) {\n return [key, value.join(',')]\n }\n\n return [key, value]\n })\n\n return {\n ...rest,\n data: {\n fields: Object.fromEntries(fieldsArray),\n include,\n },\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- export type ReportErrorScope = 'screen' | 'message' | 'http';
1
+ export type ReportErrorScope = 'screen' | 'component' | 'http';
2
2
  export type ReportErrorContext = {
3
3
  componentStack?: string;
4
4
  scope?: ReportErrorScope;
@@ -1 +1 @@
1
- {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/log.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;AAE5D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC,CAAA;AAED,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAChD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC/C,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAA;CAClE;AAED,qBAAa,UAAU;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACnC,MAAM,CAAC,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAA;IACjC,aAAa,CAAC,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;gBAEnC,MAAM,GAAE,OAAO,CAAC,gBAAgB,CAAM;IAOlD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAMrC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAMpC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,kBAAkB;CAQvD"}
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/log.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAA;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC,CAAA;AAED,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAChD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC/C,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAA;CAClE;AAED,qBAAa,UAAU;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACnC,MAAM,CAAC,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAA;IACjC,aAAa,CAAC,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;gBAEnC,MAAM,GAAE,OAAO,CAAC,gBAAgB,CAAM;IAOlD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAMrC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAMpC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,kBAAkB;CAQvD"}
@@ -1 +1 @@
1
- {"version":3,"file":"log.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/log.ts"],"names":[],"mappings":"AAiBA,MAAM,OAAO,UAAU;IACrB,OAAO,CAAS;IAChB,OAAO,CAA4B;IACnC,MAAM,CAA2B;IACjC,aAAa,CAAkC;IAE/C,YAAY,SAAoC,EAAE;QAChD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,KAAK,CAAA;QACtC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,CAAA;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAA;QACzB,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,WAAW,CAAA;IACzC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAW;QACnC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAM;QAEzB,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAW;QAClC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAM;QAEzB,IAAI,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAA;IACjC,CAAC;IAED,WAAW,CAAC,KAAY,EAAE,OAA4B;QACpD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC/B,CAAC;CACF","sourcesContent":["export type ReportErrorScope = 'screen' | 'message' | 'http'\n\nexport type ReportErrorContext = {\n componentStack?: string\n scope?: ReportErrorScope\n screenName?: string\n tags?: Record<string, string>\n extra?: Record<string, unknown>\n}\n\ninterface LogAdapterConfig {\n enabled: boolean\n error: (message: string, ...args: any[]) => void\n info: (message: string, ...args: any[]) => void\n reportError: (error: Error, context?: ReportErrorContext) => void\n}\n\nexport class LogAdapter {\n enabled: boolean\n onError?: LogAdapterConfig['error']\n onInfo?: LogAdapterConfig['info']\n onReportError?: LogAdapterConfig['reportError']\n\n constructor(config: Partial<LogAdapterConfig> = {}) {\n this.enabled = config.enabled ?? false\n this.onError = config.error\n this.onInfo = config.info\n this.onReportError = config.reportError\n }\n\n error(message: string, ...args: any[]) {\n if (!this.enabled) return\n\n this.onError?.(message, ...args)\n }\n\n info(message: string, ...args: any[]) {\n if (!this.enabled) return\n\n this.onInfo?.(message, ...args)\n }\n\n reportError(error: Error, context?: ReportErrorContext) {\n if (this.onReportError) {\n this.onReportError(error, context)\n return\n }\n\n console.error(error, context)\n }\n}\n"]}
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/log.ts"],"names":[],"mappings":"AAiBA,MAAM,OAAO,UAAU;IACrB,OAAO,CAAS;IAChB,OAAO,CAA4B;IACnC,MAAM,CAA2B;IACjC,aAAa,CAAkC;IAE/C,YAAY,SAAoC,EAAE;QAChD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,KAAK,CAAA;QACtC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,CAAA;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAA;QACzB,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,WAAW,CAAA;IACzC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAW;QACnC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAM;QAEzB,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAW;QAClC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAM;QAEzB,IAAI,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAA;IACjC,CAAC;IAED,WAAW,CAAC,KAAY,EAAE,OAA4B;QACpD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC/B,CAAC;CACF","sourcesContent":["export type ReportErrorScope = 'screen' | 'component' | 'http'\n\nexport type ReportErrorContext = {\n componentStack?: string\n scope?: ReportErrorScope\n screenName?: string\n tags?: Record<string, string>\n extra?: Record<string, unknown>\n}\n\ninterface LogAdapterConfig {\n enabled: boolean\n error: (message: string, ...args: any[]) => void\n info: (message: string, ...args: any[]) => void\n reportError: (error: Error, context?: ReportErrorContext) => void\n}\n\nexport class LogAdapter {\n enabled: boolean\n onError?: LogAdapterConfig['error']\n onInfo?: LogAdapterConfig['info']\n onReportError?: LogAdapterConfig['reportError']\n\n constructor(config: Partial<LogAdapterConfig> = {}) {\n this.enabled = config.enabled ?? false\n this.onError = config.error\n this.onInfo = config.info\n this.onReportError = config.reportError\n }\n\n error(message: string, ...args: any[]) {\n if (!this.enabled) return\n\n this.onError?.(message, ...args)\n }\n\n info(message: string, ...args: any[]) {\n if (!this.enabled) return\n\n this.onInfo?.(message, ...args)\n }\n\n reportError(error: Error, context?: ReportErrorContext) {\n if (this.onReportError) {\n this.onReportError(error, context)\n return\n }\n\n console.error(error, context)\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.37.0-rc.2",
3
+ "version": "3.37.0-rc.4",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "react-native": "./src/index.tsx",
@@ -72,5 +72,5 @@
72
72
  "react-native-url-polyfill": "^2.0.0",
73
73
  "typescript": "~5.9.2"
74
74
  },
75
- "gitHead": "13e581dd9640a86feea850c6f0d1c34e7b0357cd"
75
+ "gitHead": "47b2675fc5a5c7b4dcca6a62690fe01a4e4151f1"
76
76
  }
@@ -31,7 +31,7 @@ import {
31
31
  MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
32
32
  platformFontWeightMedium,
33
33
  } from '../../utils/styles'
34
- import ErrorBoundary from '../page/error_boundary'
34
+ import { ComponentErrorBoundary } from '../page/component_error_boundary'
35
35
  import { MessageAttachments } from './message_attachments'
36
36
  import { MessageMarkdown } from './message_markdown'
37
37
  import { MessageReadReceipts } from './message_read_receipts'
@@ -103,7 +103,9 @@ export function Message({
103
103
  const messageIsReplyLabel = replyToReplyRootMessage ? 'Reply' : ''
104
104
  const attachmentLabel = some(attachments) ? pluralize(attachments.length, 'attachment') : ''
105
105
  const replyCountLabel = isReplyRootMessage ? replyCountText : ''
106
- const accessibilityLabel = `${author?.name || ''} ${messageIsReplyLabel} ${attachmentLabel} ${messageText || ''} ${timestamp} ${replyCountLabel}`
106
+ const accessibilityLabel = `${author?.name || ''} ${messageIsReplyLabel} ${attachmentLabel} ${
107
+ messageText || ''
108
+ } ${timestamp} ${replyCountLabel}`
107
109
 
108
110
  useEffect(() => {
109
111
  if (pending) {
@@ -237,14 +239,14 @@ export function Message({
237
239
  </View>
238
240
  ) : (
239
241
  <>
240
- <ErrorBoundary>
242
+ <ComponentErrorBoundary>
241
243
  <MessageAttachments
242
244
  attachments={attachments}
243
245
  metaProps={metaProps}
244
246
  onMessageAttachmentLongPress={handleMessageAttachmentLongPress}
245
247
  onMessageLongPress={handleMessageLongPress}
246
248
  />
247
- </ErrorBoundary>
249
+ </ComponentErrorBoundary>
248
250
  {text && (
249
251
  <View style={styles.messageText}>
250
252
  <MessageMarkdown text={text} />
@@ -1,4 +1,6 @@
1
1
  export * from './conversations/conversations'
2
+ export * from './page/component_error_boundary'
2
3
  export * from './page/error_boundary'
4
+ export * from './page/page_error_boundary'
3
5
  export * from './display'
4
6
  export * from './group_conversation_list'
@@ -0,0 +1,46 @@
1
+ import { render } from '@testing-library/react-native'
2
+ import React from 'react'
3
+ import { Text } from 'react-native'
4
+ import { Log } from '../../../utils/native_adapters/configuration'
5
+ import { ComponentErrorBoundary } from '../component_error_boundary'
6
+
7
+ function Boom({ thrown }: { thrown: unknown }) {
8
+ throw thrown
9
+ }
10
+
11
+ describe('ComponentErrorBoundary', () => {
12
+ let reportError: jest.SpyInstance
13
+ let consoleError: jest.SpyInstance
14
+
15
+ beforeEach(() => {
16
+ reportError = jest.spyOn(Log, 'reportError').mockImplementation(() => {})
17
+ consoleError = jest.spyOn(console, 'error').mockImplementation(() => {})
18
+ })
19
+
20
+ afterEach(() => {
21
+ reportError.mockRestore()
22
+ consoleError.mockRestore()
23
+ })
24
+
25
+ it('defaults the reporter scope to "component" and renders nothing on catch', () => {
26
+ const { toJSON } = render(
27
+ <ComponentErrorBoundary>
28
+ <Boom thrown={new Error('attachment boom')} />
29
+ </ComponentErrorBoundary>
30
+ )
31
+
32
+ expect(toJSON()).toBeNull()
33
+ expect(reportError.mock.calls[0][1]).toMatchObject({ scope: 'component' })
34
+ })
35
+
36
+ it('lets callers override the defaults', () => {
37
+ const { getByText } = render(
38
+ <ComponentErrorBoundary scope="screen" fallback={<Text>overridden</Text>}>
39
+ <Boom thrown={new Error('boom')} />
40
+ </ComponentErrorBoundary>
41
+ )
42
+
43
+ expect(getByText('overridden')).toBeTruthy()
44
+ expect(reportError.mock.calls[0][1]).toMatchObject({ scope: 'screen' })
45
+ })
46
+ })
@@ -0,0 +1,93 @@
1
+ import { render } from '@testing-library/react-native'
2
+ import React from 'react'
3
+ import { Text } from 'react-native'
4
+ import { FailedResponse } from '../../../types/api_primitives'
5
+ import { Log } from '../../../utils/native_adapters/configuration'
6
+ import { ResponseError } from '../../../utils/response_error'
7
+ import { ErrorBoundary } from '../error_boundary'
8
+
9
+ function Boom({ thrown }: { thrown: unknown }) {
10
+ throw thrown
11
+ }
12
+
13
+ describe('ErrorBoundary', () => {
14
+ let reportError: jest.SpyInstance
15
+ let consoleError: jest.SpyInstance
16
+
17
+ beforeEach(() => {
18
+ reportError = jest.spyOn(Log, 'reportError').mockImplementation(() => {})
19
+ consoleError = jest.spyOn(console, 'error').mockImplementation(() => {})
20
+ })
21
+
22
+ afterEach(() => {
23
+ reportError.mockRestore()
24
+ consoleError.mockRestore()
25
+ })
26
+
27
+ it('reports caught errors with the component stack and chat tags', () => {
28
+ const error = new TypeError('Invalid time value')
29
+
30
+ render(
31
+ <ErrorBoundary scope="screen" screenName="ConversationScreen" fallback={null}>
32
+ <Boom thrown={error} />
33
+ </ErrorBoundary>
34
+ )
35
+
36
+ expect(reportError).toHaveBeenCalledTimes(1)
37
+ const [reportedError, context] = reportError.mock.calls[0]
38
+ expect(reportedError).toBe(error)
39
+ expect(context).toMatchObject({
40
+ scope: 'screen',
41
+ screenName: 'ConversationScreen',
42
+ tags: { team: 'chat', package: 'chat-react-native' },
43
+ })
44
+ expect(typeof context.componentStack).toBe('string')
45
+ expect(context.componentStack.length).toBeGreaterThan(0)
46
+ })
47
+
48
+ it('does not report ResponseError — those are owned by the HTTP layer', () => {
49
+ const failure = { status: 500, statusText: 'Server Error', errors: [] } as FailedResponse
50
+
51
+ render(
52
+ <ErrorBoundary fallback={null}>
53
+ <Boom thrown={new ResponseError(failure)} />
54
+ </ErrorBoundary>
55
+ )
56
+
57
+ expect(reportError).not.toHaveBeenCalled()
58
+ })
59
+
60
+ it('renders the provided fallback element when a child throws', () => {
61
+ const { getByText } = render(
62
+ <ErrorBoundary fallback={<Text>custom fallback</Text>}>
63
+ <Boom thrown={new Error('boom')} />
64
+ </ErrorBoundary>
65
+ )
66
+
67
+ expect(getByText('custom fallback')).toBeTruthy()
68
+ })
69
+
70
+ it('renders nothing when no fallback is provided', () => {
71
+ const { toJSON } = render(
72
+ <ErrorBoundary>
73
+ <Boom thrown={new Error('boom')} />
74
+ </ErrorBoundary>
75
+ )
76
+
77
+ expect(toJSON()).toBeNull()
78
+ })
79
+
80
+ it('calls a fallback render function with the caught error and a reset handler', () => {
81
+ const fallback = jest.fn().mockReturnValue(<Text>fn fallback</Text>)
82
+ const error = new Error('boom')
83
+
84
+ const { getByText } = render(
85
+ <ErrorBoundary fallback={fallback}>
86
+ <Boom thrown={error} />
87
+ </ErrorBoundary>
88
+ )
89
+
90
+ expect(getByText('fn fallback')).toBeTruthy()
91
+ expect(fallback).toHaveBeenCalledWith(error, expect.any(Function))
92
+ })
93
+ })
@@ -0,0 +1,77 @@
1
+ import { NavigationContainer } from '@react-navigation/native'
2
+ import { QueryClientProvider } from '@tanstack/react-query'
3
+ import { render } from '@testing-library/react-native'
4
+ import React from 'react'
5
+ import { buildTestQueryClient } from '../../../__utils__/query_client'
6
+ import { Log } from '../../../utils/native_adapters/configuration'
7
+ import { PageErrorBoundary } from '../page_error_boundary'
8
+
9
+ jest.mock('../../primitive/blank_state_primitive', () => {
10
+ const { Text } = require('react-native')
11
+ const { createElement } = require('react')
12
+ const passthrough = ({ children }: { children?: unknown }) => children
13
+ const asText = ({ children }: { children?: unknown }) => createElement(Text, null, children)
14
+ const empty = () => null
15
+ return {
16
+ __esModule: true,
17
+ default: {
18
+ Root: passthrough,
19
+ Imagery: empty,
20
+ Content: passthrough,
21
+ Heading: asText,
22
+ Text: asText,
23
+ Button: empty,
24
+ TextButton: asText,
25
+ },
26
+ }
27
+ })
28
+
29
+ function Boom({ thrown }: { thrown: unknown }) {
30
+ throw thrown
31
+ }
32
+
33
+ const renderWithProviders = (ui: React.ReactElement) =>
34
+ render(
35
+ <QueryClientProvider client={buildTestQueryClient()}>
36
+ <NavigationContainer>{ui}</NavigationContainer>
37
+ </QueryClientProvider>
38
+ )
39
+
40
+ describe('PageErrorBoundary', () => {
41
+ let reportError: jest.SpyInstance
42
+ let consoleError: jest.SpyInstance
43
+
44
+ beforeEach(() => {
45
+ reportError = jest.spyOn(Log, 'reportError').mockImplementation(() => {})
46
+ consoleError = jest.spyOn(console, 'error').mockImplementation(() => {})
47
+ })
48
+
49
+ afterEach(() => {
50
+ reportError.mockRestore()
51
+ consoleError.mockRestore()
52
+ })
53
+
54
+ it('defaults the reporter scope to "screen"', () => {
55
+ renderWithProviders(
56
+ <PageErrorBoundary screenName="ConversationScreen">
57
+ <Boom thrown={new Error('boom')} />
58
+ </PageErrorBoundary>
59
+ )
60
+
61
+ expect(reportError.mock.calls[0][1]).toMatchObject({
62
+ scope: 'screen',
63
+ screenName: 'ConversationScreen',
64
+ })
65
+ })
66
+
67
+ it('renders the full Oops fallback when no fallback is provided', () => {
68
+ const { getByText } = renderWithProviders(
69
+ <PageErrorBoundary>
70
+ <Boom thrown={new Error('boom')} />
71
+ </PageErrorBoundary>
72
+ )
73
+
74
+ expect(getByText('Oops!')).toBeTruthy()
75
+ expect(getByText('Something unexpected happened.')).toBeTruthy()
76
+ })
77
+ })
@@ -0,0 +1,13 @@
1
+ import React, { PropsWithChildren } from 'react'
2
+ import { ErrorBoundary, ErrorBoundaryProps } from './error_boundary'
3
+
4
+ export function ComponentErrorBoundary({
5
+ children,
6
+ ...props
7
+ }: PropsWithChildren<ErrorBoundaryProps>) {
8
+ return (
9
+ <ErrorBoundary scope="component" fallback={null} {...props}>
10
+ {children}
11
+ </ErrorBoundary>
12
+ )
13
+ }
@@ -1,137 +1,53 @@
1
- import { useNavigation } from '@react-navigation/native'
2
- import { useQueryErrorResetBoundary } from '@tanstack/react-query'
3
- import React, { PropsWithChildren, useCallback, useEffect, useMemo } from 'react'
4
- import { onAuthRefresh } from '../../utils/auth_events'
1
+ import React, { PropsWithChildren } from 'react'
2
+ import { Log } from '../../utils/native_adapters/configuration'
3
+ import { ReportErrorScope } from '../../utils/native_adapters/log'
5
4
  import { ResponseError } from '../../utils/response_error'
6
- import BlankState from '../primitive/blank_state_primitive'
5
+
6
+ export type ErrorBoundaryFallback =
7
+ | React.ReactNode
8
+ | ((error: Error, reset: () => void) => React.ReactNode)
9
+
10
+ export type ErrorBoundaryProps = {
11
+ scope?: ReportErrorScope
12
+ screenName?: string
13
+ fallback?: ErrorBoundaryFallback
14
+ }
7
15
 
8
16
  type ErrorBoundaryState = {
9
- error: ResponseError | Error | TypeError | null
10
- unsubscriber: () => void
17
+ error: Error | null
11
18
  }
12
19
 
13
- class ErrorBoundary extends React.Component<PropsWithChildren<{ onReset?: () => void }>> {
14
- state: ErrorBoundaryState = {
15
- error: null,
16
- unsubscriber: () => {},
17
- }
20
+ export class ErrorBoundary extends React.Component<
21
+ PropsWithChildren<ErrorBoundaryProps>,
22
+ ErrorBoundaryState
23
+ > {
24
+ state: ErrorBoundaryState = { error: null }
18
25
 
19
- componentDidCatch(error: any) {
20
- this.handleError(error)
26
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
27
+ return { error }
21
28
  }
22
29
 
23
- handleError(error: any) {
24
- this.setState({ error })
30
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
31
+ if (error instanceof ResponseError) return
32
+
33
+ Log.reportError(error, {
34
+ componentStack: errorInfo.componentStack ?? undefined,
35
+ scope: this.props.scope,
36
+ screenName: this.props.screenName,
37
+ tags: { team: 'chat', package: 'chat-react-native' },
38
+ })
25
39
  }
26
40
 
27
41
  handleReset = () => {
28
- this.props.onReset?.()
29
42
  this.setState({ error: null })
30
43
  }
31
44
 
32
45
  render() {
33
- if (this.state.error) {
34
- return <ErrorView error={this.state.error} onReset={this.handleReset} />
35
- } else {
36
- return this.props.children
37
- }
38
- }
39
- }
46
+ if (!this.state.error) return this.props.children
40
47
 
41
- function ErrorView({ error, onReset }: { error: Error | ResponseError; onReset: () => void }) {
42
- const { reset } = useQueryErrorResetBoundary()
48
+ const { fallback } = this.props
49
+ if (fallback === undefined) return null
43
50
 
44
- const handleReset = useCallback(() => {
45
- reset()
46
- onReset()
47
- }, [reset, onReset])
48
-
49
- useEffect(() => handleReset, [handleReset])
50
-
51
- if (error instanceof ResponseError) {
52
- return <ResponseErrorView response={error.response} onReset={handleReset} />
53
- }
54
-
55
- if (isNetworkError(error)) {
56
- return (
57
- <ErrorContent
58
- heading={'Problem connecting!'}
59
- body={'Check your internet connection and try again.'}
60
- />
61
- )
51
+ return typeof fallback === 'function' ? fallback(this.state.error, this.handleReset) : fallback
62
52
  }
63
-
64
- return <ErrorContent heading={'Oops!'} body={'Something unexpected happened.'} />
65
53
  }
66
-
67
- function isNetworkError(error: ResponseError | Error | TypeError | null) {
68
- const isError = error instanceof Error
69
- const networkFailedMessages = [
70
- 'Network request failed',
71
- 'Network request timed out',
72
- 'Failed to fetch',
73
- ]
74
-
75
- if (!isError) return false
76
-
77
- return new RegExp(networkFailedMessages.join('|'), 'i').test(error.message)
78
- }
79
-
80
- function ResponseErrorView({ response, onReset }: { response: Response; onReset: () => void }) {
81
- const { status } = response
82
-
83
- const heading = useMemo(() => {
84
- switch (status) {
85
- case 403:
86
- return 'Permission required'
87
- case 404:
88
- return 'Content not found'
89
- default:
90
- return 'Oops!'
91
- }
92
- }, [status])
93
-
94
- const body = useMemo(() => {
95
- switch (status) {
96
- case 403:
97
- return 'Contact your administrator for access.'
98
- case 404:
99
- return 'If you believe something should be here, please reach out to your administrator.'
100
- default:
101
- return 'Something unexpected happened.'
102
- }
103
- }, [status])
104
-
105
- useEffect(() => {
106
- if (status !== 401) return
107
-
108
- return onAuthRefresh(onReset)
109
- }, [status, onReset])
110
-
111
- return <ErrorContent heading={heading} body={body} />
112
- }
113
-
114
- function ErrorContent({ heading, body }: { heading: string; body: string }) {
115
- const navigation = useNavigation()
116
-
117
- return (
118
- <BlankState.Root>
119
- <BlankState.Imagery name="people.noTextMessage" />
120
- <BlankState.Content>
121
- <BlankState.Heading>{heading}</BlankState.Heading>
122
- <BlankState.Text>{body}</BlankState.Text>
123
- </BlankState.Content>
124
- <BlankState.Button
125
- title="Go back"
126
- onPress={navigation.goBack}
127
- size="md"
128
- accessibilityRole="link"
129
- />
130
- <BlankState.TextButton onPress={() => navigation.navigate('BugReport')}>
131
- Report a bug
132
- </BlankState.TextButton>
133
- </BlankState.Root>
134
- )
135
- }
136
-
137
- export default ErrorBoundary