Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement offline support through CozyPouchLink #1209

Open
wants to merge 17 commits into
base: feat/meta_offline
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This notably includes the possiblity to run client-side konnectors, to get your
- [iOS only] Install XCode
- [Android only] Install Android Studio (or Android SDK)
- [Android only] Java 11
- [Android only] Install NDK (21.4.7075529) and CMake (3.10.2) from Android Studio's SDK Manager
- [Android only] Copy the Android's `debug.keystore` from Cozy's password-store into `android/app/debug.keystore`
- Run `pass show app-amirale/Certificates/debug.keystore > android/app/debug.keystore`
- If you don't have access to Cozy's password-store, just generate a new `debug.keystore` file
Expand Down
2 changes: 1 addition & 1 deletion __tests__/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const config = {
'<rootDir>/__tests__/transformer/imageTransformer.js'
},
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native(-.*)?|@react-native(-community)?|p-timeout?|p-wait-for?|@notifee?)|@fengweichong/react-native-gzip?/)'
'node_modules/(?!((jest-)?react-native(-.*)?|@react-native(-community)?|p-timeout?|p-wait-for?|@notifee?)|@fengweichong/react-native-gzip|@craftzdog/*?/)'
],
rootDir: '../'
}
Expand Down
21 changes: 21 additions & 0 deletions __tests__/jestSetupFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,24 @@ jest.mock('../src/core/tools/env', () => ({
devlog: jest.fn(),
shouldDisableAutolock: jest.fn().mockReturnValue(false)
}))

jest.mock('../src/pouchdb/pouchdb', () => ({}))
jest.mock('react-native-quick-websql', () => ({}))

class mockPouchLink {
constructor() {}
}

jest.mock('cozy-pouch-link', () => {
return jest.fn().mockImplementation(() => {
return new mockPouchLink()
})
})

jest.mock('react-native-mail', () => ({
mail: jest.fn()
}))

jest.mock('rn-fetch-blob', () => ({
mail: jest.fn()
}))
2 changes: 2 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ buildscript {
googlePlayServicesLocationVersion = "20.0.0"
DocumentScanner_compileSdkVersion = 34
DocumentScanner_targetSdkVersion = 34
QuickBase64_compileSdkVersion = 34
QuickBase64_targetSdkVersion = 34
kotlinVersion = '1.8.0' // react-native-receive-sharing-intent requires a global kotlin version else the build fails
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
Expand Down
1 change: 1 addition & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
root: ['./'],
alias: {
'^/(.+)': './src/\\1',
'pouchdb-collate': '@craftzdog/pouchdb-collate-react-native',
'@cozy/minilog': 'cozy-minilog'
},
extensions: [
Expand Down
33 changes: 33 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -502,18 +502,29 @@ PODS:
- React-Core
- react-native-flipper (0.146.1):
- React-Core
- react-native-get-random-values (1.11.0):
- React-Core
- react-native-gzip (2.0.0):
- NVHTarGzip
- React
- react-native-idle-timer (2.2.1):
- React-Core
- react-native-mail (6.1.1):
- React-Core
- react-native-mlkit-ocr (0.3.0):
- GoogleMLKit/TextRecognition (= 2.6.0)
- React
- react-native-netinfo (9.5.0):
- React-Core
- react-native-print (0.11.0):
- React-Core
- react-native-quick-base64 (2.1.2):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- react-native-quick-sqlite (8.0.6):
- React
- React-callinvoker
- React-Core
- react-native-receive-sharing-intent (2.2.1):
- React-Core
- react-native-restart (0.0.27):
Expand Down Expand Up @@ -639,6 +650,8 @@ PODS:
- React-jsi (= 0.72.12)
- React-logger (= 0.72.12)
- React-perflogger (= 0.72.12)
- rn-fetch-blob (0.12.0):
- React-Core
- RNBackgroundFetch (4.2.5):
- React-Core
- RNBackgroundGeolocation (4.16.4):
Expand Down Expand Up @@ -764,11 +777,15 @@ DEPENDENCIES:
- "react-native-cookies (from `../node_modules/@react-native-cookies/cookies`)"
- react-native-document-scanner-plugin (from `../node_modules/react-native-document-scanner-plugin`)
- react-native-flipper (from `../node_modules/react-native-flipper`)
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
- "react-native-gzip (from `../node_modules/@fengweichong/react-native-gzip`)"
- react-native-idle-timer (from `../node_modules/react-native-idle-timer`)
- react-native-mail (from `../node_modules/react-native-mail`)
- react-native-mlkit-ocr (from `../node_modules/react-native-mlkit-ocr`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-print (from `../node_modules/react-native-print`)
- react-native-quick-base64 (from `../node_modules/react-native-quick-base64`)
- react-native-quick-sqlite (from `../node_modules/react-native-quick-sqlite`)
- "react-native-receive-sharing-intent (from `../node_modules/@mythologi/react-native-receive-sharing-intent`)"
- react-native-restart (from `../node_modules/react-native-restart`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
Expand All @@ -791,6 +808,7 @@ DEPENDENCIES:
- React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- rn-fetch-blob (from `../node_modules/rn-fetch-blob`)
- RNBackgroundFetch (from `../node_modules/react-native-background-fetch`)
- RNBackgroundGeolocation (from `../node_modules/react-native-background-geolocation`)
- RNBootSplash (from `../node_modules/react-native-bootsplash`)
Expand Down Expand Up @@ -919,16 +937,24 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-document-scanner-plugin"
react-native-flipper:
:path: "../node_modules/react-native-flipper"
react-native-get-random-values:
:path: "../node_modules/react-native-get-random-values"
react-native-gzip:
:path: "../node_modules/@fengweichong/react-native-gzip"
react-native-idle-timer:
:path: "../node_modules/react-native-idle-timer"
react-native-mail:
:path: "../node_modules/react-native-mail"
react-native-mlkit-ocr:
:path: "../node_modules/react-native-mlkit-ocr"
react-native-netinfo:
:path: "../node_modules/@react-native-community/netinfo"
react-native-print:
:path: "../node_modules/react-native-print"
react-native-quick-base64:
:path: "../node_modules/react-native-quick-base64"
react-native-quick-sqlite:
:path: "../node_modules/react-native-quick-sqlite"
react-native-receive-sharing-intent:
:path: "../node_modules/@mythologi/react-native-receive-sharing-intent"
react-native-restart:
Expand Down Expand Up @@ -973,6 +999,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/react/utils"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
rn-fetch-blob:
:path: "../node_modules/rn-fetch-blob"
RNBackgroundFetch:
:path: "../node_modules/react-native-background-fetch"
RNBackgroundGeolocation:
Expand Down Expand Up @@ -1089,11 +1117,15 @@ SPEC CHECKSUMS:
react-native-cookies: f54fcded06bb0cda05c11d86788020b43528a26c
react-native-document-scanner-plugin: df5b82df67ff612262c40c26ef2c8239c5af5c55
react-native-flipper: 4bfe0a324e663f1ae2f76ad0da75673de6895efe
react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06
react-native-gzip: 5ffb84bf191c7cd135338eca748317bc466d41a1
react-native-idle-timer: f1920a59fe776340d004ff9de13c4a6eedcc8807
react-native-mail: 8fdcd3aef007c33a6877a18eb4cf7447a1d4ce4a
react-native-mlkit-ocr: 72cdbde86f8d29cba26cf9fa0a1865fe45c8f8d6
react-native-netinfo: 48c5f79a84fbc3ba1d28a8b0d04adeda72885fa8
react-native-print: f704aef52d931bfce6d1d84351dbb5232d7ecb89
react-native-quick-base64: 42a805dcf2ae4f38c4d2c7e7a3c7dd936e5d248a
react-native-quick-sqlite: e0e23b749382a85e4b57146f753de737a6c3a9e1
react-native-receive-sharing-intent: 0c21b8e80f629a73341f2566ce9b99df8124bb10
react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
Expand All @@ -1116,6 +1148,7 @@ SPEC CHECKSUMS:
React-runtimescheduler: 8aea338c561b2175f47018124c076d89d3808d30
React-utils: 9a24cb88f950d1020ee55bddacbc8c16a611e2dc
ReactCommon: 76843a9bb140596351ac2786257ac9fe60cafabb
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNBackgroundFetch: 2f800a04434620db15ede2e8f21886608a2d1743
RNBackgroundGeolocation: 7df16548756b443aac809663cba8a4e03f0b8732
RNBootSplash: 4844706cbb56a3270556c9b94e59dedadccd47e4
Expand Down
18 changes: 16 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"storybook:update": "sb-rn-get-stories"
},
"dependencies": {
"@craftzdog/pouchdb-collate-react-native": "^7.3.0",
"@fengweichong/react-native-gzip": "github:cozy/react-native-gzip#1.1.0",
"@mythologi/react-native-receive-sharing-intent": "2.2.1",
"@notifee/react-native": "^7.8.0",
Expand All @@ -54,14 +55,16 @@
"@sentry/integrations": "7.81.1",
"@sentry/react-native": "5.16.0",
"base-64": "^1.0.0",
"cozy-client": "^48.5.0",
"cozy-client": "^49.0.0",
"cozy-clisk": "^0.36.1",
"cozy-device-helper": "^2.7.0",
"cozy-flags": "^3.2.0",
"cozy-intent": "^2.22.0",
"cozy-intent": "^2.23.0",
"cozy-logger": "^1.10.0",
"cozy-minilog": "3.3.1",
"cozy-pouch-link": "^49.0.0",
"date-fns": "2.29.3",
"events": "^3.3.0",
"html-entities": "^2.3.3",
"i18next": "23.7.16",
"i18next-intervalplural-postprocessor": "3.0.0",
Expand All @@ -73,6 +76,11 @@
"patch-package": "^8.0.0",
"post-me": "^0.4.5",
"postinstall-postinstall": "^2.1.0",
"pouchdb-adapter-http": "^8.0.1",
"pouchdb-adapter-react-native-sqlite": "^3.0.1",
"pouchdb-core": "^8.0.1",
"pouchdb-mapreduce": "^8.0.1",
"pouchdb-replication": "^8.0.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-i18next": "14.0.0",
Expand All @@ -91,6 +99,7 @@
"react-native-flipper": "^0.146.1",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "1.10.3",
"react-native-get-random-values": "^1.11.0",
"react-native-google-play-integrity": "github:cozy/react-native-google-play-integrity#1.0.1",
"react-native-iap": "^12.11.0",
"react-native-idle-timer": "^2.2.1",
Expand All @@ -99,11 +108,15 @@
"react-native-ios11-devicecheck": "https://github.com/cozy/react-native-devicecheck#app-attest-v0.1",
"react-native-keychain": "^8.0.0",
"react-native-localize": "2.2.6",
"react-native-mail": "^6.1.1",
"react-native-mask-input": "1.2.1",
"react-native-mlkit-ocr": "^0.3.0",
"react-native-permissions": "^3.9.3",
"react-native-play-install-referrer": "^1.1.8",
"react-native-print": "0.11.0",
"react-native-quick-base64": "2.1.2",
"react-native-quick-sqlite": "8.0.6",
"react-native-quick-websql": "^0.3.0",
"react-native-restart": "^0.0.27",
"react-native-safe-area-context": "^4.5.0",
"react-native-screens": "3.32.0",
Expand All @@ -116,6 +129,7 @@
"react-scripts": "4.0.3",
"redux-logger": "3.0.6",
"redux-persist": "^6.0.0",
"rn-fetch-blob": "^0.12.0",
"rn-flipper-async-storage-advanced": "^1.0.4",
"semver": "^7.3.2"
},
Expand Down
12 changes: 11 additions & 1 deletion src/@types/cozy-client.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
/* eslint-disable @typescript-eslint/no-unused-vars */

import 'cozy-client'
import { FileDocument, CozyClientDocument } from 'cozy-client/types/types'
import { QueryDefinition } from 'cozy-client'
import {
FileDocument,
CozyClientDocument,
QueryOptions,
QueryResult
} from 'cozy-client/types/types'

declare module 'cozy-client' {
interface ClientOptions {
Expand Down Expand Up @@ -157,6 +163,10 @@ declare module 'cozy-client' {
on: (event: string, callback: () => void) => void
removeListener: (event: string, callback: () => void) => void
logout: () => Promise<void>
query: (
queryDefinition: QueryDefinition,
options?: QueryOptions
) => Promise<QueryResult>
}

export const createMockClient = (options?: ClientOptions): CozyClient =>
Expand Down
4 changes: 3 additions & 1 deletion src/components/webviews/CozyProxyWebView.functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export const initHtmlContent = async ({
return
}

const htmlContent = await httpServerContext.getIndexHtmlForSlug(slug, client)
const { html: htmlContent, source: htmlSource } =
await httpServerContext.getIndexHtmlForSlug(slug, client)

if (
!cookieAlreadyExists &&
Expand All @@ -47,6 +48,7 @@ export const initHtmlContent = async ({
setHtmlContentCreationDate(Date.now())
dispatch(oldState => ({
...oldState,
activateCache: htmlSource === 'cache' && Platform.OS === 'android',
html: htmlContent,
nativeConfig: nativeConfigActual,
source: sourceActual
Expand Down
3 changes: 3 additions & 0 deletions src/components/webviews/CozyProxyWebView.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ export const CozyProxyWebView = ({
dispatch(oldState => ({ ...oldState, isLoading: false }))
onLoad?.(syntheticEvent)
}}
cacheMode={
state.activateCache ? 'LOAD_CACHE_ELSE_NETWORK' : 'LOAD_DEFAULT'
}
{...props}
/>
) : null}
Expand Down
5 changes: 4 additions & 1 deletion src/components/webviews/CozyProxyWebView.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ describe('CozyWebview', () => {

describe('OAuth Client Limit', () => {
const httpServerContext = {
getIndexHtmlForSlug: jest.fn()
getIndexHtmlForSlug: jest.fn().mockResolvedValue({
source: 'stack',
html: 'SOME_HTML'
})
}
const href = 'https://claude-home.mycozy.cloud'
const client = {}
Expand Down
1 change: 1 addition & 0 deletions src/components/webviews/CozyWebview.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ jest.mock('cozy-intent', () => ({
}))

jest.mock('cozy-client', () => ({
...jest.requireActual('cozy-client'),
useClient: jest.fn().mockReturnValue({}),
useInstanceInfo: jest.fn().mockReturnValue({})
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const makeMetadata = (routeName?: string): string => {
return JSON.stringify({
immersive: routeName ? immersiveRoutes.includes(routeName) : false,
navbarHeight,
offline_available: true,
Ldoppea marked this conversation as resolved.
Show resolved Hide resolved
platform: Platform,
routeName,
statusBarHeight,
Expand Down
5 changes: 5 additions & 0 deletions src/hooks/useAppBootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useEffect, useState } from 'react'
import { deconstructCozyWebLinkWithSlug } from 'cozy-client'

import { handleLogsDeepLink } from '/app/domain/logger/deeplinkHandler'
import { handleDbDeepLink } from '/pouchdb/deeplinkHandler'
import { SentryCustomTags, setSentryTag } from '/libs/monitoring/Sentry'
import { manageIconCache } from '/libs/functions/iconTable'
import { getDefaultIconParams } from '/libs/functions/openApp'
Expand Down Expand Up @@ -160,6 +161,10 @@ export const useAppBootstrap = client => {
return
}

if (handleDbDeepLink(url, client)) {
return
}

if (!client) {
const action = parseOnboardLink(url)

Expand Down
1 change: 1 addition & 0 deletions src/hooks/useAppBootstrap.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jest.mock('/libs/RootNavigation', () => ({
jest.mock('./useSplashScreen', () => ({
useSplashScreen: () => ({ hideSplashScreen: mockHideSplashScreen })
}))
jest.mock('/app/theme/SplashScreenService', () => ({}))

jest.mock('/libs/functions/openApp', () => ({
getDefaultIconParams: jest.fn().mockReturnValue({})
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import '/pouchdb/shim'

import { AppRegistry } from 'react-native'

import App from './App'
Expand Down
Loading
Loading