@planningcenter/chat-react-native 2.2.2-rc.0 → 2.2.2-rc.1
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/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/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/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/contexts/chat_context.tsx +5 -2
- package/src/types/api_primitives.ts +12 -0
- package/src/utils/client/client.ts +34 -11
|
@@ -5,12 +5,13 @@ import { ENV, Session } from '../utils';
|
|
|
5
5
|
import { ChatTheme, DefaultTheme } from '../utils/theme';
|
|
6
6
|
export type ChatContextValue = {
|
|
7
7
|
token?: OAuthToken;
|
|
8
|
+
onForceLogout: () => void;
|
|
8
9
|
onTokenExpired: () => void;
|
|
9
10
|
theme: ChatTheme;
|
|
10
11
|
env?: ENV;
|
|
11
12
|
session: Session;
|
|
12
13
|
};
|
|
13
|
-
export interface ChatProviderProps extends Omit<ChatContextValue, 'theme' | 'session'> {
|
|
14
|
+
export interface ChatProviderProps extends Omit<ChatContextValue, 'client' | 'theme' | 'session'> {
|
|
14
15
|
theme: CreateChatThemeProps;
|
|
15
16
|
}
|
|
16
17
|
export declare const ChatContext: React.Context<ChatContextValue>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat_context.d.ts","sourceRoot":"","sources":["../../src/contexts/chat_context.tsx"],"names":[],"mappings":"AACA,OAAO,KAAiC,MAAM,OAAO,CAAA;AACrD,OAAO,EAAE,eAAe,EAAkB,MAAM,cAAc,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,SAAS,EAAgB,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,KAAK,EAAE,SAAS,CAAA;IAChB,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"chat_context.d.ts","sourceRoot":"","sources":["../../src/contexts/chat_context.tsx"],"names":[],"mappings":"AACA,OAAO,KAAiC,MAAM,OAAO,CAAA;AACrD,OAAO,EAAE,eAAe,EAAkB,MAAM,cAAc,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,SAAS,EAAgB,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,aAAa,EAAE,MAAM,IAAI,CAAA;IACzB,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,KAAK,EAAE,SAAS,CAAA;IAChB,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAC/F,KAAK,EAAE,oBAAoB,CAAA;CAC5B;AAED,eAAO,MAAM,WAAW,iCAOtB,CAAA;AAEF,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,iBAAiB,CAAA;CAAE,qBAe5F;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,eAAe,CAAA;CAC9B;AAED,eAAO,MAAM,kBAAkB,yDAG5B,oBAAoB,KAAG,SAYzB,CAAA"}
|
|
@@ -7,17 +7,19 @@ export const ChatContext = createContext({
|
|
|
7
7
|
theme: defaultTheme('light'),
|
|
8
8
|
token: undefined,
|
|
9
9
|
env: undefined,
|
|
10
|
+
onForceLogout: () => { },
|
|
10
11
|
onTokenExpired: () => { },
|
|
11
12
|
session: new Session(),
|
|
12
13
|
});
|
|
13
14
|
export function ChatProvider({ children, value }) {
|
|
14
|
-
const { env, token, onTokenExpired } = value;
|
|
15
|
+
const { env, token, onTokenExpired, onForceLogout } = value;
|
|
15
16
|
const theme = useCreateChatTheme(value.theme || {});
|
|
16
17
|
const session = useMemo(() => new Session({ token, env }), [env, token]);
|
|
17
18
|
const contextValue = {
|
|
18
19
|
env,
|
|
19
20
|
token,
|
|
20
21
|
onTokenExpired,
|
|
22
|
+
onForceLogout,
|
|
21
23
|
session,
|
|
22
24
|
theme,
|
|
23
25
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat_context.js","sourceRoot":"","sources":["../../src/contexts/chat_context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC9B,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,EAAmB,cAAc,EAAE,MAAM,cAAc,CAAA;AAE9D,OAAO,EAAO,OAAO,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAa,YAAY,EAAgB,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"chat_context.js","sourceRoot":"","sources":["../../src/contexts/chat_context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC9B,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,EAAmB,cAAc,EAAE,MAAM,cAAc,CAAA;AAE9D,OAAO,EAAO,OAAO,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAa,YAAY,EAAgB,MAAM,gBAAgB,CAAA;AAetE,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAmB;IACzD,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC;IAC5B,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE,SAAS;IACd,aAAa,EAAE,GAAG,EAAE,GAAE,CAAC;IACvB,cAAc,EAAE,GAAG,EAAE,GAAE,CAAC;IACxB,OAAO,EAAE,IAAI,OAAO,EAAE;CACvB,CAAC,CAAA;AAEF,MAAM,UAAU,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAA+C;IAC3F,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,GAAG,KAAK,CAAA;IAC3D,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IACnD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;IAExE,MAAM,YAAY,GAAqB;QACrC,GAAG;QACH,KAAK;QACL,cAAc;QACd,aAAa;QACb,OAAO;QACP,KAAK;KACN,CAAA;IAED,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;AACrF,CAAC;AAOD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EACjC,KAAK,EAAE,WAAW,GAAG,EAAE,EACvB,WAAW,EAAE,cAAc,GACN,EAAa,EAAE;IACpC,MAAM,mBAAmB,GAAG,cAAc,EAAE,IAAI,OAAO,CAAA;IACvD,MAAM,WAAW,GAAG,cAAc,IAAI,mBAAmB,CAAA;IAEzD,MAAM,KAAK,GAAG;QACZ,GAAG,KAAK,CAAC,EAAE,EAAE,YAAY,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;QACpD,MAAM,EAAE;YACN,GAAG,KAAK,CAAC,EAAE,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC;SACpE;KACF,CAAA;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA","sourcesContent":["import { merge } from 'lodash'\nimport React, { createContext, useMemo } from 'react'\nimport { ColorSchemeName, useColorScheme } from 'react-native'\nimport { DeepPartial, OAuthToken } from '../types'\nimport { ENV, Session } from '../utils'\nimport { ChatTheme, defaultTheme, DefaultTheme } from '../utils/theme'\n\nexport type ChatContextValue = {\n token?: OAuthToken\n onForceLogout: () => void\n onTokenExpired: () => void\n theme: ChatTheme\n env?: ENV\n session: Session\n}\n\nexport interface ChatProviderProps extends Omit<ChatContextValue, 'client' | 'theme' | 'session'> {\n theme: CreateChatThemeProps\n}\n\nexport const ChatContext = createContext<ChatContextValue>({\n theme: defaultTheme('light'),\n token: undefined,\n env: undefined,\n onForceLogout: () => {},\n onTokenExpired: () => {},\n session: new Session(),\n})\n\nexport function ChatProvider({ children, value }: { children: any; value: ChatProviderProps }) {\n const { env, token, onTokenExpired, onForceLogout } = value\n const theme = useCreateChatTheme(value.theme || {})\n const session = useMemo(() => new Session({ token, env }), [env, token])\n\n const contextValue: ChatContextValue = {\n env,\n token,\n onTokenExpired,\n onForceLogout,\n session,\n theme,\n }\n\n return <ChatContext.Provider value={contextValue}>{children}</ChatContext.Provider>\n}\n\nexport interface CreateChatThemeProps {\n theme?: DeepPartial<DefaultTheme>\n colorScheme?: ColorSchemeName\n}\n\nexport const useCreateChatTheme = ({\n theme: customTheme = {},\n colorScheme: appColorScheme,\n}: CreateChatThemeProps): ChatTheme => {\n const internalColorScheme = useColorScheme() || 'light'\n const colorScheme = appColorScheme || internalColorScheme\n\n const theme = {\n ...merge({}, defaultTheme(colorScheme), customTheme),\n colors: {\n ...merge({}, defaultTheme(colorScheme).colors, customTheme?.colors),\n },\n }\n\n return theme\n}\n"]}
|
|
@@ -2,6 +2,18 @@ export interface ResourceObject {
|
|
|
2
2
|
id: string;
|
|
3
3
|
type: string;
|
|
4
4
|
}
|
|
5
|
+
export interface ErrorObject {
|
|
6
|
+
source?: {
|
|
7
|
+
parameter: string;
|
|
8
|
+
};
|
|
9
|
+
detail: string;
|
|
10
|
+
meta?: {
|
|
11
|
+
associated_resources: any[];
|
|
12
|
+
resource: string;
|
|
13
|
+
};
|
|
14
|
+
title: string;
|
|
15
|
+
status: string;
|
|
16
|
+
}
|
|
5
17
|
export type ApiResource<Type = ResourceObject> = {
|
|
6
18
|
data: Type;
|
|
7
19
|
links: Record<string, string>;
|
|
@@ -12,6 +24,9 @@ export type ApiCollection<Type = ResourceObject> = {
|
|
|
12
24
|
links: Record<string, string>;
|
|
13
25
|
meta: CollectionMeta;
|
|
14
26
|
};
|
|
27
|
+
export interface ApiError {
|
|
28
|
+
errors: ErrorObject[];
|
|
29
|
+
}
|
|
15
30
|
export interface CollectionMeta {
|
|
16
31
|
count: number;
|
|
17
32
|
totalCount: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api_primitives.d.ts","sourceRoot":"","sources":["../../src/types/api_primitives.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,WAAW,CAAC,IAAI,GAAG,cAAc,IAAI;IAC/C,IAAI,EAAE,IAAI,CAAA;IACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,aAAa,CAAC,IAAI,GAAG,cAAc,IAAI;IACjD,IAAI,EAAE,IAAI,EAAE,CAAA;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,IAAI,EAAE,cAAc,CAAA;CACrB,CAAA;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAA;CACjC"}
|
|
1
|
+
{"version":3,"file":"api_primitives.d.ts","sourceRoot":"","sources":["../../src/types/api_primitives.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE;QAAE,oBAAoB,EAAE,GAAG,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IACxD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,WAAW,CAAC,IAAI,GAAG,cAAc,IAAI;IAC/C,IAAI,EAAE,IAAI,CAAA;IACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,aAAa,CAAC,IAAI,GAAG,cAAc,IAAI;IACjD,IAAI,EAAE,IAAI,EAAE,CAAA;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,IAAI,EAAE,cAAc,CAAA;CACrB,CAAA;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,WAAW,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAA;CACjC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api_primitives.js","sourceRoot":"","sources":["../../src/types/api_primitives.ts"],"names":[],"mappings":"","sourcesContent":["export interface ResourceObject {\n id: string\n type: string\n}\n\nexport type ApiResource<Type = ResourceObject> = {\n data: Type\n links: Record<string, string>\n meta: Record<string, unknown>\n}\n\nexport type ApiCollection<Type = ResourceObject> = {\n data: Type[]\n links: Record<string, string>\n meta: CollectionMeta\n}\n\nexport interface CollectionMeta {\n count: number\n totalCount: number\n next?: Record<string, unknown>\n parent?: ResourceObject\n [attributeName: string]: unknown\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api_primitives.js","sourceRoot":"","sources":["../../src/types/api_primitives.ts"],"names":[],"mappings":"","sourcesContent":["export interface ResourceObject {\n id: string\n type: string\n}\n\nexport interface ErrorObject {\n source?: { parameter: string }\n detail: string\n meta?: { associated_resources: any[]; resource: string }\n title: string\n status: string\n}\n\nexport type ApiResource<Type = ResourceObject> = {\n data: Type\n links: Record<string, string>\n meta: Record<string, unknown>\n}\n\nexport type ApiCollection<Type = ResourceObject> = {\n data: Type[]\n links: Record<string, string>\n meta: CollectionMeta\n}\n\nexport interface ApiError {\n errors: ErrorObject[]\n}\n\nexport interface CollectionMeta {\n count: number\n totalCount: number\n next?: Record<string, unknown>\n parent?: ResourceObject\n [attributeName: string]: unknown\n}\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":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAA;AAC5B,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,WAAW,EAEX,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,mBAAmB,CAAA;AAa1B,MAAM,OAAO,MAAM;IACjB,OAAO,GAAW,EAAE,CAAA;IACpB,cAAc,GAA2B,EAAE,CAAA;IAC3C,GAAG,CAAK;IACR,cAAc,CAAa;IAC3B,aAAa,CAAa;IAE1B,YAAY,EACV,OAAO,EACP,cAAc,GAAG,EAAE,EACnB,OAAO,EACP,GAAG,EACH,cAAc,EACd,aAAa,GACF;QACX,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAA;QACpC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAA;QACpC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAA;QACpC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAA;IACpC,CAAC;IAED,KAAK,CAAC,GAAG,CAAwC,IAAgB;QAC/D,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,CAAA;QACnC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;QAC/B,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAErC,MAAM,uBAAuB,CAAC,GAAG,CAAC,CAAA;QAElC,MAAM,WAAW,GAAoB,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;QAE1E,MAAM,WAAW,GAAG,CAAC,EACnB,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,EACxB,GAAG,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EACrD,GAAG,OAAO,EACE,EAAE,EAAE;YAChB,OAAO,WAAW,CAAC;gBACjB,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,CAAC;gBACP,GAAG,EAAE,UAAU;gBACf,GAAG,OAAO;gBACV,OAAO;aACR,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;gBAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;gBAE1E,iFAAiF;gBACjF,IAAI,KAAK,EAAE,IAAI,EAAE,CAAC;oBAChB,OAAO,WAAW,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAA;gBAC5E,CAAC;qBAAM,CAAC;oBACN,OAAO,OAAO,CAAA;gBAChB,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC,CAAA;QAED,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAA;QAErD,OAAO,yBAAyB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAChF,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAkB;QAC5B,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAErC,MAAM,WAAW,GAAoB,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;QAEvF,OAAO,wBAAwB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACnF,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAiB;QAC1B,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAErC,MAAM,WAAW,GAAoB,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;QAE/F,OAAO,wBAAwB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACnF,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAmB;QAC9B,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAErC,MAAM,WAAW,GAAoB,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;QAEvE,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACzD,CAAC;IAED,WAAW,GAAG,KAAK,EAAE,QAAkB,EAAE,EAAE;QACzC,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAA;YACtD,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,EAAE,CAAA;YACnC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;YACjE,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;YAClE,cAAc,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAA;YACzC,aAAa,IAAI,IAAI,CAAC,aAAa,EAAE,EAAE,CAAA;QACzC,CAAC;QAED,OAAO,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACjC,CAAC,CAAA;IAED,kBAAkB,GAAG,KAAK,EAAE,QAAkB,EAAiC,EAAE;QAC/E,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAa,CAAA;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC,CAAA;IAED,IAAI,OAAO;QACT,OAAO;YACL,MAAM,EAAE,0BAA0B;YAClC,cAAc,EAAE,kBAAkB;YAClC,mBAAmB,EAAE,IAAI,CAAC,OAAO;YACjC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO;YACnB,GAAG,IAAI,CAAC,cAAc;SACvB,CAAA;IACH,CAAC;CACF;AAED,eAAe,MAAM,CAAA","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.2.2-rc.
|
|
3
|
+
"version": "2.2.2-rc.1",
|
|
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": "9a3a46afb5c78a3e7b5c4892a1d92c9874214f37"
|
|
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,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
|
}
|
|
@@ -3,6 +3,14 @@ export interface ResourceObject {
|
|
|
3
3
|
type: string
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
export interface ErrorObject {
|
|
7
|
+
source?: { parameter: string }
|
|
8
|
+
detail: string
|
|
9
|
+
meta?: { associated_resources: any[]; resource: string }
|
|
10
|
+
title: string
|
|
11
|
+
status: string
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
export type ApiResource<Type = ResourceObject> = {
|
|
7
15
|
data: Type
|
|
8
16
|
links: Record<string, string>
|
|
@@ -15,6 +23,10 @@ export type ApiCollection<Type = ResourceObject> = {
|
|
|
15
23
|
meta: CollectionMeta
|
|
16
24
|
}
|
|
17
25
|
|
|
26
|
+
export interface ApiError {
|
|
27
|
+
errors: ErrorObject[]
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
export interface CollectionMeta {
|
|
19
31
|
count: number
|
|
20
32
|
totalCount: number
|
|
@@ -1,4 +1,4 @@
|
|
|
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 {
|
|
@@ -15,7 +15,8 @@ import { DeleteRequest, GetRequest, PatchRequest, PostRequest, WalkRequest } fro
|
|
|
15
15
|
type ClientArgs = {
|
|
16
16
|
version: string
|
|
17
17
|
defaultHeaders?: Record<string, string>
|
|
18
|
-
|
|
18
|
+
onForceLogout?: () => void
|
|
19
|
+
onTokenExpired?: () => void
|
|
19
20
|
session: Session
|
|
20
21
|
app: string
|
|
21
22
|
}
|
|
@@ -24,13 +25,22 @@ export class Client {
|
|
|
24
25
|
version: string = ''
|
|
25
26
|
defaultHeaders: Record<string, string> = {}
|
|
26
27
|
uri: Uri
|
|
27
|
-
onTokenExpired
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
onTokenExpired?: () => void
|
|
29
|
+
onForceLogout?: () => void
|
|
30
|
+
|
|
31
|
+
constructor({
|
|
32
|
+
version,
|
|
33
|
+
defaultHeaders = {},
|
|
34
|
+
session,
|
|
35
|
+
app,
|
|
36
|
+
onTokenExpired,
|
|
37
|
+
onForceLogout,
|
|
38
|
+
}: ClientArgs) {
|
|
30
39
|
this.version = version
|
|
31
40
|
this.uri = new Uri({ session, app })
|
|
32
41
|
this.defaultHeaders = defaultHeaders
|
|
33
42
|
this.onTokenExpired = onTokenExpired
|
|
43
|
+
this.onForceLogout = onForceLogout
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
async get<T extends ApiCollection | ApiResource>(args: GetRequest): Promise<T> {
|
|
@@ -69,7 +79,7 @@ export class Client {
|
|
|
69
79
|
|
|
70
80
|
const handler = isWalking ? walkRequest : makeRequest
|
|
71
81
|
|
|
72
|
-
return throwErrorIfFieldsMissing(handler, requestArgs).catch(this.
|
|
82
|
+
return throwErrorIfFieldsMissing(handler, requestArgs).catch(this.handleNotOk)
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
async patch(args: PatchRequest) {
|
|
@@ -78,7 +88,7 @@ export class Client {
|
|
|
78
88
|
|
|
79
89
|
const requestArgs: MakeRequestArgs = { data: args.data, url, action: 'PATCH', headers }
|
|
80
90
|
|
|
81
|
-
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.
|
|
91
|
+
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleNotOk)
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
async post(args: PostRequest) {
|
|
@@ -87,7 +97,7 @@ export class Client {
|
|
|
87
97
|
|
|
88
98
|
const requestArgs: MakeRequestArgs = { ...args, data: args.data, url, action: 'POST', headers }
|
|
89
99
|
|
|
90
|
-
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.
|
|
100
|
+
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleNotOk)
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
async delete(args: DeleteRequest) {
|
|
@@ -96,17 +106,30 @@ export class Client {
|
|
|
96
106
|
|
|
97
107
|
const requestArgs: MakeRequestArgs = { url, action: 'DELETE', headers }
|
|
98
108
|
|
|
99
|
-
return makeRequest(requestArgs).catch(this.
|
|
109
|
+
return makeRequest(requestArgs).catch(this.handleNotOk)
|
|
100
110
|
}
|
|
101
111
|
|
|
102
|
-
|
|
112
|
+
handleNotOk = async (response: Response) => {
|
|
103
113
|
if (response.status === 401) {
|
|
104
|
-
this.
|
|
114
|
+
const parsed = await this.parseErrorResponse(response)
|
|
115
|
+
const errors = parsed?.errors || []
|
|
116
|
+
const isTokenExpired = errors.some(e => /baboon/i.test(e.detail))
|
|
117
|
+
const isForceLogout = errors.some(e => /capuchin/i.test(e.detail))
|
|
118
|
+
isTokenExpired && this.onTokenExpired?.()
|
|
119
|
+
isForceLogout && this.onForceLogout?.()
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
return Promise.reject(response)
|
|
108
123
|
}
|
|
109
124
|
|
|
125
|
+
parseErrorResponse = async (response: Response): Promise<ApiError | undefined> => {
|
|
126
|
+
try {
|
|
127
|
+
return (await response.json()) as ApiError
|
|
128
|
+
} catch {
|
|
129
|
+
return undefined
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
110
133
|
get headers() {
|
|
111
134
|
return {
|
|
112
135
|
Accept: 'application/vnd.api+json',
|