@intra-mart/smartlime 1.3.0 → 1.4.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/lib/packages/OAuth/index.js +4 -4
- package/lib/packages/Smartlime/index.d.ts +3 -1
- package/lib/packages/Smartlime/index.js +2 -2
- package/lib/packages/Tenant/Context.d.ts +7 -0
- package/lib/packages/Tenant/hooks/index.d.ts +2 -0
- package/lib/packages/Tenant/hooks/index.js +2 -0
- package/lib/packages/Tenant/hooks/useIMFetchModuleState.d.ts +2 -0
- package/lib/packages/Tenant/hooks/useIMFetchModuleState.js +22 -0
- package/lib/packages/Tenant/hooks/useIMModuleInfo.d.ts +6 -0
- package/lib/packages/Tenant/hooks/useIMModuleInfo.js +13 -0
- package/lib/packages/Tenant/index.d.ts +16 -1
- package/lib/packages/Tenant/index.js +99 -1
- package/lib/packages/Tenant/modulesType.d.ts +3 -0
- package/lib/packages/Tenant/modulesType.js +2 -0
- package/lib/packages/WebView/InternalIMWebView.d.ts +24 -0
- package/lib/packages/WebView/InternalIMWebView.js +205 -0
- package/lib/packages/WebView/index.d.ts +3 -9
- package/lib/packages/WebView/index.js +23 -164
- package/package.json +2 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as SecureStore from 'expo-secure-store';
|
|
2
2
|
import { exchangeCodeAsync, loadAsync, Prompt, refreshAsync, } from 'expo-auth-session';
|
|
3
3
|
import { useCallback, useLayoutEffect, useMemo, useRef, } from 'react';
|
|
4
4
|
import { Context as DefaultContext } from './Context';
|
|
@@ -70,13 +70,13 @@ const saveTokenStorage = async (storageKey, baseUrl, accessToken, refreshToken,
|
|
|
70
70
|
refreshToken,
|
|
71
71
|
expirationDate,
|
|
72
72
|
};
|
|
73
|
-
return await
|
|
73
|
+
return await SecureStore.setItemAsync(storageKey, JSON.stringify(item));
|
|
74
74
|
};
|
|
75
75
|
const deleteTokenStorage = async (tokenStorageKey) => {
|
|
76
|
-
return await
|
|
76
|
+
return await SecureStore.deleteItemAsync(tokenStorageKey);
|
|
77
77
|
};
|
|
78
78
|
const getTokenStorage = async (storageKey) => {
|
|
79
|
-
const state = await
|
|
79
|
+
const state = await SecureStore.getItem(storageKey);
|
|
80
80
|
if (state == null)
|
|
81
81
|
return;
|
|
82
82
|
const result = JSON.parse(state);
|
|
@@ -3,6 +3,7 @@ import { IMMe } from '../Me';
|
|
|
3
3
|
import { IMOAuth } from '../OAuth';
|
|
4
4
|
import { IMSession } from '../Session';
|
|
5
5
|
import { IMSearch } from '../Search';
|
|
6
|
+
import { IMTenant } from '../Tenant';
|
|
6
7
|
interface SmartlimeProps {
|
|
7
8
|
children: JSX.Element;
|
|
8
9
|
baseUrl: string;
|
|
@@ -10,10 +11,11 @@ interface SmartlimeProps {
|
|
|
10
11
|
* prop is currently not working.
|
|
11
12
|
*/
|
|
12
13
|
tenantId?: string;
|
|
14
|
+
imtenant: Omit<React.ComponentProps<typeof IMTenant>, 'children' | 'baseUrl' | 'tenantId'>;
|
|
13
15
|
imoauth: Omit<React.ComponentProps<typeof IMOAuth>, 'children'>;
|
|
14
16
|
imme?: Omit<React.ComponentProps<typeof IMMe>, 'children'>;
|
|
15
17
|
imsession?: Omit<React.ComponentProps<typeof IMSession>, 'children'>;
|
|
16
18
|
imseach?: Omit<React.ComponentProps<typeof IMSearch>, 'children'>;
|
|
17
19
|
}
|
|
18
|
-
export declare const Smartlime: ({ children, baseUrl, tenantId, imoauth, imme, imsession, imseach, }: SmartlimeProps) => import("react").JSX.Element;
|
|
20
|
+
export declare const Smartlime: ({ children, baseUrl, tenantId, imtenant, imoauth, imme, imsession, imseach, }: SmartlimeProps) => import("react").JSX.Element;
|
|
19
21
|
export {};
|
|
@@ -4,8 +4,8 @@ import { IMSession } from '../Session';
|
|
|
4
4
|
import { IMSearch } from '../Search';
|
|
5
5
|
import { IMTenant } from '../Tenant';
|
|
6
6
|
import { NotSearchPropError } from './NotSearchPropError';
|
|
7
|
-
export const Smartlime = ({ children, baseUrl, tenantId, imoauth, imme, imsession, imseach, }) => {
|
|
8
|
-
const imtenantProps = { baseUrl, tenantId };
|
|
7
|
+
export const Smartlime = ({ children, baseUrl, tenantId, imtenant, imoauth, imme, imsession, imseach, }) => {
|
|
8
|
+
const imtenantProps = { baseUrl, tenantId, ...imtenant };
|
|
9
9
|
const imseachProps = imseach != null
|
|
10
10
|
? imseach
|
|
11
11
|
: {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
+
import { FetchModuleState, Moudles } from '.';
|
|
3
|
+
import { RenderTarget } from '../../_shared/renderTarget';
|
|
2
4
|
export interface TenantContext {
|
|
3
5
|
getBaseUrl: () => string;
|
|
4
6
|
getTenantId: () => string | undefined;
|
|
7
|
+
getFetchModuleState: () => FetchModuleState;
|
|
8
|
+
getModuleInfo: () => Moudles | undefined;
|
|
9
|
+
destroyModuleInfo: () => Promise<void>;
|
|
10
|
+
fetchModuleInfo: () => Promise<void>;
|
|
11
|
+
modulesStateRenderTarget: RenderTarget;
|
|
5
12
|
}
|
|
6
13
|
export declare const Context: import("react").Context<TenantContext | null>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useContext, useEffect, useReducer, useRef } from 'react';
|
|
2
|
+
import { Context as DefaultContext } from '../Context';
|
|
3
|
+
import { IMTenantError } from '../IMTenantError';
|
|
4
|
+
export const useIMFetchModuleState = (Context) => {
|
|
5
|
+
const initRef = useRef(true);
|
|
6
|
+
const [, forceRender] = useReducer((s) => s + 1, 0);
|
|
7
|
+
const tenantContext = useContext(Context || DefaultContext);
|
|
8
|
+
if (tenantContext == null) {
|
|
9
|
+
throw new IMTenantError('useIMFetchModuleState requires either a Context provide or an ancestor element with a IMTenantProvider.');
|
|
10
|
+
}
|
|
11
|
+
if (initRef.current) {
|
|
12
|
+
initRef.current = false;
|
|
13
|
+
tenantContext.modulesStateRenderTarget.addEventListener(forceRender);
|
|
14
|
+
}
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
return () => {
|
|
17
|
+
tenantContext.modulesStateRenderTarget.removeEventListener(forceRender);
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
const state = tenantContext.getFetchModuleState();
|
|
21
|
+
return state;
|
|
22
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Context as DefaultContext } from '../Context';
|
|
2
|
+
export declare const useIMModuleInfo: (Context?: typeof DefaultContext) => {
|
|
3
|
+
fetchModuleInfo: () => Promise<void>;
|
|
4
|
+
getModuleInfo: () => import("..").Moudles | undefined;
|
|
5
|
+
destroyModuleInfo: () => Promise<void>;
|
|
6
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useContext, useMemo } from 'react';
|
|
2
|
+
import { Context as DefaultContext } from '../Context';
|
|
3
|
+
import { IMTenantError } from '../IMTenantError';
|
|
4
|
+
export const useIMModuleInfo = (Context) => {
|
|
5
|
+
const tenantContext = useContext(Context || DefaultContext);
|
|
6
|
+
if (tenantContext == null) {
|
|
7
|
+
throw new IMTenantError('useIMModuleInfo requires either a Context provide or an ancestor element with a IMTenantProvider.');
|
|
8
|
+
}
|
|
9
|
+
return useMemo(() => {
|
|
10
|
+
const { fetchModuleInfo, getModuleInfo, destroyModuleInfo } = tenantContext;
|
|
11
|
+
return { fetchModuleInfo, getModuleInfo, destroyModuleInfo };
|
|
12
|
+
}, [tenantContext]);
|
|
13
|
+
};
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { Context as DefaultContext } from './Context';
|
|
3
|
+
import { ModuleIds } from './modulesType';
|
|
3
4
|
interface IMTenantProps {
|
|
4
5
|
children: JSX.Element;
|
|
5
6
|
baseUrl: string;
|
|
6
7
|
tenantId?: string;
|
|
7
8
|
tenantContext?: typeof DefaultContext;
|
|
9
|
+
useModuleInfo?: boolean;
|
|
8
10
|
}
|
|
9
|
-
export
|
|
11
|
+
export interface ModuleInfo<T extends ModuleIds[number]> {
|
|
12
|
+
moduleKey: T;
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
othes: unknown;
|
|
15
|
+
}
|
|
16
|
+
export type Moudles = {
|
|
17
|
+
[key in ModuleIds[number]]: ModuleInfo<key>;
|
|
18
|
+
};
|
|
19
|
+
export type FetchModuleState = 'notRequired' | 'notFetched' | 'fetched';
|
|
20
|
+
export interface ModuleState {
|
|
21
|
+
fetchModuleState: FetchModuleState;
|
|
22
|
+
modules?: Moudles;
|
|
23
|
+
}
|
|
24
|
+
export declare const IMTenant: ({ children, baseUrl, tenantId, tenantContext, useModuleInfo, }: IMTenantProps) => import("react").JSX.Element;
|
|
10
25
|
export {};
|
|
@@ -1,19 +1,117 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
1
2
|
import { useCallback, useRef } from 'react';
|
|
3
|
+
import { useRenderTarget } from '../../_shared/renderTarget';
|
|
4
|
+
import { join } from '../../utils/path';
|
|
2
5
|
import { Context as DefaultContext } from './Context';
|
|
6
|
+
import { IMTenantError } from './IMTenantError';
|
|
7
|
+
import { HYBRID_SSO_KEY, MODULE_IDS } from './modulesType';
|
|
3
8
|
const useGetBaseUrl = (baseUrl) => {
|
|
4
9
|
return useCallback(() => baseUrl, [baseUrl]);
|
|
5
10
|
};
|
|
6
11
|
const useGetTenantId = (tenantId) => {
|
|
7
12
|
return useCallback(() => tenantId, [tenantId]);
|
|
8
13
|
};
|
|
9
|
-
|
|
14
|
+
const createInitFetchModuleState = (useModuleInfo) => {
|
|
15
|
+
return useModuleInfo ? 'notFetched' : 'notRequired';
|
|
16
|
+
};
|
|
17
|
+
const useStorageKey = (baseUrl, moduleKey) => {
|
|
18
|
+
return `${baseUrl}_${moduleKey}`;
|
|
19
|
+
};
|
|
20
|
+
const saveModuleInfoStorage = async (storageKey, ModuleInfo) => {
|
|
21
|
+
return await AsyncStorage.setItem(storageKey, JSON.stringify(ModuleInfo));
|
|
22
|
+
};
|
|
23
|
+
const deleteModuleInfoStorage = async (storageKey) => {
|
|
24
|
+
return await AsyncStorage.removeItem(storageKey);
|
|
25
|
+
};
|
|
26
|
+
const getModuleInfoStorage = async (storageKey) => {
|
|
27
|
+
const state = await AsyncStorage.getItem(storageKey);
|
|
28
|
+
if (state == null)
|
|
29
|
+
return;
|
|
30
|
+
const result = JSON.parse(state);
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
33
|
+
const useFetchModuleStateRef = (renderTarget, useModuleInfo) => {
|
|
34
|
+
const ref = useRef(createInitFetchModuleState(useModuleInfo));
|
|
35
|
+
const setter = useCallback((state, silent) => {
|
|
36
|
+
if (!useModuleInfo)
|
|
37
|
+
throw new IMTenantError('useModuleInfo is not enabled');
|
|
38
|
+
if (ref.current === state)
|
|
39
|
+
return;
|
|
40
|
+
ref.current = state;
|
|
41
|
+
if (!silent)
|
|
42
|
+
renderTarget.dispatch();
|
|
43
|
+
}, []);
|
|
44
|
+
const getter = useCallback(() => ref.current, []);
|
|
45
|
+
return [ref, setter, getter];
|
|
46
|
+
};
|
|
47
|
+
const useModuleInfoRef = (getBaseUrl, fetchModuleStateSetter, useModuleInfo) => {
|
|
48
|
+
const ref = useRef();
|
|
49
|
+
const setter = useCallback((state, silent) => {
|
|
50
|
+
if (!useModuleInfo)
|
|
51
|
+
throw new IMTenantError('useModuleInfo is not enabled');
|
|
52
|
+
if (ref.current === state)
|
|
53
|
+
return;
|
|
54
|
+
ref.current = state;
|
|
55
|
+
fetchModuleStateSetter('fetched', silent);
|
|
56
|
+
}, []);
|
|
57
|
+
const getter = useCallback(() => ref.current, []);
|
|
58
|
+
const destroy = useCallback(async () => {
|
|
59
|
+
for (let i = 0; i < MODULE_IDS.length; i++) {
|
|
60
|
+
const key = MODULE_IDS[i];
|
|
61
|
+
if (key) {
|
|
62
|
+
const storageKey = useStorageKey(getBaseUrl(), key);
|
|
63
|
+
await deleteModuleInfoStorage(storageKey);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
ref.current = undefined;
|
|
67
|
+
fetchModuleStateSetter('notFetched');
|
|
68
|
+
}, []);
|
|
69
|
+
return [ref, setter, getter, destroy];
|
|
70
|
+
};
|
|
71
|
+
const useCheckHybridSSOUrl = (baseUrl) => {
|
|
72
|
+
return join(baseUrl, '/im_hybrid_sso/logout');
|
|
73
|
+
};
|
|
74
|
+
const useFetchModuleInfo = (getBaseUrl, setModuleInfo) => {
|
|
75
|
+
const hybridSSOUrl = useCheckHybridSSOUrl(getBaseUrl());
|
|
76
|
+
return useCallback(async () => {
|
|
77
|
+
const storageKey = useStorageKey(getBaseUrl(), HYBRID_SSO_KEY);
|
|
78
|
+
const storageState = await getModuleInfoStorage(storageKey);
|
|
79
|
+
if (storageState) {
|
|
80
|
+
setModuleInfo({
|
|
81
|
+
[HYBRID_SSO_KEY]: storageState,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const response = await fetch(hybridSSOUrl);
|
|
86
|
+
const hasHyBridSSO = response.status !== 404;
|
|
87
|
+
const state = {
|
|
88
|
+
moduleKey: HYBRID_SSO_KEY,
|
|
89
|
+
enabled: hasHyBridSSO,
|
|
90
|
+
othes: {},
|
|
91
|
+
};
|
|
92
|
+
saveModuleInfoStorage(storageKey, state);
|
|
93
|
+
setModuleInfo({ [HYBRID_SSO_KEY]: state });
|
|
94
|
+
}
|
|
95
|
+
}, [setModuleInfo]);
|
|
96
|
+
};
|
|
97
|
+
export const IMTenant = ({ children, baseUrl, tenantId, tenantContext, useModuleInfo, }) => {
|
|
10
98
|
const Context = tenantContext || DefaultContext;
|
|
11
99
|
const contextRef = useRef();
|
|
100
|
+
const modulesStateRenderTarget = useRenderTarget();
|
|
12
101
|
const getBaseUrl = useGetBaseUrl(baseUrl);
|
|
13
102
|
const getTenantId = useGetTenantId(tenantId);
|
|
103
|
+
const [, setFetchModuleState, getFetchModuleState] = useFetchModuleStateRef(modulesStateRenderTarget, useModuleInfo);
|
|
104
|
+
const [, setModuleInfo, getModuleInfo, destroyModuleInfo] = useModuleInfoRef(getBaseUrl, setFetchModuleState, useModuleInfo);
|
|
105
|
+
const fetchModuleInfo = useFetchModuleInfo(getBaseUrl, setModuleInfo);
|
|
106
|
+
saveModuleInfoStorage;
|
|
14
107
|
contextRef.current = {
|
|
15
108
|
getBaseUrl,
|
|
16
109
|
getTenantId,
|
|
110
|
+
getFetchModuleState,
|
|
111
|
+
getModuleInfo,
|
|
112
|
+
destroyModuleInfo,
|
|
113
|
+
fetchModuleInfo,
|
|
114
|
+
modulesStateRenderTarget,
|
|
17
115
|
};
|
|
18
116
|
return (<Context.Provider value={contextRef.current}>{children}</Context.Provider>);
|
|
19
117
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { WebView, WebViewProps } from 'react-native-webview';
|
|
3
|
+
import { WebViewSourceUri } from 'react-native-webview/lib/WebViewTypes';
|
|
4
|
+
import { IMWebViewError } from './IMWebViewError';
|
|
5
|
+
export type OnSessionCreationError = (error: IMWebViewError) => void;
|
|
6
|
+
export interface InternalIMWebViewProps extends Omit<WebViewProps, 'onShouldStartLoadWithRequest'> {
|
|
7
|
+
source: WebViewSourceUri;
|
|
8
|
+
onSessionCreationError?: OnSessionCreationError;
|
|
9
|
+
indicator?: JSX.Element | null;
|
|
10
|
+
forceLogin?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const InternalIMWebView: React.ForwardRefExoticComponent<InternalIMWebViewProps & React.RefAttributes<WebView<{}>>>;
|
|
13
|
+
export declare const internalIMWebViewStyles: {
|
|
14
|
+
indicator: {
|
|
15
|
+
position: "absolute";
|
|
16
|
+
left: number;
|
|
17
|
+
right: number;
|
|
18
|
+
top: number;
|
|
19
|
+
bottom: number;
|
|
20
|
+
alignItems: "center";
|
|
21
|
+
justifyContent: "center";
|
|
22
|
+
backgroundColor: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import { ActivityIndicator, StyleSheet } from 'react-native';
|
|
3
|
+
import { WebView } from 'react-native-webview';
|
|
4
|
+
import { useIMBaseUrl, useIMToken } from '../..';
|
|
5
|
+
import { mergeRef } from '../../utils/mergeRef';
|
|
6
|
+
import { join } from '../../utils/path';
|
|
7
|
+
import { useIMModuleInfo } from '../Tenant/hooks/useIMModuleInfo';
|
|
8
|
+
import { HYBRID_SSO_KEY } from '../Tenant/modulesType';
|
|
9
|
+
import { IMWebViewError } from './IMWebViewError';
|
|
10
|
+
const IMAUTOLOGIN_SUCCESS = 'IMAUTOLOGIN_SUCCESS';
|
|
11
|
+
const IMAUTOLOGIN_ERROR = 'IMAUTOLOGIN_ERROR';
|
|
12
|
+
const useCheckSessionUrl = (baseUrl) => {
|
|
13
|
+
return join(baseUrl, '/api/smacolow/session');
|
|
14
|
+
};
|
|
15
|
+
const useSessionEstablishmentUrl = (baseUrl) => {
|
|
16
|
+
return join(baseUrl, '/api/bearer/smacolow/session');
|
|
17
|
+
};
|
|
18
|
+
const useLogoutUrl = (baseUrl) => {
|
|
19
|
+
const { getModuleInfo } = useIMModuleInfo();
|
|
20
|
+
const info = getModuleInfo();
|
|
21
|
+
if (info && info[HYBRID_SSO_KEY]) {
|
|
22
|
+
const hybridSSO = info[HYBRID_SSO_KEY];
|
|
23
|
+
if (hybridSSO.enabled)
|
|
24
|
+
return join(baseUrl, '/login');
|
|
25
|
+
}
|
|
26
|
+
return join(baseUrl, '/logout');
|
|
27
|
+
};
|
|
28
|
+
const useSessionEstablishmentScript = (sessionEstablishmentUrl, token) => {
|
|
29
|
+
return `
|
|
30
|
+
fetch('${sessionEstablishmentUrl}', {
|
|
31
|
+
headers: {
|
|
32
|
+
'X-Intramart-Session': 'keep',
|
|
33
|
+
Authorization: 'Bearer ${token}',
|
|
34
|
+
},
|
|
35
|
+
}).then((state) => {
|
|
36
|
+
if(state.status === 200) window.ReactNativeWebView.postMessage('${IMAUTOLOGIN_SUCCESS}');
|
|
37
|
+
else window.ReactNativeWebView.postMessage('${IMAUTOLOGIN_ERROR}');
|
|
38
|
+
}).catch(() => {
|
|
39
|
+
window.ReactNativeWebView.postMessage('${IMAUTOLOGIN_ERROR}');
|
|
40
|
+
});
|
|
41
|
+
true;
|
|
42
|
+
`;
|
|
43
|
+
};
|
|
44
|
+
const useLogin = (props, webviewRef) => {
|
|
45
|
+
const baseUrl = useIMBaseUrl();
|
|
46
|
+
const token = useIMToken();
|
|
47
|
+
const targetUrl = props.source.uri;
|
|
48
|
+
const checkSessionUrl = useCheckSessionUrl(baseUrl);
|
|
49
|
+
const sessionEstablishmentUrl = useSessionEstablishmentUrl(baseUrl);
|
|
50
|
+
const logoutUrl = useLogoutUrl(baseUrl);
|
|
51
|
+
const initialUrlRef = useRef(props.forceLogin ? logoutUrl : checkSessionUrl);
|
|
52
|
+
const currentUrlRef = useRef(initialUrlRef.current);
|
|
53
|
+
const isCheckSessionErrorRef = useRef(false);
|
|
54
|
+
const [isFailedLogin, setIsFailedLogin] = useState(false);
|
|
55
|
+
const [isLogin, setIsLogin] = useState(false);
|
|
56
|
+
const [isTargetUrl, setIsTargetUrl] = useState(false);
|
|
57
|
+
const sessionEstablishmentScript = useSessionEstablishmentScript(sessionEstablishmentUrl, token);
|
|
58
|
+
const onHttpError = (event) => {
|
|
59
|
+
if (currentUrlRef.current === checkSessionUrl &&
|
|
60
|
+
event.nativeEvent.statusCode === 401) {
|
|
61
|
+
isCheckSessionErrorRef.current = true;
|
|
62
|
+
}
|
|
63
|
+
else if (currentUrlRef.current === targetUrl) {
|
|
64
|
+
if (props.onHttpError)
|
|
65
|
+
props.onHttpError(event);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
setIsFailedLogin(true);
|
|
69
|
+
if (props.onSessionCreationError) {
|
|
70
|
+
props.onSessionCreationError(new IMWebViewError('http error occurred during auto login.'));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const onShouldStartLoadWithRequest = (request) => {
|
|
75
|
+
if (isLogin || isTargetUrl)
|
|
76
|
+
return true;
|
|
77
|
+
if (currentUrlRef.current === checkSessionUrl) {
|
|
78
|
+
if (request.url === logoutUrl || request.url === checkSessionUrl)
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (currentUrlRef.current === logoutUrl) {
|
|
82
|
+
if (request.url === targetUrl || request.url === logoutUrl)
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (currentUrlRef.current === targetUrl) {
|
|
86
|
+
if (request.url === targetUrl)
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
};
|
|
91
|
+
const onLoadStart = (event) => {
|
|
92
|
+
if (currentUrlRef.current === targetUrl && !isLogin) {
|
|
93
|
+
if (props.onLoadStart)
|
|
94
|
+
props.onLoadStart(event);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const onLoadEnd = (event) => {
|
|
98
|
+
if (currentUrlRef.current === targetUrl && !isLogin) {
|
|
99
|
+
if (props.onLoadEnd)
|
|
100
|
+
props.onLoadEnd(event);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const onLoadProgress = (event) => {
|
|
104
|
+
if (currentUrlRef.current === targetUrl && !isLogin) {
|
|
105
|
+
if (props.onLoadProgress)
|
|
106
|
+
props.onLoadProgress(event);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const onLoad = (event) => {
|
|
110
|
+
if (isLogin || isFailedLogin)
|
|
111
|
+
return;
|
|
112
|
+
if ((isCheckSessionErrorRef.current === false &&
|
|
113
|
+
currentUrlRef.current === checkSessionUrl) ||
|
|
114
|
+
currentUrlRef.current === sessionEstablishmentUrl) {
|
|
115
|
+
setIsTargetUrl(true);
|
|
116
|
+
currentUrlRef.current = targetUrl;
|
|
117
|
+
webviewRef.current?.injectJavaScript(`
|
|
118
|
+
location.replace('${targetUrl}');
|
|
119
|
+
true;
|
|
120
|
+
`);
|
|
121
|
+
}
|
|
122
|
+
else if (isCheckSessionErrorRef.current === true &&
|
|
123
|
+
checkSessionUrl === currentUrlRef.current) {
|
|
124
|
+
currentUrlRef.current = logoutUrl;
|
|
125
|
+
webviewRef.current?.injectJavaScript(`
|
|
126
|
+
location.replace('${logoutUrl}');
|
|
127
|
+
true;
|
|
128
|
+
`);
|
|
129
|
+
}
|
|
130
|
+
else if (currentUrlRef.current === logoutUrl) {
|
|
131
|
+
const url = new URL(event.nativeEvent.url);
|
|
132
|
+
// because im_hybrid_sso may be called in the logoutUrl as an iframe when IM-HybridSSO is enabled
|
|
133
|
+
if (join(url.origin, url.pathname).replace(/\/$/, '') === logoutUrl) {
|
|
134
|
+
webviewRef.current?.injectJavaScript(sessionEstablishmentScript);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (currentUrlRef.current === targetUrl && !isLogin) {
|
|
138
|
+
setIsLogin(true);
|
|
139
|
+
if (props.onLoad)
|
|
140
|
+
props.onLoad(event);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const onMessage = (event) => {
|
|
144
|
+
if (currentUrlRef.current === targetUrl && props.onMessage)
|
|
145
|
+
props.onMessage(event);
|
|
146
|
+
if (event.nativeEvent.data === IMAUTOLOGIN_SUCCESS) {
|
|
147
|
+
setIsTargetUrl(true);
|
|
148
|
+
currentUrlRef.current = targetUrl;
|
|
149
|
+
webviewRef.current?.injectJavaScript(`
|
|
150
|
+
location.replace('${targetUrl}');
|
|
151
|
+
`);
|
|
152
|
+
}
|
|
153
|
+
if (event.nativeEvent.data === IMAUTOLOGIN_ERROR) {
|
|
154
|
+
setIsFailedLogin(true);
|
|
155
|
+
if (props.onSessionCreationError) {
|
|
156
|
+
props.onSessionCreationError(new IMWebViewError('http error occurred during auto login.'));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const autoLoginProps = {
|
|
161
|
+
source: {
|
|
162
|
+
uri: initialUrlRef.current,
|
|
163
|
+
},
|
|
164
|
+
sharedCookiesEnabled: false,
|
|
165
|
+
onLoad,
|
|
166
|
+
onLoadStart,
|
|
167
|
+
onLoadEnd,
|
|
168
|
+
onLoadProgress,
|
|
169
|
+
onHttpError,
|
|
170
|
+
onMessage,
|
|
171
|
+
onShouldStartLoadWithRequest,
|
|
172
|
+
};
|
|
173
|
+
const webviewProps = isLogin
|
|
174
|
+
? props
|
|
175
|
+
: isTargetUrl
|
|
176
|
+
? { ...props, ...autoLoginProps }
|
|
177
|
+
: autoLoginProps;
|
|
178
|
+
return {
|
|
179
|
+
isLogin,
|
|
180
|
+
isFailedLogin,
|
|
181
|
+
webviewProps,
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
export const InternalIMWebView = React.forwardRef((props, ref) => {
|
|
185
|
+
const webviewRef = useRef(null);
|
|
186
|
+
const margedRef = mergeRef(ref, webviewRef);
|
|
187
|
+
const { isLogin, isFailedLogin, webviewProps } = useLogin(props, webviewRef);
|
|
188
|
+
const indicator = isLogin && !isFailedLogin ? null : props.indicator !== void 0 ? (props.indicator) : (<ActivityIndicator style={internalIMWebViewStyles.indicator}/>);
|
|
189
|
+
return (<>
|
|
190
|
+
<WebView ref={margedRef} {...webviewProps}/>
|
|
191
|
+
{indicator}
|
|
192
|
+
</>);
|
|
193
|
+
});
|
|
194
|
+
export const internalIMWebViewStyles = StyleSheet.create({
|
|
195
|
+
indicator: {
|
|
196
|
+
position: 'absolute',
|
|
197
|
+
left: 0,
|
|
198
|
+
right: 0,
|
|
199
|
+
top: 0,
|
|
200
|
+
bottom: 0,
|
|
201
|
+
alignItems: 'center',
|
|
202
|
+
justifyContent: 'center',
|
|
203
|
+
backgroundColor: 'gray',
|
|
204
|
+
},
|
|
205
|
+
});
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { WebView
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
export type OnSessionCreationError = (error: IMWebViewError) => void;
|
|
6
|
-
export interface IMWebViewProps extends Omit<WebViewProps, 'onShouldStartLoadWithRequest'> {
|
|
7
|
-
source: WebViewSourceUri;
|
|
8
|
-
onSessionCreationError?: OnSessionCreationError;
|
|
9
|
-
indicator?: JSX.Element | null;
|
|
10
|
-
forceLogin?: boolean;
|
|
2
|
+
import { WebView } from 'react-native-webview';
|
|
3
|
+
import { InternalIMWebViewProps } from './InternalIMWebView';
|
|
4
|
+
export interface IMWebViewProps extends InternalIMWebViewProps {
|
|
11
5
|
}
|
|
12
6
|
export declare const IMWebView: React.ForwardRefExoticComponent<IMWebViewProps & React.RefAttributes<WebView<{}>>>;
|
|
@@ -1,171 +1,30 @@
|
|
|
1
|
-
import React, { useRef
|
|
2
|
-
import { ActivityIndicator,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { mergeRef } from '../../utils/mergeRef';
|
|
6
|
-
import { join } from '../../utils/path';
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import { ActivityIndicator, View } from 'react-native';
|
|
3
|
+
import { useIMFetchModuleState } from '../Tenant/hooks/useIMFetchModuleState';
|
|
4
|
+
import { useIMModuleInfo } from '../Tenant/hooks/useIMModuleInfo';
|
|
7
5
|
import { IMWebViewError } from './IMWebViewError';
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
19
|
-
const useLogin = (props, webviewRef) => {
|
|
20
|
-
const baseUrl = useIMBaseUrl();
|
|
21
|
-
const token = useIMToken();
|
|
22
|
-
const targetUrl = props.source.uri;
|
|
23
|
-
const checkSessionUrl = useCheckSessionUrl(baseUrl);
|
|
24
|
-
const getSessionUrl = useGetSessionUrl(baseUrl);
|
|
25
|
-
const logoutUrl = useLogoutUrl(baseUrl);
|
|
26
|
-
const initialUrlRef = useRef(props.forceLogin ? logoutUrl : checkSessionUrl);
|
|
27
|
-
const currentUrlRef = useRef(initialUrlRef.current);
|
|
28
|
-
const isCheckSessionErrorRef = useRef(false);
|
|
29
|
-
const [isFailedLogin, setIsFailedLogin] = useState(false);
|
|
30
|
-
const [isLogin, setIsLogin] = useState(false);
|
|
31
|
-
const [isTargetUrl, setIsTargetUrl] = useState(false);
|
|
32
|
-
const onHttpError = (event) => {
|
|
33
|
-
if (currentUrlRef.current === checkSessionUrl &&
|
|
34
|
-
event.nativeEvent.statusCode === 401) {
|
|
35
|
-
isCheckSessionErrorRef.current = true;
|
|
36
|
-
}
|
|
37
|
-
else if (currentUrlRef.current === targetUrl) {
|
|
38
|
-
if (props.onHttpError)
|
|
39
|
-
props.onHttpError(event);
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
setIsFailedLogin(true);
|
|
43
|
-
if (props.onSessionCreationError) {
|
|
44
|
-
props.onSessionCreationError(new IMWebViewError('http error occurred during auto login.'));
|
|
6
|
+
import { InternalIMWebView, internalIMWebViewStyles, } from './InternalIMWebView';
|
|
7
|
+
const useInitModuleState = (onSessionCreationError) => {
|
|
8
|
+
const initRef = useRef(true);
|
|
9
|
+
const fetchModuleState = useIMFetchModuleState();
|
|
10
|
+
const { fetchModuleInfo } = useIMModuleInfo();
|
|
11
|
+
if (fetchModuleState === 'notFetched' && initRef.current) {
|
|
12
|
+
initRef.current = false;
|
|
13
|
+
(async () => {
|
|
14
|
+
try {
|
|
15
|
+
await fetchModuleInfo();
|
|
45
16
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const onShouldStartLoadWithRequest = (request) => {
|
|
49
|
-
if (isLogin || isTargetUrl)
|
|
50
|
-
return true;
|
|
51
|
-
if (currentUrlRef.current === checkSessionUrl) {
|
|
52
|
-
if (request.url === logoutUrl || request.url === checkSessionUrl)
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
if (currentUrlRef.current === logoutUrl) {
|
|
56
|
-
if (request.url === targetUrl || request.url === logoutUrl)
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
if (currentUrlRef.current === targetUrl) {
|
|
60
|
-
if (request.url === targetUrl)
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
return false;
|
|
64
|
-
};
|
|
65
|
-
const onLoad = (event) => {
|
|
66
|
-
if (isLogin || isFailedLogin)
|
|
67
|
-
return;
|
|
68
|
-
if ((isCheckSessionErrorRef.current === false &&
|
|
69
|
-
currentUrlRef.current === checkSessionUrl) ||
|
|
70
|
-
currentUrlRef.current === getSessionUrl) {
|
|
71
|
-
setIsTargetUrl(true);
|
|
72
|
-
currentUrlRef.current = targetUrl;
|
|
73
|
-
webviewRef.current?.injectJavaScript(`
|
|
74
|
-
location.replace('${targetUrl}');
|
|
75
|
-
true;
|
|
76
|
-
`);
|
|
77
|
-
}
|
|
78
|
-
else if (isCheckSessionErrorRef.current === true &&
|
|
79
|
-
checkSessionUrl === currentUrlRef.current) {
|
|
80
|
-
currentUrlRef.current = logoutUrl;
|
|
81
|
-
webviewRef.current?.injectJavaScript(`
|
|
82
|
-
location.replace('${logoutUrl}');
|
|
83
|
-
true;
|
|
84
|
-
`);
|
|
85
|
-
}
|
|
86
|
-
else if (currentUrlRef.current === logoutUrl) {
|
|
87
|
-
const url = new URL(event.nativeEvent.url);
|
|
88
|
-
// because im_hybrid_sso may be called in the logoutUrl as an iframe when IM-HybridSSO is enabled
|
|
89
|
-
if (join(url.origin, url.pathname).replace(/\/$/, '') === logoutUrl) {
|
|
90
|
-
webviewRef.current?.injectJavaScript(`
|
|
91
|
-
fetch('${getSessionUrl}', {
|
|
92
|
-
headers: {
|
|
93
|
-
'X-Intramart-Session': 'keep',
|
|
94
|
-
Authorization: 'Bearer ${token}',
|
|
95
|
-
},
|
|
96
|
-
}).then((state) => {
|
|
97
|
-
if(state.status === 200) window.ReactNativeWebView.postMessage('${IMAUTOLOGIN_SUCCESS}');
|
|
98
|
-
else window.ReactNativeWebView.postMessage('${IMAUTOLOGIN_ERROR}');
|
|
99
|
-
}).catch(() => {
|
|
100
|
-
window.ReactNativeWebView.postMessage('${IMAUTOLOGIN_ERROR}');
|
|
101
|
-
});
|
|
102
|
-
true;
|
|
103
|
-
`);
|
|
17
|
+
catch (error) {
|
|
18
|
+
onSessionCreationError?.(new IMWebViewError('failed to fetch module status.'));
|
|
104
19
|
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (props.onLoad)
|
|
109
|
-
props.onLoad(event);
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
const onMessage = (event) => {
|
|
113
|
-
if (currentUrlRef.current === targetUrl && props.onMessage)
|
|
114
|
-
props.onMessage(event);
|
|
115
|
-
if (event.nativeEvent.data === IMAUTOLOGIN_SUCCESS) {
|
|
116
|
-
setIsTargetUrl(true);
|
|
117
|
-
currentUrlRef.current = targetUrl;
|
|
118
|
-
webviewRef.current?.injectJavaScript(`
|
|
119
|
-
location.replace('${targetUrl}');
|
|
120
|
-
`);
|
|
121
|
-
}
|
|
122
|
-
if (event.nativeEvent.data === IMAUTOLOGIN_ERROR) {
|
|
123
|
-
setIsFailedLogin(true);
|
|
124
|
-
if (props.onSessionCreationError) {
|
|
125
|
-
props.onSessionCreationError(new IMWebViewError('http error occurred during auto login.'));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
const autoLoginProps = {
|
|
130
|
-
source: {
|
|
131
|
-
uri: initialUrlRef.current,
|
|
132
|
-
},
|
|
133
|
-
sharedCookiesEnabled: false,
|
|
134
|
-
onLoad,
|
|
135
|
-
onHttpError,
|
|
136
|
-
onMessage,
|
|
137
|
-
onShouldStartLoadWithRequest,
|
|
138
|
-
};
|
|
139
|
-
const webviewProps = isLogin
|
|
140
|
-
? props
|
|
141
|
-
: isTargetUrl
|
|
142
|
-
? { ...props, ...autoLoginProps }
|
|
143
|
-
: autoLoginProps;
|
|
144
|
-
return {
|
|
145
|
-
isLogin,
|
|
146
|
-
isFailedLogin,
|
|
147
|
-
webviewProps,
|
|
148
|
-
};
|
|
20
|
+
})();
|
|
21
|
+
}
|
|
22
|
+
return fetchModuleState;
|
|
149
23
|
};
|
|
150
24
|
export const IMWebView = React.forwardRef((props, ref) => {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return (<>
|
|
156
|
-
<WebView ref={margedRef} {...webviewProps}/>
|
|
157
|
-
{indicator}
|
|
25
|
+
const moduleState = useInitModuleState(props.onSessionCreationError);
|
|
26
|
+
return moduleState !== 'notFetched' ? (<InternalIMWebView ref={ref} {...props}/>) : (<>
|
|
27
|
+
<View style={props.style}/>
|
|
28
|
+
<ActivityIndicator style={internalIMWebViewStyles.indicator}/>
|
|
158
29
|
</>);
|
|
159
30
|
});
|
|
160
|
-
const styles = StyleSheet.create({
|
|
161
|
-
indicator: {
|
|
162
|
-
position: 'absolute',
|
|
163
|
-
left: 0,
|
|
164
|
-
right: 0,
|
|
165
|
-
top: 0,
|
|
166
|
-
bottom: 0,
|
|
167
|
-
alignItems: 'center',
|
|
168
|
-
justifyContent: 'center',
|
|
169
|
-
backgroundColor: 'gray',
|
|
170
|
-
},
|
|
171
|
-
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intra-mart/smartlime",
|
|
3
3
|
"description": "expoで使用できるintra mart accelplatform SDK",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.4.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"intra-mart",
|
|
7
7
|
"AccelPlatform",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"expo-auth-session": "~5.4.0",
|
|
51
51
|
"expo-crypto": "~12.8.0",
|
|
52
52
|
"expo-random": "~13.6.0",
|
|
53
|
+
"expo-secure-store": "^13.0.1",
|
|
53
54
|
"react-native-webview": "13.6.4",
|
|
54
55
|
"set-cookie-parser": "^2.6.0"
|
|
55
56
|
},
|