From 04f143823e772c86c6ef13e43f5c7456c2c977b5 Mon Sep 17 00:00:00 2001 From: Antonin Cezard Date: Sat, 11 Nov 2023 10:56:26 +0100 Subject: [PATCH] refactor: migrate multiple files in libs to full TS refactor: improve typing for RootNavigation refactor: rename constants to .ts refactor: rename localStore index to .ts refactor: rename multiple files in lib/fn to ts refactor: update files with typings some tests will fail, to be fixed next commit refactor: fix tests after typing --- src/App.js | 2 +- src/components/webviews/CozyWebview.spec.js | 4 +- src/hooks/useAppBootstrap.spec.js | 2 +- src/hooks/useSession.js | 21 --- src/hooks/useSession.ts | 26 ++++ .../{RootNavigation.js => RootNavigation.ts} | 18 ++- src/libs/{constants.js => constants.ts} | 0 src/libs/functions/getHostname.js | 7 - ...etHostname.spec.js => getHostname.spec.ts} | 4 + src/libs/functions/getHostname.ts | 22 ++++ src/libs/functions/isSecureProtocol.js | 5 - ...tocol.spec.js => isSecureProtocol.spec.ts} | 18 ++- src/libs/functions/isSecureProtocol.ts | 7 + src/libs/functions/{logger.js => logger.ts} | 0 src/libs/functions/makeHandlers.js | 6 - ...eHandlers.spec.js => makeHandlers.spec.ts} | 2 + src/libs/functions/makeHandlers.ts | 15 +++ ...teHelpers.spec.js => routeHelpers.spec.ts} | 36 +++-- src/libs/functions/session.js | 90 ------------- .../{session.spec.js => session.spec.ts} | 22 ++-- src/libs/functions/session.ts | 123 ++++++++++++++++++ ...gHelpers.spec.js => stringHelpers.spec.ts} | 14 +- .../{stringHelpers.js => stringHelpers.ts} | 10 +- src/libs/functions/urlHasKonnector.js | 10 -- ...nector.spec.js => urlHasKonnector.spec.ts} | 0 src/libs/functions/urlHasKonnector.ts | 12 ++ src/libs/httpserver/server-helpers.spec.ts | 2 +- src/libs/intents/localMethods.spec.ts | 6 +- src/libs/intents/localMethods.ts | 1 + src/libs/keychain.js | 4 +- src/libs/localStore/{index.js => index.ts} | 0 src/libs/notifications/notifications.ts | 2 +- src/libs/services/NetService.ts | 12 +- src/screens/konnectors/LauncherView.js | 7 +- 34 files changed, 298 insertions(+), 212 deletions(-) delete mode 100644 src/hooks/useSession.js create mode 100644 src/hooks/useSession.ts rename src/libs/{RootNavigation.js => RootNavigation.ts} (67%) rename src/libs/{constants.js => constants.ts} (100%) delete mode 100644 src/libs/functions/getHostname.js rename src/libs/functions/{getHostname.spec.js => getHostname.spec.ts} (81%) create mode 100644 src/libs/functions/getHostname.ts delete mode 100644 src/libs/functions/isSecureProtocol.js rename src/libs/functions/{isSecureProtocol.spec.js => isSecureProtocol.spec.ts} (60%) create mode 100644 src/libs/functions/isSecureProtocol.ts rename src/libs/functions/{logger.js => logger.ts} (100%) delete mode 100644 src/libs/functions/makeHandlers.js rename src/libs/functions/{makeHandlers.spec.js => makeHandlers.spec.ts} (95%) create mode 100644 src/libs/functions/makeHandlers.ts rename src/libs/functions/{routeHelpers.spec.js => routeHelpers.spec.ts} (60%) delete mode 100644 src/libs/functions/session.js rename src/libs/functions/{session.spec.js => session.spec.ts} (89%) create mode 100644 src/libs/functions/session.ts rename src/libs/functions/{stringHelpers.spec.js => stringHelpers.spec.ts} (74%) rename src/libs/functions/{stringHelpers.js => stringHelpers.ts} (81%) delete mode 100644 src/libs/functions/urlHasKonnector.js rename src/libs/functions/{urlHasKonnector.spec.js => urlHasKonnector.spec.ts} (100%) create mode 100644 src/libs/functions/urlHasKonnector.ts rename src/libs/localStore/{index.js => index.ts} (100%) diff --git a/src/App.js b/src/App.js index bc1426614..327c86986 100644 --- a/src/App.js +++ b/src/App.js @@ -24,7 +24,7 @@ import { getClient } from '/libs/client' import { getColors } from '/ui/colors' import { localMethods } from '/libs/intents/localMethods' import { persistor, store } from '/redux/store' -import { useAppBootstrap } from '/hooks/useAppBootstrap.js' +import { useAppBootstrap } from '/hooks/useAppBootstrap' import { useGlobalAppState } from '/hooks/useGlobalAppState' import { useCookieResyncOnResume } from '/hooks/useCookieResyncOnResume' import { useCozyEnvironmentOverride } from '/hooks/useCozyEnvironmentOverride' diff --git a/src/components/webviews/CozyWebview.spec.js b/src/components/webviews/CozyWebview.spec.js index aa37e387b..085536a5f 100644 --- a/src/components/webviews/CozyWebview.spec.js +++ b/src/components/webviews/CozyWebview.spec.js @@ -18,7 +18,7 @@ jest.mock('react-native-file-viewer', () => ({ jest.mock('@react-native-cookies/cookies', () => ({ set: jest.fn() })) -jest.mock('/libs/RootNavigation.js', () => ({ +jest.mock('/libs/RootNavigation', () => ({ navigationRef: { getCurrentRoute: jest.fn().mockReturnValue({ name: 'home' }) } @@ -65,7 +65,7 @@ jest.mock('@react-navigation/native', () => ({ useIsFocused: () => mockUseIsFocused() })) -jest.mock('../../hooks/useSession.js', () => ({ +jest.mock('../../hooks/useSession', () => ({ useSession: () => ({ shouldInterceptAuth: false, handleInterceptAuth: jest.fn(), diff --git a/src/hooks/useAppBootstrap.spec.js b/src/hooks/useAppBootstrap.spec.js index 26f5f41ab..06f7465be 100644 --- a/src/hooks/useAppBootstrap.spec.js +++ b/src/hooks/useAppBootstrap.spec.js @@ -39,7 +39,7 @@ jest.mock('/libs/monitoring/Sentry', () => ({ setSentryTag: jest.fn() })) -jest.mock('/libs/RootNavigation.js', () => ({ +jest.mock('/libs/RootNavigation', () => ({ navigate: jest.fn() })) diff --git a/src/hooks/useSession.js b/src/hooks/useSession.js deleted file mode 100644 index 6700b4a47..000000000 --- a/src/hooks/useSession.js +++ /dev/null @@ -1,21 +0,0 @@ -import { useClient, useCapabilities } from 'cozy-client' - -import { useEffect, useMemo, useState } from 'react' - -import { makeSessionAPI } from '/libs/functions/session' - -export const useSession = () => { - const [subdomainType, setSubdomainType] = useState() - const client = useClient() - const { capabilities, fetchStatus } = useCapabilities(client) - - useEffect(() => { - fetchStatus === 'loaded' && - setSubdomainType(capabilities?.flat_subdomains ? 'flat' : 'nested') - }, [capabilities, fetchStatus]) - - return useMemo( - () => makeSessionAPI(client, subdomainType), - [client, subdomainType] - ) -} diff --git a/src/hooks/useSession.ts b/src/hooks/useSession.ts new file mode 100644 index 000000000..7117eed33 --- /dev/null +++ b/src/hooks/useSession.ts @@ -0,0 +1,26 @@ +import { useEffect, useMemo, useState } from 'react' + +import { useClient, useCapabilities } from 'cozy-client' + +import { makeSessionAPI, SessionApi } from '/libs/functions/session' + +export const useSession = (): SessionApi => { + const [subdomainType, setSubdomainType] = useState() + const client = useClient() + const { capabilities, fetchStatus } = useCapabilities(client) + + useEffect(() => { + fetchStatus === 'loaded' && + // @ts-expect-error : cozy-client has to be updated + setSubdomainType(capabilities?.flat_subdomains ? 'flat' : 'nested') + }, [capabilities, fetchStatus]) + + return useMemo( + // We have to assume that client and subdomainType are defined + // Still, this is old code and we should probably refactor it + // Adding a @TODO flag for now + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + () => makeSessionAPI(client!, subdomainType!), + [client, subdomainType] + ) +} diff --git a/src/libs/RootNavigation.js b/src/libs/RootNavigation.ts similarity index 67% rename from src/libs/RootNavigation.js rename to src/libs/RootNavigation.ts index d8129a8d8..1bdfcb73e 100644 --- a/src/libs/RootNavigation.js +++ b/src/libs/RootNavigation.ts @@ -7,21 +7,25 @@ import Minilog from 'cozy-minilog' const log = Minilog('RootNavigation') -export const navigationRef = createNavigationContainerRef() +export const navigationRef = + createNavigationContainerRef>() -export const getCurrentRouteName = () => { +export const getCurrentRouteName = (): string | null => { if (!navigationRef.isReady()) { return null } - return navigationRef.getCurrentRoute().name + return navigationRef.getCurrentRoute()?.name ?? null } -const isReady = () => navigationRef.isReady() +const isReady = (): boolean => navigationRef.isReady() -export const goBack = () => navigationRef.goBack() +export const goBack = (): void => navigationRef.goBack() -export const navigate = (name, params) => { +export const navigate = ( + name: string, + params?: Record +): void => { try { if (isReady()) return navigationRef.navigate(name, params) @@ -34,7 +38,7 @@ export const navigate = (name, params) => { } } -export const reset = (name, params = {}) => { +export const reset = (name: string, params = {}): void => { try { if (isReady()) return navigationRef.dispatch( diff --git a/src/libs/constants.js b/src/libs/constants.ts similarity index 100% rename from src/libs/constants.js rename to src/libs/constants.ts diff --git a/src/libs/functions/getHostname.js b/src/libs/functions/getHostname.js deleted file mode 100644 index c422580ab..000000000 --- a/src/libs/functions/getHostname.js +++ /dev/null @@ -1,7 +0,0 @@ -export const getHostname = nativeEvent => { - try { - return new URL(nativeEvent.url).hostname - } catch { - return nativeEvent?.url - } -} diff --git a/src/libs/functions/getHostname.spec.js b/src/libs/functions/getHostname.spec.ts similarity index 81% rename from src/libs/functions/getHostname.spec.js rename to src/libs/functions/getHostname.spec.ts index 86e8acf72..d3e2c452b 100644 --- a/src/libs/functions/getHostname.spec.js +++ b/src/libs/functions/getHostname.spec.ts @@ -15,3 +15,7 @@ it('does not throw if no url key', () => { it('does not throw if no event at all', () => { expect(getHostname()).toBeUndefined() }) + +it('does not throw if event with empty url', () => { + expect(getHostname({ url: undefined })).toBeUndefined() +}) diff --git a/src/libs/functions/getHostname.ts b/src/libs/functions/getHostname.ts new file mode 100644 index 000000000..7029541b2 --- /dev/null +++ b/src/libs/functions/getHostname.ts @@ -0,0 +1,22 @@ +import { getErrorMessage } from 'cozy-intent' + +import { devlog } from '/core/tools/env' + +export const getHostname = ( + nativeEvent?: { url?: string | unknown } | unknown +): string | undefined => { + if ( + !nativeEvent || + typeof nativeEvent !== 'object' || + !('url' in nativeEvent) || + typeof nativeEvent.url !== 'string' + ) + return + + try { + return new URL(nativeEvent.url).hostname + } catch (error) { + devlog('getHostname failed, nativeEvent:', nativeEvent, error) + if (getErrorMessage(error).includes('Invalid URL')) return nativeEvent.url + } +} diff --git a/src/libs/functions/isSecureProtocol.js b/src/libs/functions/isSecureProtocol.js deleted file mode 100644 index 213160946..000000000 --- a/src/libs/functions/isSecureProtocol.js +++ /dev/null @@ -1,5 +0,0 @@ -export const isSecureProtocol = client => { - const instanceUrl = new URL(client.getStackClient().uri) - - return instanceUrl.protocol === 'https:' -} diff --git a/src/libs/functions/isSecureProtocol.spec.js b/src/libs/functions/isSecureProtocol.spec.ts similarity index 60% rename from src/libs/functions/isSecureProtocol.spec.js rename to src/libs/functions/isSecureProtocol.spec.ts index 08dbeca03..94f74a621 100644 --- a/src/libs/functions/isSecureProtocol.spec.js +++ b/src/libs/functions/isSecureProtocol.spec.ts @@ -1,3 +1,5 @@ +import type CozyClient from 'cozy-client' + import { isSecureProtocol } from './isSecureProtocol' jest.mock('cozy-client', () => ({ @@ -6,25 +8,21 @@ jest.mock('cozy-client', () => ({ describe('isSecureProtocol', () => { it(`shoud return true if cozy-client's URL uses HTTPs protocol`, () => { - const client = { - getStackClient: () => ({ + const result = isSecureProtocol({ + getStackClient: (): { uri: string } => ({ uri: 'https://localhost:8080' }) - } - - const result = isSecureProtocol(client) + } as unknown as jest.Mocked) expect(result).toBe(true) }) it(`shoud return false if cozy-client's URL uses HTTP protocol`, () => { - const client = { - getStackClient: () => ({ + const result = isSecureProtocol({ + getStackClient: (): { uri: string } => ({ uri: 'http://localhost:8080' }) - } - - const result = isSecureProtocol(client) + } as unknown as jest.Mocked) expect(result).toBe(false) }) diff --git a/src/libs/functions/isSecureProtocol.ts b/src/libs/functions/isSecureProtocol.ts new file mode 100644 index 000000000..e28c7cf8a --- /dev/null +++ b/src/libs/functions/isSecureProtocol.ts @@ -0,0 +1,7 @@ +import type CozyClient from 'cozy-client' + +export const isSecureProtocol = (client: CozyClient): boolean => { + const instanceUrl = new URL(client.getStackClient().uri) + + return instanceUrl.protocol === 'https:' +} diff --git a/src/libs/functions/logger.js b/src/libs/functions/logger.ts similarity index 100% rename from src/libs/functions/logger.js rename to src/libs/functions/logger.ts diff --git a/src/libs/functions/makeHandlers.js b/src/libs/functions/makeHandlers.js deleted file mode 100644 index 7f8c6a943..000000000 --- a/src/libs/functions/makeHandlers.js +++ /dev/null @@ -1,6 +0,0 @@ -export const makeHandlers = handlers => event => - Object.keys(handlers?.constructor === Object ? handlers : {}).forEach( - handlerName => - event?.nativeEvent?.data?.includes?.(handlerName) && - handlers[handlerName]?.() - ) diff --git a/src/libs/functions/makeHandlers.spec.js b/src/libs/functions/makeHandlers.spec.ts similarity index 95% rename from src/libs/functions/makeHandlers.spec.js rename to src/libs/functions/makeHandlers.spec.ts index 97b0ed023..7c39a8dd6 100644 --- a/src/libs/functions/makeHandlers.spec.js +++ b/src/libs/functions/makeHandlers.spec.ts @@ -1,6 +1,7 @@ import { makeHandlers } from '/libs/functions/makeHandlers' it('does not throw with bad HOF bad closure', () => { + // @ts-expect-error : we want to test this case const badHandler = makeHandlers(NaN) expect(() => badHandler()).not.toThrow() }) @@ -17,6 +18,7 @@ it('does not throw with good HOF bad closure 2', () => { it('does not throw with good HOF bad closure 3', () => { const goodHandler = makeHandlers({ foo: jest.fn() }) + // @ts-expect-error : we want to test this case expect(() => goodHandler({ nativeEvent: '19' })).not.toThrow() }) diff --git a/src/libs/functions/makeHandlers.ts b/src/libs/functions/makeHandlers.ts new file mode 100644 index 000000000..fce687679 --- /dev/null +++ b/src/libs/functions/makeHandlers.ts @@ -0,0 +1,15 @@ +type makeHandlersType = ( + handlers?: Record void> +) => (event?: { nativeEvent?: { data?: string | unknown } }) => void + +export const makeHandlers: makeHandlersType = handlers => event => + Object.keys(handlers?.constructor === Object ? handlers : {}).forEach( + handlerName => { + const data = event?.nativeEvent?.data + const isString = typeof data === 'string' + + if (!isString) return + + return data.includes(handlerName) && handlers?.[handlerName]?.() + } + ) diff --git a/src/libs/functions/routeHelpers.spec.js b/src/libs/functions/routeHelpers.spec.ts similarity index 60% rename from src/libs/functions/routeHelpers.spec.js rename to src/libs/functions/routeHelpers.spec.ts index 2cbbd3282..e9988904d 100644 --- a/src/libs/functions/routeHelpers.spec.js +++ b/src/libs/functions/routeHelpers.spec.ts @@ -1,40 +1,41 @@ -import { consumeRouteParameter } from './routeHelpers' +import { NavigationProp, RouteProp } from '@react-navigation/native' + +import { consumeRouteParameter } from '/libs/functions/routeHelpers' + +const navigation = { + setParams: jest.fn() +} as unknown as NavigationProp, string> & { + setParams: jest.Mock +} describe('consumeRouteParameter', () => { beforeEach(() => { jest.clearAllMocks() }) - it('should return parameter and clear it from navigation when parameter exist', async () => { + it('should return parameter and clear it from navigation when parameter exist', () => { const route = { params: { foo: 'bar' } - } - - const navigation = { - setParams: jest.fn() - } + } as RouteProp, string> const param = consumeRouteParameter('foo', route, navigation) expect(param).toBe('bar') expect(navigation.setParams).toHaveBeenCalled() + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access expect(navigation.setParams.mock.calls[0][0]).toStrictEqual({ foo: undefined }) }) - it('should return undefined and should not try to clear it from navigation when parameter does not exist', async () => { + it('should return undefined and should not try to clear it from navigation when parameter does not exist', () => { const route = { params: { foo: 'bar' } - } - - const navigation = { - setParams: jest.fn() - } + } as RouteProp, string> const param = consumeRouteParameter('unexistingParam', route, navigation) @@ -42,21 +43,18 @@ describe('consumeRouteParameter', () => { expect(navigation.setParams).not.toHaveBeenCalled() }) - it('should return parameter and clear it from navigation when parameter exist but has falsy value', async () => { + it('should return parameter and clear it from navigation when parameter exist but has falsy value', () => { const route = { params: { foo: 0 } - } - - const navigation = { - setParams: jest.fn() - } + } as RouteProp, string> const param = consumeRouteParameter('foo', route, navigation) expect(param).toBe(0) expect(navigation.setParams).toHaveBeenCalled() + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access expect(navigation.setParams.mock.calls[0][0]).toStrictEqual({ foo: undefined }) diff --git a/src/libs/functions/session.js b/src/libs/functions/session.js deleted file mode 100644 index dcd02a444..000000000 --- a/src/libs/functions/session.js +++ /dev/null @@ -1,90 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage' -import Minilog from 'cozy-minilog' - -const log = Minilog('SessionScript') -Minilog.enable() - -import strings from '/constants/strings.json' -import { isSameCozy } from '/libs/functions/urlHelpers' - -const _throw = message => { - throw new Error(message) -} - -const appendParams = (url, key, value) => { - const searchParamsRedirect = url.searchParams.get(strings.REDIRECT) - - if (searchParamsRedirect === '' || !url) { - return _throw(strings.errorAppendParamsFail) - } - - const appendedURL = new URL(searchParamsRedirect || `${url}`) - - appendedURL.searchParams.append(key, value) - appendedURL.hash = url.hash - - return `${appendedURL}` -} - -const validateRedirect = async (cozyUrl, subDomainType, destinationUrl) => { - if (isSameCozy({ cozyUrl, destinationUrl, subDomainType })) - return destinationUrl - else _throw(strings.errors.cozyClientMismatch) -} - -const fetchSessionCode = async client => { - const { session_code } = await client.getStackClient().fetchSessionCode() - - return session_code -} - -const wrapUrl = async (client, uri) => { - const sessionCode = await fetchSessionCode(client) - - return appendParams(new URL(uri), strings.SESSION_CODE, sessionCode) -} - -const shouldCreateSession = async () => { - try { - const sessionCreatedFlag = await AsyncStorage.getItem( - strings.SESSION_CREATED_FLAG - ) - - return !sessionCreatedFlag - } catch (error) { - log.error(`Error when reading the AsyncStorage : ${error.toString()}`) - - return false - } -} - -const consumeSessionToken = () => - AsyncStorage.setItem(strings.SESSION_CREATED_FLAG, '1') - -const resetSessionToken = () => - AsyncStorage.removeItem(strings.SESSION_CREATED_FLAG) - -// Higher-order functions -const handleInterceptAuth = (client, subdomain) => async url => { - const wrappedUrl = await wrapUrl(client, url) - - return validateRedirect(client.getStackClient().uri, subdomain, wrappedUrl) -} - -const handleCreateSession = client => async uri => await wrapUrl(client, uri) - -const shouldInterceptAuth = client => url => - url.startsWith(`${client.getStackClient().uri}${strings.authLogin}`) - -// Function factory taking environment values as parameters -const makeSessionAPI = (client, subDomainType) => ({ - consumeSessionToken, - handleCreateSession: handleCreateSession(client), - handleInterceptAuth: handleInterceptAuth(client, subDomainType), - resetSessionToken, - shouldCreateSession, - shouldInterceptAuth: shouldInterceptAuth(client), - subDomainType -}) -// Exposed API -export { makeSessionAPI, resetSessionToken } diff --git a/src/libs/functions/session.spec.js b/src/libs/functions/session.spec.ts similarity index 89% rename from src/libs/functions/session.spec.js rename to src/libs/functions/session.spec.ts index e3f43268f..978cf41ce 100644 --- a/src/libs/functions/session.spec.js +++ b/src/libs/functions/session.spec.ts @@ -1,6 +1,8 @@ import AsyncStorage from '@react-native-async-storage/async-storage' -import { createMockClient } from 'cozy-client/dist/mock' +import type CozyClient from 'cozy-client' +// @ts-expect-error : cozy-client has to be updated +import type { StackClient } from 'cozy-stack-client' import { makeSessionAPI } from './session' @@ -8,13 +10,17 @@ import strings from '/constants/strings.json' const session_code = '123' const uri = 'http://cozy.10-0-2-2.nip.io:8080' -const client = createMockClient({}) +const client = {} as jest.Mocked + const subdomain = 'nested' -client.getStackClient = jest.fn(() => ({ - fetchSessionCode: () => Promise.resolve({ session_code }), - uri -})) +client.getStackClient = jest.fn( + (): StackClient => ({ + fetchSessionCode: (): Promise<{ session_code: string }> => + Promise.resolve({ session_code }), + uri + }) +) const { shouldCreateSession, @@ -27,12 +33,12 @@ const { describe('shouldCreateSession', () => { it('returns true when no token is found', async () => { - AsyncStorage.clear() + void AsyncStorage.clear() expect(await shouldCreateSession()).toBe(true) }) it('returns false when a token is found', async () => { - AsyncStorage.setItem(strings.SESSION_CREATED_FLAG, '1') + void AsyncStorage.setItem(strings.SESSION_CREATED_FLAG, '1') expect(await shouldCreateSession()).toBe(false) }) }) diff --git a/src/libs/functions/session.ts b/src/libs/functions/session.ts new file mode 100644 index 000000000..d21a1f897 --- /dev/null +++ b/src/libs/functions/session.ts @@ -0,0 +1,123 @@ +import AsyncStorage from '@react-native-async-storage/async-storage' + +import type CozyClient from 'cozy-client' +import Minilog from 'cozy-minilog' + +import { getErrorMessage } from '/libs/functions/getErrorMessage' + +const log = Minilog('SessionScript') +// @ts-expect-error : cozy-minilog has to be updated +// eslint-disable-next-line @typescript-eslint/no-unsafe-call +Minilog.enable() + +import strings from '/constants/strings.json' +import { isSameCozy } from '/libs/functions/urlHelpers' + +const _throw = (message: string): never => { + throw new Error(message) +} + +const appendParams = (url: URL | null, key: string, value: string): string => { + const searchParamsRedirect = url?.searchParams.get(strings.REDIRECT) + + if (searchParamsRedirect === '' || !url) { + return _throw(strings.errorAppendParamsFail) + } + + const appendedURL = new URL(searchParamsRedirect ?? `${url.toString()}`) + + appendedURL.searchParams.append(key, value) + appendedURL.hash = url.hash + + return `${appendedURL.toString()}` +} + +const validateRedirect = ( + cozyUrl: string, + subDomainType: string, + destinationUrl: string +): string | undefined => { + if (isSameCozy({ cozyUrl, destinationUrl, subDomainType })) + return destinationUrl + else _throw(strings.errors.cozyClientMismatch) +} + +const fetchSessionCode = async (client: CozyClient): Promise => { + const { session_code } = await client.getStackClient().fetchSessionCode() + + return session_code +} + +const wrapUrl = async ( + client: CozyClient, + uri: URL | string +): Promise => { + const sessionCode = await fetchSessionCode(client) + + return appendParams(new URL(uri), strings.SESSION_CODE, sessionCode) +} + +const shouldCreateSession = async (): Promise => { + try { + const sessionCreatedFlag = await AsyncStorage.getItem( + strings.SESSION_CREATED_FLAG + ) + + return !sessionCreatedFlag + } catch (error) { + log.error(`Error when reading the AsyncStorage : ${getErrorMessage(error)}`) + + return false + } +} + +const consumeSessionToken = (): Promise => + AsyncStorage.setItem(strings.SESSION_CREATED_FLAG, '1') + +const resetSessionToken = (): Promise => + AsyncStorage.removeItem(strings.SESSION_CREATED_FLAG) + +// Higher-order functions +const handleInterceptAuth = + (client: CozyClient, subdomain: string) => + async (url: string): Promise => { + const wrappedUrl = await wrapUrl(client, url) + + return validateRedirect(client.getStackClient().uri, subdomain, wrappedUrl) + } + +const handleCreateSession = + (client: CozyClient) => + async (uri: URL): Promise => + await wrapUrl(client, uri) + +const shouldInterceptAuth = + (client: CozyClient) => + (url: string): boolean => + url.startsWith(`${client.getStackClient().uri}${strings.authLogin}`) + +export interface SessionApi { + consumeSessionToken: () => Promise + handleCreateSession: (uri: URL) => Promise + handleInterceptAuth: (url: string) => Promise + resetSessionToken: () => Promise + shouldCreateSession: () => Promise + shouldInterceptAuth: (url: string) => boolean + subDomainType: string +} + +// Function factory taking environment values as parameters +const makeSessionAPI = ( + client: CozyClient, + subDomainType: string +): SessionApi => ({ + consumeSessionToken, + handleCreateSession: handleCreateSession(client), + handleInterceptAuth: handleInterceptAuth(client, subDomainType), + resetSessionToken, + shouldCreateSession, + shouldInterceptAuth: shouldInterceptAuth(client), + subDomainType +}) +// Exposed API +export { makeSessionAPI, resetSessionToken } diff --git a/src/libs/functions/stringHelpers.spec.js b/src/libs/functions/stringHelpers.spec.ts similarity index 74% rename from src/libs/functions/stringHelpers.spec.js rename to src/libs/functions/stringHelpers.spec.ts index 26269989c..ac6eb2809 100644 --- a/src/libs/functions/stringHelpers.spec.js +++ b/src/libs/functions/stringHelpers.spec.ts @@ -6,7 +6,7 @@ import { describe('stringHelper', () => { describe('escapeSpecialCharacters', () => { - it('should escape special characters', async () => { + it('should escape special characters', () => { const result = escapeSpecialCharacters('[.*+?^${}()|[]\\') expect(result).toBe('\\[\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\') @@ -14,19 +14,19 @@ describe('stringHelper', () => { }) describe('replaceAll', () => { - it('should replace a character if found', async () => { + it('should replace a character if found', () => { const result = replaceAll('Hello', 'o', 'a') expect(result).toBe('Hella') }) - it('should replace all characters if multiple are found', async () => { + it('should replace all characters if multiple are found', () => { const result = replaceAll('Hello', 'l', 'a') expect(result).toBe('Heaao') }) - it('should not modify the original string', async () => { + it('should not modify the original string', () => { const originalString = 'Hello' const result = replaceAll(originalString, 'o', 'a') @@ -34,13 +34,13 @@ describe('stringHelper', () => { expect(originalString).toBe('Hello') }) - it('should handle special characters (antislash)', async () => { + it('should handle special characters (antislash)', () => { const result = replaceAll('C:\\foo\\bar', '\\', '/') expect(result).toBe('C:/foo/bar') }) - it('should handle special characters (some regex random string)', async () => { + it('should handle special characters (some regex random string)', () => { const result = replaceAll('[.*+?^${}()|[]\\', '.*', '_') expect(result).toBe('[_+?^${}()|[]\\') @@ -48,7 +48,7 @@ describe('stringHelper', () => { }) describe('normalizeFqdn', () => { - it(`should replace all ':' by '_'`, async () => { + it(`should replace all ':' by '_'`, () => { const result = normalizeFqdn('cozy.tools:8080') expect(result).toBe('cozy.tools_8080') diff --git a/src/libs/functions/stringHelpers.js b/src/libs/functions/stringHelpers.ts similarity index 81% rename from src/libs/functions/stringHelpers.js rename to src/libs/functions/stringHelpers.ts index 5d085798f..d4a9a7f53 100644 --- a/src/libs/functions/stringHelpers.js +++ b/src/libs/functions/stringHelpers.ts @@ -4,7 +4,7 @@ * @param {string} str - the reference string to escape * @returns {string} */ -export const escapeSpecialCharacters = str => { +export const escapeSpecialCharacters = (str: string): string => { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } @@ -17,7 +17,11 @@ export const escapeSpecialCharacters = str => { * @param {string} replace - the string that replaces the `find` substring * @returns {string} */ -export const replaceAll = (str, find, replace) => { +export const replaceAll = ( + str: string, + find: string, + replace: string +): string => { const escapedString = escapeSpecialCharacters(find) const regex = new RegExp(escapedString, 'g') @@ -30,7 +34,7 @@ export const replaceAll = (str, find, replace) => { * @param {string} fqdn - FQDN to be normalized * @returns {string} normalized FQDN */ -export const normalizeFqdn = fqdn => { +export const normalizeFqdn = (fqdn: string): string => { const normalizedFqdn = fqdn.replace(':', '_') return normalizedFqdn diff --git a/src/libs/functions/urlHasKonnector.js b/src/libs/functions/urlHasKonnector.js deleted file mode 100644 index f2d8b26e3..000000000 --- a/src/libs/functions/urlHasKonnector.js +++ /dev/null @@ -1,10 +0,0 @@ -export const urlHasKonnectorOpen = url => { - try { - return ( - new URL(url.endsWith('/') ? url.slice(0, -1) : url).hash.split('/') - .length >= 3 - ) - } catch { - return false - } -} diff --git a/src/libs/functions/urlHasKonnector.spec.js b/src/libs/functions/urlHasKonnector.spec.ts similarity index 100% rename from src/libs/functions/urlHasKonnector.spec.js rename to src/libs/functions/urlHasKonnector.spec.ts diff --git a/src/libs/functions/urlHasKonnector.ts b/src/libs/functions/urlHasKonnector.ts new file mode 100644 index 000000000..5160825c9 --- /dev/null +++ b/src/libs/functions/urlHasKonnector.ts @@ -0,0 +1,12 @@ +export const urlHasKonnectorOpen = (url: string | URL): boolean => { + try { + const urlString = typeof url === 'string' ? url : url.toString() + const urlObject = new URL( + urlString.endsWith('/') ? urlString.slice(0, -1) : urlString + ) + const hashArray = urlObject.hash.split('/') + return hashArray.length >= 3 + } catch { + return false + } +} diff --git a/src/libs/httpserver/server-helpers.spec.ts b/src/libs/httpserver/server-helpers.spec.ts index cedd197aa..511008895 100644 --- a/src/libs/httpserver/server-helpers.spec.ts +++ b/src/libs/httpserver/server-helpers.spec.ts @@ -14,7 +14,7 @@ jest.mock('react-native-safe-area-context', () => ({ } })) -jest.mock('/libs/RootNavigation.js', () => ({ +jest.mock('/libs/RootNavigation', () => ({ navigationRef: { getCurrentRoute: jest.fn().mockReturnValue({ name: 'home' }) } diff --git a/src/libs/intents/localMethods.spec.ts b/src/libs/intents/localMethods.spec.ts index c4be6d80f..471d5bf48 100644 --- a/src/libs/intents/localMethods.spec.ts +++ b/src/libs/intents/localMethods.spec.ts @@ -1,16 +1,16 @@ import AsyncStorage from '@react-native-async-storage/async-storage' import * as Keychain from 'react-native-keychain' -import CozyClient from 'cozy-client/types/CozyClient.js' +import CozyClient from 'cozy-client/types/CozyClient' -import * as RootNavigation from '/libs/RootNavigation.js' +import * as RootNavigation from '/libs/RootNavigation' import strings from '/constants/strings.json' import { localMethods, asyncLogout } from '/libs/intents/localMethods' import { NativeMethodsRegister } from 'cozy-intent' jest.mock('react-native-keychain') -jest.mock('../RootNavigation.js') +jest.mock('../RootNavigation') jest.mock('@react-native-cookies/cookies', () => ({ clearAll: jest.fn() })) diff --git a/src/libs/intents/localMethods.ts b/src/libs/intents/localMethods.ts index 482c12ec1..94acc8074 100644 --- a/src/libs/intents/localMethods.ts +++ b/src/libs/intents/localMethods.ts @@ -193,6 +193,7 @@ interface CustomMethods { setGeolocationTrackingId: typeof setGeolocationTrackingId forceUploadGeolocationTrackingData: typeof forceUploadGeolocationTrackingData getDeviceInfo: typeof getDeviceInfo + isAvailable: typeof isAvailable } const prepareBackupWithClient = ( diff --git a/src/libs/keychain.js b/src/libs/keychain.js index 671f893c0..1d06a28f1 100644 --- a/src/libs/keychain.js +++ b/src/libs/keychain.js @@ -1,8 +1,8 @@ -import Minilog from 'cozy-minilog' import * as Keychain from 'react-native-keychain' // eslint-disable-next-line no-unused-vars -import { AccountsDoctype } from 'cozy-client/dist/types.js' +import { AccountsDoctype } from 'cozy-client/dist/types' +import Minilog from 'cozy-minilog' const log = Minilog('Keychain') diff --git a/src/libs/localStore/index.js b/src/libs/localStore/index.ts similarity index 100% rename from src/libs/localStore/index.js rename to src/libs/localStore/index.ts diff --git a/src/libs/notifications/notifications.ts b/src/libs/notifications/notifications.ts index f1389ea47..598d338d7 100644 --- a/src/libs/notifications/notifications.ts +++ b/src/libs/notifications/notifications.ts @@ -13,7 +13,7 @@ import { navigate } from '/libs/RootNavigation' import { navigateToApp } from '/libs/functions/openApp' import { saveNotificationDeviceToken } from '/libs/client' import { getErrorMessage } from '/libs/functions/getErrorMessage' -import { routes } from '/constants/routes.js' +import { routes } from '/constants/routes' const log = Minilog('notifications') diff --git a/src/libs/services/NetService.ts b/src/libs/services/NetService.ts index 4efdd15e0..be396b3dc 100644 --- a/src/libs/services/NetService.ts +++ b/src/libs/services/NetService.ts @@ -1,8 +1,8 @@ -import Minilog from 'cozy-minilog' import NetInfo, { NetInfoState } from '@react-native-community/netinfo' import { useEffect } from 'react' import CozyClient from 'cozy-client' +import Minilog from 'cozy-minilog' import strings from '/constants/strings.json' import { navigate } from '/libs/RootNavigation' @@ -36,7 +36,10 @@ export const useNetService = (client?: CozyClient): void => } }, [client]) -const waitForOnline = (callbackRoute: string, params?: unknown): void => { +const waitForOnline = ( + callbackRoute: string, + params?: Record +): void => { log.debug('Adding NetInfo listener') // Define the unsubscribe function inside the listener @@ -66,7 +69,10 @@ const isOffline = async (): Promise => (await NetInfo.fetch()).isConnected === false // Function to handle offline state by navigating to an error route and setting up listener for online state -const handleOffline = (callbackRoute: string, params?: unknown): void => { +const handleOffline = ( + callbackRoute: string, + params?: Record +): void => { navigate(routes.error, { type: strings.errorScreens.offline }) waitForOnline(callbackRoute, params) diff --git a/src/screens/konnectors/LauncherView.js b/src/screens/konnectors/LauncherView.js index 839f2f177..f6779543a 100644 --- a/src/screens/konnectors/LauncherView.js +++ b/src/screens/konnectors/LauncherView.js @@ -171,10 +171,7 @@ export class LauncherView extends Component { // This call is important because the connection backdrop has normally set the icons to white on dark // We want dark on white this.launcher.waitForWorkerVisible(() => - setFlagshipUI( - { topTheme: 'dark', bottomTheme: 'dark' }, - 'LauncherView.js' - ) + setFlagshipUI({ topTheme: 'dark', bottomTheme: 'dark' }, 'LauncherView') ) this.launcher.setLogger(this.props.onKonnectorLog) @@ -243,7 +240,7 @@ export class LauncherView extends Component { // Not strictly necessary, but to err on the side of caution we reset the flagship UI to dark // In unlikely scenarios it is possible that the flagship UI is still set to light on white (connectionBackdrop) - setFlagshipUI({ topTheme: 'dark', bottomTheme: 'dark' }, 'LauncherView.js') + setFlagshipUI({ topTheme: 'dark', bottomTheme: 'dark' }, 'LauncherView') } render() {