@planningcenter/chat-react-native 2.2.2-rc.0 → 2.3.0-rc.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.
- package/build/components/conversations.d.ts.map +1 -1
- package/build/components/conversations.js +4 -0
- package/build/components/conversations.js.map +1 -1
- package/build/components/display/action_button.d.ts +8 -0
- package/build/components/display/action_button.d.ts.map +1 -0
- package/build/components/display/action_button.js +44 -0
- package/build/components/display/action_button.js.map +1 -0
- package/build/contexts/api_provider.js +2 -2
- package/build/contexts/api_provider.js.map +1 -1
- package/build/contexts/chat_context.d.ts +2 -1
- package/build/contexts/chat_context.d.ts.map +1 -1
- package/build/contexts/chat_context.js +3 -1
- package/build/contexts/chat_context.js.map +1 -1
- package/build/hooks/index.d.ts +2 -0
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +2 -0
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/use_api.d.ts +383 -0
- package/build/hooks/use_api.d.ts.map +1 -0
- package/build/hooks/use_api.js +40 -0
- package/build/hooks/use_api.js.map +1 -0
- package/build/hooks/use_api_client.d.ts +5 -3
- package/build/hooks/use_api_client.d.ts.map +1 -1
- package/build/hooks/use_api_client.js +1 -1
- package/build/hooks/use_api_client.js.map +1 -1
- package/build/hooks/use_chat_permissions.d.ts +172 -0
- package/build/hooks/use_chat_permissions.d.ts.map +1 -0
- package/build/hooks/use_chat_permissions.js +17 -0
- package/build/hooks/use_chat_permissions.js.map +1 -0
- package/build/hooks/use_conversation.d.ts.map +1 -1
- package/build/hooks/use_conversation.js +6 -5
- package/build/hooks/use_conversation.js.map +1 -1
- package/build/hooks/use_suspense_api.d.ts +13 -4
- package/build/hooks/use_suspense_api.d.ts.map +1 -1
- package/build/hooks/use_suspense_api.js +1 -0
- package/build/hooks/use_suspense_api.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/navigation/index.d.ts +123 -2
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +40 -4
- package/build/navigation/index.js.map +1 -1
- package/build/navigation/screenLayout.js +1 -1
- package/build/navigation/screenLayout.js.map +1 -1
- package/build/screens/create/conversation_create_screen.d.ts +9 -0
- package/build/screens/create/conversation_create_screen.d.ts.map +1 -0
- package/build/screens/create/conversation_create_screen.js +123 -0
- package/build/screens/create/conversation_create_screen.js.map +1 -0
- package/build/screens/create/conversation_filter_recipients_screen.d.ts +8 -0
- package/build/screens/create/conversation_filter_recipients_screen.d.ts.map +1 -0
- package/build/screens/create/conversation_filter_recipients_screen.js +52 -0
- package/build/screens/create/conversation_filter_recipients_screen.js.map +1 -0
- package/build/screens/create/conversation_select_recipients_screen.d.ts +8 -0
- package/build/screens/create/conversation_select_recipients_screen.d.ts.map +1 -0
- package/build/screens/create/conversation_select_recipients_screen.js +105 -0
- package/build/screens/create/conversation_select_recipients_screen.js.map +1 -0
- package/build/types/api_primitives.d.ts +15 -0
- package/build/types/api_primitives.d.ts.map +1 -1
- package/build/types/api_primitives.js.map +1 -1
- package/build/types/resources/app_grant.d.ts +6 -0
- package/build/types/resources/app_grant.d.ts.map +1 -0
- package/build/types/resources/app_grant.js +2 -0
- package/build/types/resources/app_grant.js.map +1 -0
- package/build/types/resources/groups/groups_group_resource.d.ts +12 -0
- package/build/types/resources/groups/groups_group_resource.d.ts.map +1 -0
- package/build/types/resources/groups/groups_group_resource.js +2 -0
- package/build/types/resources/groups/groups_group_resource.js.map +1 -0
- package/build/types/resources/groups/index.d.ts +2 -0
- package/build/types/resources/groups/index.d.ts.map +1 -0
- package/build/types/resources/groups/index.js +2 -0
- package/build/types/resources/groups/index.js.map +1 -0
- package/build/types/resources/index.d.ts +2 -0
- package/build/types/resources/index.d.ts.map +1 -1
- package/build/types/resources/index.js +2 -0
- package/build/types/resources/index.js.map +1 -1
- package/build/utils/client/client.d.ts +8 -5
- package/build/utils/client/client.d.ts.map +1 -1
- package/build/utils/client/client.js +22 -7
- package/build/utils/client/client.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/client.ts +59 -4
- package/src/__tests__/hooks/useTheme.tsx +1 -1
- package/src/components/conversations.tsx +8 -0
- package/src/components/display/action_button.tsx +62 -0
- package/src/contexts/api_provider.tsx +2 -2
- package/src/contexts/chat_context.tsx +5 -2
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use_api.ts +80 -0
- package/src/hooks/use_api_client.ts +13 -15
- package/src/hooks/use_chat_permissions.ts +20 -0
- package/src/hooks/use_conversation.ts +6 -5
- package/src/hooks/use_suspense_api.ts +16 -4
- package/src/index.tsx +1 -1
- package/src/navigation/index.tsx +46 -7
- package/src/navigation/screenLayout.tsx +1 -1
- package/src/screens/create/conversation_create_screen.tsx +148 -0
- package/src/screens/create/conversation_filter_recipients_screen.tsx +79 -0
- package/src/screens/create/conversation_select_recipients_screen.tsx +136 -0
- package/src/types/api_primitives.ts +12 -0
- package/src/types/resources/app_grant.ts +6 -0
- package/src/types/resources/groups/groups_group_resource.ts +12 -0
- package/src/types/resources/groups/index.ts +1 -0
- package/src/types/resources/index.ts +2 -0
- package/src/utils/client/client.ts +34 -11
- package/build/contexts/index.d.ts +0 -3
- package/build/contexts/index.d.ts.map +0 -1
- package/build/contexts/index.js +0 -3
- package/build/contexts/index.js.map +0 -1
- package/src/contexts/index.ts +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/resources/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/resources/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/resources/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA","sourcesContent":["export * from './conversation'\nexport * from './member'\nexport * from './message'\nexport * from './oauth_token'\nexport * from './person'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/resources/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA","sourcesContent":["export * from './conversation'\nexport * from './member'\nexport * from './message'\nexport * from './oauth_token'\nexport * from './person'\nexport * from './groups'\nexport * from './app_grant'\n"]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { ApiCollection, ApiResource } from '../../types';
|
|
1
|
+
import { ApiCollection, ApiError, ApiResource } from '../../types';
|
|
2
2
|
import { Session } from '../session';
|
|
3
3
|
import { Uri } from '../uri';
|
|
4
4
|
import { DeleteRequest, GetRequest, PatchRequest, PostRequest } from './types';
|
|
5
5
|
type ClientArgs = {
|
|
6
6
|
version: string;
|
|
7
7
|
defaultHeaders?: Record<string, string>;
|
|
8
|
-
|
|
8
|
+
onForceLogout?: () => void;
|
|
9
|
+
onTokenExpired?: () => void;
|
|
9
10
|
session: Session;
|
|
10
11
|
app: string;
|
|
11
12
|
};
|
|
@@ -13,13 +14,15 @@ export declare class Client {
|
|
|
13
14
|
version: string;
|
|
14
15
|
defaultHeaders: Record<string, string>;
|
|
15
16
|
uri: Uri;
|
|
16
|
-
onTokenExpired
|
|
17
|
-
|
|
17
|
+
onTokenExpired?: () => void;
|
|
18
|
+
onForceLogout?: () => void;
|
|
19
|
+
constructor({ version, defaultHeaders, session, app, onTokenExpired, onForceLogout, }: ClientArgs);
|
|
18
20
|
get<T extends ApiCollection | ApiResource>(args: GetRequest): Promise<T>;
|
|
19
21
|
patch(args: PatchRequest): Promise<any>;
|
|
20
22
|
post(args: PostRequest): Promise<any>;
|
|
21
23
|
delete(args: DeleteRequest): Promise<any>;
|
|
22
|
-
|
|
24
|
+
handleNotOk: (response: Response) => Promise<never>;
|
|
25
|
+
parseErrorResponse: (response: Response) => Promise<ApiError | undefined>;
|
|
23
26
|
get headers(): {
|
|
24
27
|
'User-Agent': string;
|
|
25
28
|
Authorization: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/utils/client/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/utils/client/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAA;AAU5B,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAe,MAAM,SAAS,CAAA;AAE3F,KAAK,UAAU,GAAG;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,qBAAa,MAAM;IACjB,OAAO,EAAE,MAAM,CAAK;IACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAK;IAC3C,GAAG,EAAE,GAAG,CAAA;IACR,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;gBAEd,EACV,OAAO,EACP,cAAmB,EACnB,OAAO,EACP,GAAG,EACH,cAAc,EACd,aAAa,GACd,EAAE,UAAU;IAQP,GAAG,CAAC,CAAC,SAAS,aAAa,GAAG,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC;IAuCxE,KAAK,CAAC,IAAI,EAAE,YAAY;IASxB,IAAI,CAAC,IAAI,EAAE,WAAW;IAStB,MAAM,CAAC,IAAI,EAAE,aAAa;IAShC,WAAW,aAAoB,QAAQ,oBAWtC;IAED,kBAAkB,aAAoB,QAAQ,KAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAM7E;IAED,IAAI,OAAO;;;;;;MAQV;CACF;AAED,eAAe,MAAM,CAAA"}
|
|
@@ -5,11 +5,13 @@ export class Client {
|
|
|
5
5
|
defaultHeaders = {};
|
|
6
6
|
uri;
|
|
7
7
|
onTokenExpired;
|
|
8
|
-
|
|
8
|
+
onForceLogout;
|
|
9
|
+
constructor({ version, defaultHeaders = {}, session, app, onTokenExpired, onForceLogout, }) {
|
|
9
10
|
this.version = version;
|
|
10
11
|
this.uri = new Uri({ session, app });
|
|
11
12
|
this.defaultHeaders = defaultHeaders;
|
|
12
13
|
this.onTokenExpired = onTokenExpired;
|
|
14
|
+
this.onForceLogout = onForceLogout;
|
|
13
15
|
}
|
|
14
16
|
async get(args) {
|
|
15
17
|
const { walk, ...data } = args.data;
|
|
@@ -37,32 +39,45 @@ export class Client {
|
|
|
37
39
|
});
|
|
38
40
|
};
|
|
39
41
|
const handler = isWalking ? walkRequest : makeRequest;
|
|
40
|
-
return throwErrorIfFieldsMissing(handler, requestArgs).catch(this.
|
|
42
|
+
return throwErrorIfFieldsMissing(handler, requestArgs).catch(this.handleNotOk);
|
|
41
43
|
}
|
|
42
44
|
async patch(args) {
|
|
43
45
|
const headers = { ...this.headers, ...args.headers };
|
|
44
46
|
const url = this.uri.appUrl(args.url);
|
|
45
47
|
const requestArgs = { data: args.data, url, action: 'PATCH', headers };
|
|
46
|
-
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.
|
|
48
|
+
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleNotOk);
|
|
47
49
|
}
|
|
48
50
|
async post(args) {
|
|
49
51
|
const headers = { ...this.headers, ...args.headers };
|
|
50
52
|
const url = this.uri.appUrl(args.url);
|
|
51
53
|
const requestArgs = { ...args, data: args.data, url, action: 'POST', headers };
|
|
52
|
-
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.
|
|
54
|
+
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleNotOk);
|
|
53
55
|
}
|
|
54
56
|
async delete(args) {
|
|
55
57
|
const headers = { ...this.headers, ...args.headers };
|
|
56
58
|
const url = this.uri.appUrl(args.url);
|
|
57
59
|
const requestArgs = { url, action: 'DELETE', headers };
|
|
58
|
-
return makeRequest(requestArgs).catch(this.
|
|
60
|
+
return makeRequest(requestArgs).catch(this.handleNotOk);
|
|
59
61
|
}
|
|
60
|
-
|
|
62
|
+
handleNotOk = async (response) => {
|
|
61
63
|
if (response.status === 401) {
|
|
62
|
-
this.
|
|
64
|
+
const parsed = await this.parseErrorResponse(response);
|
|
65
|
+
const errors = parsed?.errors || [];
|
|
66
|
+
const isTokenExpired = errors.some(e => /baboon/i.test(e.detail));
|
|
67
|
+
const isForceLogout = errors.some(e => /capuchin/i.test(e.detail));
|
|
68
|
+
isTokenExpired && this.onTokenExpired?.();
|
|
69
|
+
isForceLogout && this.onForceLogout?.();
|
|
63
70
|
}
|
|
64
71
|
return Promise.reject(response);
|
|
65
72
|
};
|
|
73
|
+
parseErrorResponse = async (response) => {
|
|
74
|
+
try {
|
|
75
|
+
return (await response.json());
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
66
81
|
get headers() {
|
|
67
82
|
return {
|
|
68
83
|
Accept: 'application/vnd.api+json',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/utils/client/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAA;AAC5B,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,WAAW,EAEX,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/utils/client/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiCollection, ApiError, ApiResource } from '../../types'\nimport { Session } from '../session'\nimport { Uri } from '../uri'\nimport {\n concatRecords,\n ensureNoQueryParamsInDev,\n makeRequest,\n MakeRequestArgs,\n throwErrorIfFieldsMissing,\n throwErrorIfQueryParams,\n} from './request_helpers'\n\nimport { DeleteRequest, GetRequest, PatchRequest, PostRequest, WalkRequest } from './types'\n\ntype ClientArgs = {\n version: string\n defaultHeaders?: Record<string, string>\n onForceLogout?: () => void\n onTokenExpired?: () => void\n session: Session\n app: string\n}\n\nexport class Client {\n version: string = ''\n defaultHeaders: Record<string, string> = {}\n uri: Uri\n onTokenExpired?: () => void\n onForceLogout?: () => void\n\n constructor({\n version,\n defaultHeaders = {},\n session,\n app,\n onTokenExpired,\n onForceLogout,\n }: ClientArgs) {\n this.version = version\n this.uri = new Uri({ session, app })\n this.defaultHeaders = defaultHeaders\n this.onTokenExpired = onTokenExpired\n this.onForceLogout = onForceLogout\n }\n\n async get<T extends ApiCollection | ApiResource>(args: GetRequest): Promise<T> {\n const { walk, ...data } = args.data\n const isWalking = Boolean(walk)\n const headers = { ...this.headers, ...args.headers }\n const url = this.uri.appUrl(args.url)\n\n await throwErrorIfQueryParams(url)\n\n const requestArgs: MakeRequestArgs = { data, url, action: 'GET', headers }\n\n const walkRequest = ({\n url: requestUrl,\n data: d = { fields: {} },\n acc = { data: [], included: [], meta: {}, links: {} },\n ...options\n }: WalkRequest) => {\n return makeRequest({\n action: 'GET',\n data: d,\n url: requestUrl,\n ...options,\n headers,\n }).then(({ links, ...rest }) => {\n const records = Array.isArray(rest.data) ? concatRecords(acc, rest) : rest\n\n // `next` will have our params in the link so we do not want to pass them back in\n if (links?.next) {\n return walkRequest({ ...options, data: d, url: links.next, acc: records })\n } else {\n return records\n }\n })\n }\n\n const handler = isWalking ? walkRequest : makeRequest\n\n return throwErrorIfFieldsMissing(handler, requestArgs).catch(this.handleNotOk)\n }\n\n async patch(args: PatchRequest) {\n const headers = { ...this.headers, ...args.headers }\n const url = this.uri.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { data: args.data, url, action: 'PATCH', headers }\n\n return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleNotOk)\n }\n\n async post(args: PostRequest) {\n const headers = { ...this.headers, ...args.headers }\n const url = this.uri.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { ...args, data: args.data, url, action: 'POST', headers }\n\n return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleNotOk)\n }\n\n async delete(args: DeleteRequest) {\n const headers = { ...this.headers, ...args.headers }\n const url = this.uri.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { url, action: 'DELETE', headers }\n\n return makeRequest(requestArgs).catch(this.handleNotOk)\n }\n\n handleNotOk = async (response: Response) => {\n if (response.status === 401) {\n const parsed = await this.parseErrorResponse(response)\n const errors = parsed?.errors || []\n const isTokenExpired = errors.some(e => /baboon/i.test(e.detail))\n const isForceLogout = errors.some(e => /capuchin/i.test(e.detail))\n isTokenExpired && this.onTokenExpired?.()\n isForceLogout && this.onForceLogout?.()\n }\n\n return Promise.reject(response)\n }\n\n parseErrorResponse = async (response: Response): Promise<ApiError | undefined> => {\n try {\n return (await response.json()) as ApiError\n } catch {\n return undefined\n }\n }\n\n get headers() {\n return {\n Accept: 'application/vnd.api+json',\n 'Content-Type': 'application/json',\n 'X-PCO-API-Version': this.version,\n ...this.uri.headers,\n ...this.defaultHeaders,\n }\n }\n}\n\nexport default Client\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0-rc.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"prettier": "^3.4.2",
|
|
53
53
|
"typescript": "<5.6.0"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "a83eecfeb631cdd217b3be1d3836326d8db78943"
|
|
56
56
|
}
|
package/src/__tests__/client.ts
CHANGED
|
@@ -3,7 +3,7 @@ import Client from '../utils/client/client'
|
|
|
3
3
|
import { Session } from '../utils/session'
|
|
4
4
|
import DefaultFixtures from '../__utils__/fixtures/defaults'
|
|
5
5
|
import { BASE_URL } from '../__utils__/handlers'
|
|
6
|
-
import { OAuthToken } from '../types'
|
|
6
|
+
import { ApiError, OAuthToken } from '../types'
|
|
7
7
|
|
|
8
8
|
const APP_BASE_URL = BASE_URL
|
|
9
9
|
|
|
@@ -16,6 +16,9 @@ beforeAll(() => MockServer.server().listen())
|
|
|
16
16
|
afterEach(() => MockServer.server().resetHandlers())
|
|
17
17
|
afterAll(() => MockServer.server().close())
|
|
18
18
|
|
|
19
|
+
const onTokenExpired = jest.fn()
|
|
20
|
+
const onForceLogout = jest.fn()
|
|
21
|
+
|
|
19
22
|
beforeEach(() => {
|
|
20
23
|
jest.clearAllMocks()
|
|
21
24
|
fetchSpy = jest.spyOn(globalThis, 'fetch')
|
|
@@ -24,7 +27,8 @@ beforeEach(() => {
|
|
|
24
27
|
app: 'chat',
|
|
25
28
|
version: '2018-11-01',
|
|
26
29
|
session,
|
|
27
|
-
onTokenExpired
|
|
30
|
+
onTokenExpired,
|
|
31
|
+
onForceLogout,
|
|
28
32
|
})
|
|
29
33
|
clientWithDefaultHeaders = new Client({
|
|
30
34
|
app: 'chat',
|
|
@@ -33,7 +37,8 @@ beforeEach(() => {
|
|
|
33
37
|
'X-Custom-Default-Header': 'important data',
|
|
34
38
|
},
|
|
35
39
|
session,
|
|
36
|
-
onTokenExpired
|
|
40
|
+
onTokenExpired,
|
|
41
|
+
onForceLogout,
|
|
37
42
|
})
|
|
38
43
|
})
|
|
39
44
|
|
|
@@ -184,7 +189,12 @@ describe('get', () => {
|
|
|
184
189
|
})
|
|
185
190
|
|
|
186
191
|
it('throws an error if response is not ok', async () => {
|
|
187
|
-
MockServer.get(
|
|
192
|
+
MockServer.get(
|
|
193
|
+
url,
|
|
194
|
+
{ errors: [{ status: '403', detail: 'Zoinks, your token is borked' }] },
|
|
195
|
+
403,
|
|
196
|
+
{ once: true }
|
|
197
|
+
)
|
|
188
198
|
|
|
189
199
|
await expect(
|
|
190
200
|
client.get({
|
|
@@ -428,6 +438,51 @@ describe('url switching', () => {
|
|
|
428
438
|
})
|
|
429
439
|
})
|
|
430
440
|
|
|
441
|
+
describe('error handling', () => {
|
|
442
|
+
describe('401 errors', () => {
|
|
443
|
+
it('defaults', async () => {
|
|
444
|
+
await test401({ secretWord: '' })
|
|
445
|
+
expect(onTokenExpired).not.toHaveBeenCalled()
|
|
446
|
+
expect(onForceLogout).not.toHaveBeenCalled()
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
it('handles forced logout', async () => {
|
|
450
|
+
await test401({ secretWord: 'CAPUCHIN' })
|
|
451
|
+
expect(onTokenExpired).not.toHaveBeenCalled()
|
|
452
|
+
expect(onForceLogout).toHaveBeenCalled()
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('handles token refresh', async () => {
|
|
456
|
+
await test401({ secretWord: 'BABOON' })
|
|
457
|
+
expect(onTokenExpired).toHaveBeenCalled()
|
|
458
|
+
expect(onForceLogout).not.toHaveBeenCalled()
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
const test401 = async ({ secretWord }: { secretWord: string }) => {
|
|
462
|
+
const url = '/records'
|
|
463
|
+
|
|
464
|
+
const expiredTokenResource: Partial<ApiError> = {
|
|
465
|
+
errors: [
|
|
466
|
+
{
|
|
467
|
+
detail: `Zoinks, your token is borked ( ${secretWord} )`,
|
|
468
|
+
title: 'Unauthorized',
|
|
469
|
+
status: '401',
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
MockServer.get(url, expiredTokenResource, 401, { once: true })
|
|
475
|
+
|
|
476
|
+
await expect(
|
|
477
|
+
client.get({
|
|
478
|
+
url,
|
|
479
|
+
data: { fields: { Record: ['id'] } },
|
|
480
|
+
})
|
|
481
|
+
).rejects.toHaveProperty('status', 401)
|
|
482
|
+
}
|
|
483
|
+
})
|
|
484
|
+
})
|
|
485
|
+
|
|
431
486
|
const requestHeadersShouldContain = ({
|
|
432
487
|
headers,
|
|
433
488
|
key,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { renderHook } from '@testing-library/react-native'
|
|
2
2
|
import { useTheme } from '../../hooks'
|
|
3
|
-
import { ChatProvider, CreateChatThemeProps } from '../../contexts'
|
|
3
|
+
import { ChatProvider, CreateChatThemeProps } from '../../contexts/chat_context'
|
|
4
4
|
import React from 'react'
|
|
5
5
|
|
|
6
6
|
let themeProps: CreateChatThemeProps = {
|
|
@@ -7,11 +7,14 @@ import { useConversationsJoltEvents } from '../hooks/use_conversation_jolt_event
|
|
|
7
7
|
import { useConversations } from '../hooks/use_conversations'
|
|
8
8
|
import { formatDatePreview } from '../utils/date'
|
|
9
9
|
import { AvatarGroup, Badge, Heading, Text, TextButton } from './display'
|
|
10
|
+
import { ActionButton } from './display/action_button'
|
|
11
|
+
import { useCanCreateConversations } from '../hooks/use_chat_permissions'
|
|
10
12
|
|
|
11
13
|
export const Conversations = () => {
|
|
12
14
|
const styles = useStyles()
|
|
13
15
|
|
|
14
16
|
const { conversations, fetchNextPage, refetch, isRefetching } = useConversations()
|
|
17
|
+
const canCreateConversations = useCanCreateConversations()
|
|
15
18
|
|
|
16
19
|
// TODO: Filter using the API
|
|
17
20
|
const nonEmptyConversations = conversations.filter(c => c.lastMessageTextPreview) || []
|
|
@@ -74,6 +77,11 @@ export const Conversations = () => {
|
|
|
74
77
|
)}
|
|
75
78
|
onEndReached={() => fetchNextPage()}
|
|
76
79
|
/>
|
|
80
|
+
<ActionButton
|
|
81
|
+
visible={canCreateConversations}
|
|
82
|
+
title="New conversation"
|
|
83
|
+
onPress={() => navigation.navigate('Create')}
|
|
84
|
+
/>
|
|
77
85
|
</View>
|
|
78
86
|
)
|
|
79
87
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Animated, LayoutAnimation, StyleSheet } from 'react-native'
|
|
3
|
+
import { Button } from './button'
|
|
4
|
+
import { useEffect } from 'react'
|
|
5
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
6
|
+
import { useTheme } from '../../hooks'
|
|
7
|
+
import { Text } from './text'
|
|
8
|
+
|
|
9
|
+
export const ActionButton = ({
|
|
10
|
+
visible = true,
|
|
11
|
+
onPress,
|
|
12
|
+
title,
|
|
13
|
+
infoText,
|
|
14
|
+
}: {
|
|
15
|
+
visible?: boolean
|
|
16
|
+
onPress: () => void
|
|
17
|
+
title: string
|
|
18
|
+
infoText?: string
|
|
19
|
+
}) => {
|
|
20
|
+
const styles = useStyles()
|
|
21
|
+
const [show, setShow] = useState(visible)
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (show === visible) return
|
|
25
|
+
|
|
26
|
+
setShow(visible)
|
|
27
|
+
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
|
28
|
+
}, [show, visible])
|
|
29
|
+
|
|
30
|
+
if (!visible) return null
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Animated.View style={styles.container}>
|
|
34
|
+
{Boolean(infoText) && (
|
|
35
|
+
<Text style={styles.infoText} variant="tertiary">
|
|
36
|
+
{infoText}
|
|
37
|
+
</Text>
|
|
38
|
+
)}
|
|
39
|
+
<Button variant="fill" size="lg" onPress={onPress} title={title} />
|
|
40
|
+
</Animated.View>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const useStyles = () => {
|
|
45
|
+
const { bottom } = useSafeAreaInsets()
|
|
46
|
+
const { colors } = useTheme()
|
|
47
|
+
|
|
48
|
+
return StyleSheet.create({
|
|
49
|
+
container: {
|
|
50
|
+
paddingVertical: 16,
|
|
51
|
+
paddingHorizontal: 24,
|
|
52
|
+
paddingBottom: bottom,
|
|
53
|
+
borderTopWidth: 1,
|
|
54
|
+
borderTopColor: colors.fillColorNeutral060,
|
|
55
|
+
gap: 16,
|
|
56
|
+
},
|
|
57
|
+
infoText: {
|
|
58
|
+
textAlign: 'center',
|
|
59
|
+
color: colors.textColorDefaultSecondary,
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
}
|
|
@@ -12,9 +12,9 @@ const defaultQueryFn = ({ queryKey }: { queryKey: QueryKey }) => {
|
|
|
12
12
|
throw new Error('No token present')
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const [url, data, headers] = queryKey as RequestQueryKey
|
|
15
|
+
const [url, data, headers, app = 'chat'] = queryKey as RequestQueryKey
|
|
16
16
|
|
|
17
|
-
return apiClient.
|
|
17
|
+
return apiClient[app].get({ url, data, headers })
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const queryClient = new QueryClient({
|
|
@@ -7,13 +7,14 @@ import { ChatTheme, defaultTheme, DefaultTheme } from '../utils/theme'
|
|
|
7
7
|
|
|
8
8
|
export type ChatContextValue = {
|
|
9
9
|
token?: OAuthToken
|
|
10
|
+
onForceLogout: () => void
|
|
10
11
|
onTokenExpired: () => void
|
|
11
12
|
theme: ChatTheme
|
|
12
13
|
env?: ENV
|
|
13
14
|
session: Session
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export interface ChatProviderProps extends Omit<ChatContextValue, 'theme' | 'session'> {
|
|
17
|
+
export interface ChatProviderProps extends Omit<ChatContextValue, 'client' | 'theme' | 'session'> {
|
|
17
18
|
theme: CreateChatThemeProps
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -21,12 +22,13 @@ export const ChatContext = createContext<ChatContextValue>({
|
|
|
21
22
|
theme: defaultTheme('light'),
|
|
22
23
|
token: undefined,
|
|
23
24
|
env: undefined,
|
|
25
|
+
onForceLogout: () => {},
|
|
24
26
|
onTokenExpired: () => {},
|
|
25
27
|
session: new Session(),
|
|
26
28
|
})
|
|
27
29
|
|
|
28
30
|
export function ChatProvider({ children, value }: { children: any; value: ChatProviderProps }) {
|
|
29
|
-
const { env, token, onTokenExpired } = value
|
|
31
|
+
const { env, token, onTokenExpired, onForceLogout } = value
|
|
30
32
|
const theme = useCreateChatTheme(value.theme || {})
|
|
31
33
|
const session = useMemo(() => new Session({ token, env }), [env, token])
|
|
32
34
|
|
|
@@ -34,6 +36,7 @@ export function ChatProvider({ children, value }: { children: any; value: ChatPr
|
|
|
34
36
|
env,
|
|
35
37
|
token,
|
|
36
38
|
onTokenExpired,
|
|
39
|
+
onForceLogout,
|
|
37
40
|
session,
|
|
38
41
|
theme,
|
|
39
42
|
}
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnyUseSuspenseInfiniteQueryOptions,
|
|
3
|
+
InfiniteData,
|
|
4
|
+
useInfiniteQuery,
|
|
5
|
+
useQuery,
|
|
6
|
+
} from '@tanstack/react-query'
|
|
7
|
+
import { ApiCollection, ApiResource, ResourceObject } from '../types'
|
|
8
|
+
import { GetRequest, RequestData } from '../utils/client/types'
|
|
9
|
+
import { useApiClient } from './use_api_client'
|
|
10
|
+
import { getRequestQueryKey } from './use_suspense_api'
|
|
11
|
+
|
|
12
|
+
interface SuspenseGetOptions extends GetRequest {
|
|
13
|
+
app?: 'chat' | 'groups'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const useApiGet = <T extends ResourceObject | ResourceObject[]>(
|
|
17
|
+
args: SuspenseGetOptions
|
|
18
|
+
) => {
|
|
19
|
+
type Resource = ApiResource<T>
|
|
20
|
+
|
|
21
|
+
const { data, ...query } = useQuery<Resource, Response>({
|
|
22
|
+
queryKey: getRequestQueryKey(args),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return { ...data, ...query }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type NextMeta = Partial<{
|
|
29
|
+
offset: string
|
|
30
|
+
idLt: string
|
|
31
|
+
}>
|
|
32
|
+
|
|
33
|
+
export type SuspensePaginatorOptions = Omit<
|
|
34
|
+
AnyUseSuspenseInfiniteQueryOptions,
|
|
35
|
+
'getNextPageParam' | 'initialPageParam' | 'queryFn' | 'queryKey'
|
|
36
|
+
>
|
|
37
|
+
|
|
38
|
+
export const useApiPaginator = <T extends ResourceObject>(
|
|
39
|
+
args: SuspenseGetOptions,
|
|
40
|
+
opts?: SuspensePaginatorOptions
|
|
41
|
+
) => {
|
|
42
|
+
const apiClient = useApiClient()
|
|
43
|
+
const query = useInfiniteQuery<
|
|
44
|
+
ApiCollection<T>,
|
|
45
|
+
Response,
|
|
46
|
+
InfiniteData<ApiCollection<T>>,
|
|
47
|
+
any,
|
|
48
|
+
Partial<RequestData> | undefined
|
|
49
|
+
>({
|
|
50
|
+
queryKey: getRequestQueryKey(args),
|
|
51
|
+
queryFn: ({ pageParam }) => {
|
|
52
|
+
const pageParmWhere = pageParam?.where || {}
|
|
53
|
+
const argsWhere = args.data.where || {}
|
|
54
|
+
const where = { ...argsWhere, ...pageParmWhere }
|
|
55
|
+
|
|
56
|
+
const offset = pageParam?.offset || args.data.offset
|
|
57
|
+
const data = { ...args.data, where, offset }
|
|
58
|
+
|
|
59
|
+
return apiClient.chat.get({
|
|
60
|
+
url: args.url,
|
|
61
|
+
data,
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
initialPageParam: {} as Partial<RequestData>,
|
|
65
|
+
getNextPageParam: lastPage => {
|
|
66
|
+
const next: NextMeta = lastPage.meta?.next || {}
|
|
67
|
+
const { offset, idLt } = next
|
|
68
|
+
|
|
69
|
+
if (idLt) return { where: { id_lt: idLt } }
|
|
70
|
+
if (offset) return { offset: Number(offset) }
|
|
71
|
+
|
|
72
|
+
return undefined
|
|
73
|
+
},
|
|
74
|
+
...(opts || {}),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const data: T[] = query.data?.pages.flatMap(page => page.data) || []
|
|
78
|
+
|
|
79
|
+
return { ...query, data }
|
|
80
|
+
}
|
|
@@ -2,26 +2,24 @@ import { useContext, useMemo } from 'react'
|
|
|
2
2
|
import { ChatContext } from '../contexts/chat_context'
|
|
3
3
|
import { Client } from '../utils/client'
|
|
4
4
|
|
|
5
|
-
type App = 'chat' | 'groups'
|
|
6
|
-
const apps: App[] = ['chat', 'groups']
|
|
7
|
-
|
|
5
|
+
type App = 'chat' | 'groups' | 'services'
|
|
6
|
+
const apps: App[] = ['chat', 'groups', 'services']
|
|
7
|
+
|
|
8
|
+
export type ApiClient = { [_K in App]: Client }
|
|
8
9
|
|
|
9
10
|
export const useApiClient = () => {
|
|
10
11
|
const { session, onTokenExpired } = useContext(ChatContext)
|
|
11
12
|
const api = useMemo(
|
|
12
13
|
() =>
|
|
13
|
-
apps.reduce(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
{} as Record<App, Client>
|
|
24
|
-
),
|
|
14
|
+
apps.reduce((acc, app) => {
|
|
15
|
+
acc[app] = new Client({
|
|
16
|
+
app,
|
|
17
|
+
session,
|
|
18
|
+
version: '2018-11-01',
|
|
19
|
+
onTokenExpired,
|
|
20
|
+
})
|
|
21
|
+
return acc
|
|
22
|
+
}, {} as ApiClient),
|
|
25
23
|
[session, onTokenExpired]
|
|
26
24
|
)
|
|
27
25
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AppGrantsResource } from '../types'
|
|
2
|
+
import { useApiGet } from './use_api'
|
|
3
|
+
|
|
4
|
+
export function useAppGrants() {
|
|
5
|
+
return useApiGet<AppGrantsResource[]>({
|
|
6
|
+
url: '/me/app_grants',
|
|
7
|
+
data: {
|
|
8
|
+
fields: {
|
|
9
|
+
AppGrant: ['create_conversations', 'app_name'],
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
app: 'chat',
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useCanCreateConversations(): boolean {
|
|
17
|
+
const { data: appGrants = [] } = useAppGrants()
|
|
18
|
+
|
|
19
|
+
return appGrants.some(appGrant => appGrant.createConversations)
|
|
20
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { useMutation } from '@tanstack/react-query'
|
|
1
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { useState } from 'react'
|
|
2
3
|
import { ApiResource, ConversationResource } from '../types'
|
|
3
|
-
import { getRequestQueryKey, useSuspenseGet } from './use_suspense_api'
|
|
4
|
-
import { queryClient } from '../contexts'
|
|
5
|
-
import { useApiClient } from './use_api_client'
|
|
6
4
|
import { transformGetToPost } from '../utils/client/request_helpers'
|
|
7
|
-
import {
|
|
5
|
+
import { useApiClient } from './use_api_client'
|
|
6
|
+
import { getRequestQueryKey, useSuspenseGet } from './use_suspense_api'
|
|
8
7
|
|
|
9
8
|
export const getConversationRequestArgs = ({ conversation_id }: { conversation_id: string }) => ({
|
|
10
9
|
url: `/me/conversations/${conversation_id}`,
|
|
@@ -39,6 +38,7 @@ export const useConversation = ({ conversation_id }) => {
|
|
|
39
38
|
|
|
40
39
|
export const useConversationMute = ({ conversation_id }: { conversation_id: string }) => {
|
|
41
40
|
const apiClient = useApiClient()
|
|
41
|
+
const queryClient = useQueryClient()
|
|
42
42
|
const requestArgs = getConversationRequestArgs({ conversation_id })
|
|
43
43
|
const queryKey = getRequestQueryKey(requestArgs)
|
|
44
44
|
const { data: conversation } = useConversation({ conversation_id })
|
|
@@ -82,6 +82,7 @@ export const useConversationMute = ({ conversation_id }: { conversation_id: stri
|
|
|
82
82
|
|
|
83
83
|
export const useConversationUpdate = ({ conversation_id }: { conversation_id: string }) => {
|
|
84
84
|
const apiClient = useApiClient()
|
|
85
|
+
const queryClient = useQueryClient()
|
|
85
86
|
const requestArgs = getConversationRequestArgs({ conversation_id })
|
|
86
87
|
const queryKey = getRequestQueryKey(requestArgs)
|
|
87
88
|
|
|
@@ -8,7 +8,13 @@ import { ApiCollection, ApiResource, ResourceObject } from '../types'
|
|
|
8
8
|
import { GetRequest, RequestData } from '../utils/client/types'
|
|
9
9
|
import { useApiClient } from './use_api_client'
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
interface SuspenseGetOptions extends GetRequest {
|
|
12
|
+
app?: 'chat' | 'groups'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useSuspenseGet = <T extends ResourceObject | ResourceObject[]>(
|
|
16
|
+
args: SuspenseGetOptions
|
|
17
|
+
) => {
|
|
12
18
|
type Resource = ApiResource<T>
|
|
13
19
|
|
|
14
20
|
const { data, ...query } = useSuspenseQuery<Resource, Response>({
|
|
@@ -29,7 +35,7 @@ export type SuspensePaginatorOptions = Omit<
|
|
|
29
35
|
>
|
|
30
36
|
|
|
31
37
|
export const useSuspensePaginator = <T extends ResourceObject>(
|
|
32
|
-
args:
|
|
38
|
+
args: SuspenseGetOptions,
|
|
33
39
|
opts?: SuspensePaginatorOptions
|
|
34
40
|
) => {
|
|
35
41
|
const apiClient = useApiClient()
|
|
@@ -72,9 +78,15 @@ export const useSuspensePaginator = <T extends ResourceObject>(
|
|
|
72
78
|
return { ...query, data }
|
|
73
79
|
}
|
|
74
80
|
|
|
75
|
-
export type RequestQueryKey = [
|
|
76
|
-
|
|
81
|
+
export type RequestQueryKey = [
|
|
82
|
+
SuspenseGetOptions['url'],
|
|
83
|
+
SuspenseGetOptions['data'],
|
|
84
|
+
SuspenseGetOptions['headers'],
|
|
85
|
+
SuspenseGetOptions['app'],
|
|
86
|
+
]
|
|
87
|
+
export const getRequestQueryKey = (args: SuspenseGetOptions): RequestQueryKey => [
|
|
77
88
|
args.url,
|
|
78
89
|
args.data,
|
|
79
90
|
args.headers,
|
|
91
|
+
args.app || 'chat',
|
|
80
92
|
]
|