From b9f9ba62fdd84c72a5fa06b3c94b1baeae0736f6 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 22 May 2024 10:48:38 +0200 Subject: [PATCH 01/92] feat: Add react-native modules for PouchDB `react-native-quick-sqlite` is enforced to version `8.0.6` in order to be compatible with RN0.72 Related article: https://dev.to/craftzdog/a-performant-way-to-use-pouchdb7-on-react-native-in-2022-24ej --- README.md | 1 + __tests__/jest.config.js | 2 +- __tests__/jestSetupFile.js | 2 + android/build.gradle | 2 + babel.config.js | 1 + ios/Podfile.lock | 22 + package.json | 11 + yarn.lock | 820 +++++++++++++++++++++++++++++++++++-- 8 files changed, 832 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index f9aebd055..678d973e2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/__tests__/jest.config.js b/__tests__/jest.config.js index 0d6117ffd..f94580a2e 100644 --- a/__tests__/jest.config.js +++ b/__tests__/jest.config.js @@ -13,7 +13,7 @@ const config = { '/__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: '../' } diff --git a/__tests__/jestSetupFile.js b/__tests__/jestSetupFile.js index d6bd11cdb..a546def4a 100644 --- a/__tests__/jestSetupFile.js +++ b/__tests__/jestSetupFile.js @@ -127,3 +127,5 @@ jest.mock('../src/core/tools/env', () => ({ devlog: jest.fn(), shouldDisableAutolock: jest.fn().mockReturnValue(false) })) + +jest.mock('react-native-quick-websql', () => ({})) diff --git a/android/build.gradle b/android/build.gradle index bbddb793a..deed068e6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -10,6 +10,8 @@ buildscript { googlePlayServicesLocationVersion = "20.0.0" DocumentScanner_compileSdkVersion = 34 DocumentScanner_targetSdkVersion = 34 + QuickBase64_compileSdkVersion = 34 + QuickBase64_targetSdkVersion = 34 ndkVersion = "25.1.8937393" kotlinVersion = "1.8.0" // To solve a conflict between flipper and react-native-background-geolocation diff --git a/babel.config.js b/babel.config.js index 84c149369..66301c1ae 100644 --- a/babel.config.js +++ b/babel.config.js @@ -8,6 +8,7 @@ module.exports = { root: ['./'], alias: { '^/(.+)': './src/\\1', + 'pouchdb-collate': '@craftzdog/pouchdb-collate-react-native', '@cozy/minilog': 'cozy-minilog' }, extensions: [ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6b2e2ed6a..074c8d3ee 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1079,6 +1079,8 @@ PODS: - React-Core - react-native-flipper (0.269.0): - React-Core + - react-native-get-random-values (1.11.0): + - React-Core - react-native-gzip (2.0.0): - NVHTarGzip - React @@ -1091,6 +1093,14 @@ PODS: - React-Core - react-native-print (0.11.0): - React-Core + - react-native-quick-base64 (2.1.2): + - glog + - RCT-Folly (= 2022.05.16.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): @@ -1407,11 +1417,14 @@ 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-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`) @@ -1576,6 +1589,8 @@ 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: @@ -1586,6 +1601,10 @@ EXTERNAL SOURCES: :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: @@ -1758,11 +1777,14 @@ SPEC CHECKSUMS: react-native-cookies: f54fcded06bb0cda05c11d86788020b43528a26c react-native-document-scanner-plugin: df5b82df67ff612262c40c26ef2c8239c5af5c55 react-native-flipper: 2a568e19c9d7051f01bf9725f886474fc818ba05 + react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 react-native-gzip: 5ffb84bf191c7cd135338eca748317bc466d41a1 react-native-idle-timer: f1920a59fe776340d004ff9de13c4a6eedcc8807 react-native-mlkit-ocr: 72cdbde86f8d29cba26cf9fa0a1865fe45c8f8d6 react-native-netinfo: 48c5f79a84fbc3ba1d28a8b0d04adeda72885fa8 react-native-print: f704aef52d931bfce6d1d84351dbb5232d7ecb89 + react-native-quick-base64: e1ea036b3dec44c6da2439bd62881a09de614b23 + react-native-quick-sqlite: e0e23b749382a85e4b57146f753de737a6c3a9e1 react-native-receive-sharing-intent: 0c21b8e80f629a73341f2566ce9b99df8124bb10 react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162 react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc diff --git a/package.json b/package.json index 3e1180711..cd6adc753 100644 --- a/package.json +++ b/package.json @@ -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", @@ -63,6 +64,7 @@ "cozy-logger": "^1.10.0", "cozy-minilog": "3.3.1", "date-fns": "2.29.3", + "events": "^3.3.0", "html-entities": "^2.3.3", "i18next": "23.7.16", "i18next-intervalplural-postprocessor": "3.0.0", @@ -74,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", @@ -92,6 +99,7 @@ "react-native-flipper": "^0.269.0", "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", @@ -105,6 +113,9 @@ "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", diff --git a/yarn.lock b/yarn.lock index b6dcbe10a..5d7feefce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3089,6 +3089,24 @@ dependencies: microee "0.0.6" +"@craftzdog/pouchdb-adapter-websql-core@^7.2.3": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@craftzdog/pouchdb-adapter-websql-core/-/pouchdb-adapter-websql-core-7.2.3.tgz#01ebaaf71dd7b844c66c58ae8cc460ec3aeb8009" + integrity sha512-bXt9rOwMgUEyQhcr8KWDAhOA1J7XfI//mrlEg0O3KQo81Ck5i6oxWU+BYpw9wC2pnKmhboRkIwFAz9QQbHefrg== + dependencies: + pouchdb-adapter-utils "^7.2.2" + pouchdb-binary-utils "^7.2.2" + pouchdb-collections "^7.2.2" + pouchdb-errors "^7.2.2" + pouchdb-json "^7.2.2" + pouchdb-merge "^7.2.2" + pouchdb-utils "^7.2.2" + +"@craftzdog/pouchdb-collate-react-native@^7.3.0": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@craftzdog/pouchdb-collate-react-native/-/pouchdb-collate-react-native-7.3.0.tgz#4bc25c8d11f1f270f5c7de4a1a4c4819b04e4147" + integrity sha512-ZC1fBSt7lBy8A3bGLsZVuDf/f3NWTOj+x0ygYUMWPCZPtzKfbRWdirv/pPKKxsO09Aw2EGuo94v51UOAIQkpRQ== + "@csstools/convert-colors@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" @@ -3255,6 +3273,11 @@ version "2.0.0" resolved "https://codeload.github.com/cozy/react-native-gzip/tar.gz/41178cf223fe3c866dfa6ede78a881461de066fd" +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + "@hapi/address@2.x.x": version "2.1.4" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" @@ -4163,6 +4186,14 @@ resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-7.8.0.tgz#2990883753990f3585aa0cb5becc5cbdbcd87a43" integrity sha512-sx8h62U4FrR4pqlbN1rkgPsdamDt9Tad0zgfO6VtP6rNJq/78k8nxUnh0xIX3WPDcCV8KAzdYCE7+UNvhF1CpQ== +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + "@npmcli/move-file@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" @@ -6486,7 +6517,12 @@ abab@^2.0.3, abab@^2.0.5: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== -abort-controller@^3.0.0: +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +abort-controller@3.0.0, abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== @@ -6562,13 +6598,20 @@ adjust-sourcemap-loader@3.0.0: loader-utils "^2.0.0" regex-parser "^2.2.11" -agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" +agentkeepalive@^4.1.3: + version "4.5.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" + integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== + dependencies: + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -6769,6 +6812,14 @@ are-we-there-yet@^2.0.0: delegates "^1.0.0" readable-stream "^3.6.0" +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -6781,6 +6832,11 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +argsarray@0.0.1, argsarray@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb" + integrity sha512-u96dg2GcAKtpTrBdDoFIM7PjcBA+6rSP0OR94MOReNRyUECL6MtQt5XXmRr4qrftYaef9+l5hcpO5te7sML1Cg== + aria-query@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" @@ -7522,7 +7578,7 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^4.1.0: +bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -7776,6 +7832,11 @@ buffer-equal@0.0.1: resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" integrity sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA== +buffer-from@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -7877,6 +7938,30 @@ cacache@^15.0.5: tar "^6.0.2" unique-filename "^1.1.1" +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -8190,6 +8275,11 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +clone-buffer@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + integrity sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g== + clone-deep@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713" @@ -8281,7 +8371,7 @@ color-string@^1.9.0: color-name "^1.0.0" simple-swizzle "^0.2.2" -color-support@^1.1.2: +color-support@^1.1.2, color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== @@ -9062,7 +9152,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -9096,6 +9186,13 @@ decode-uri-component@^0.2.2: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -9123,6 +9220,11 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -9268,6 +9370,11 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +detect-libc@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -9566,7 +9673,14 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +encoding@^0.1.12: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -9612,6 +9726,11 @@ entities@^4.2.0, entities@^4.4.0: resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + envinfo@^7.10.0: version "7.14.0" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.14.0.tgz#26dac5db54418f2a4c1159153a0b2ae980838aae" @@ -9622,6 +9741,11 @@ envinfo@^7.7.3: resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + errno@^0.1.3, errno@~0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" @@ -10387,7 +10511,7 @@ eventemitter3@^4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.0.0, events@^3.2.0: +events@^3.0.0, events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -10478,6 +10602,11 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + expect@^26.6.0, expect@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" @@ -10578,6 +10707,11 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +fast-base64-decode@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418" + integrity sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q== + fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -10684,6 +10818,13 @@ fbjs@^3.0.0: setimmediate "^1.0.5" ua-parser-js "^0.7.18" +fetch-cookie@0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/fetch-cookie/-/fetch-cookie-0.11.0.tgz#e046d2abadd0ded5804ce7e2cae06d4331c15407" + integrity sha512-BQm7iZLFhMWFy5CZ/162sAGjBfdNWb7a8LEqqnzsHFhxT/X/SVj/z2t2nu3aJvjlbQkrAlTUApplPRjWyH4mhA== + dependencies: + tough-cookie "^2.3.3 || ^3.0.1 || ^4.0.0" + figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -10973,6 +11114,11 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -11124,6 +11270,20 @@ gauge@^3.0.0: strip-ansi "^6.0.1" wide-align "^1.1.2" +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -11217,6 +11377,11 @@ gifwrap@^0.9.2: image-q "^4.0.0" omggif "^1.0.10" +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -11381,6 +11546,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + grapheme-splitter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" @@ -11683,6 +11853,11 @@ htmlparser2@^3.10.1: inherits "^2.0.1" readable-stream "^3.1.1" +http-cache-semantics@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -11776,6 +11951,13 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + i18next-intervalplural-postprocessor@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/i18next-intervalplural-postprocessor/-/i18next-intervalplural-postprocessor-3.0.0.tgz#35580abdaff5e838c44c22740a7178063b7bdd0b" @@ -11795,6 +11977,13 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + icss-utils@^4.0.0, icss-utils@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" @@ -11843,6 +12032,11 @@ image-size@^1.0.2: dependencies: queue "6.0.2" +immediate@3.3.0, immediate@^3.2.2: + version "3.3.0" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.3.0.tgz#1aef225517836bcdf7f2a2de2600c79ff0269266" + integrity sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -11947,7 +12141,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.5: +ini@^1.3.5, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -12000,6 +12194,14 @@ invariant@2.2.4, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -12288,6 +12490,11 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + is-map@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" @@ -13519,6 +13726,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + jsc-android@^250231.0.0: version "250231.0.0" resolved "https://registry.yarnpkg.com/jsc-android/-/jsc-android-250231.0.0.tgz#91720f8df382a108872fa4b3f558f33ba5e95262" @@ -14043,6 +14255,28 @@ make-error@1.x: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -14455,6 +14689,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -14513,7 +14752,7 @@ minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@^1.2.6: +minimist@^1.2.3, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -14525,6 +14764,17 @@ minipass-collect@^1.0.2: dependencies: minipass "^3.0.0" +minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + minipass-flush@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" @@ -14532,13 +14782,20 @@ minipass-flush@^1.0.5: dependencies: minipass "^3.0.0" -minipass-pipeline@^1.2.2: +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== dependencies: minipass "^3.0.0" +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + minipass@^3.0.0, minipass@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" @@ -14546,14 +14803,19 @@ minipass@^3.0.0, minipass@^3.1.1: dependencies: yallist "^4.0.0" -minipass@^3.3.5: +minipass@^3.1.0, minipass@^3.1.3, minipass@^3.3.5: version "3.3.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== dependencies: yallist "^4.0.0" -minizlib@^2.1.1: +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -14593,6 +14855,11 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -14632,7 +14899,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1: +ms@^2.0.0, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -14677,6 +14944,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + native-url@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae" @@ -14699,6 +14971,11 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +negotiator@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" @@ -14737,11 +15014,23 @@ nock@13.2.9: lodash "^4.17.21" propagate "^2.0.0" +node-abi@^3.3.0: + version "3.65.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.65.0.tgz#ca92d559388e1e9cab1680a18c1a18757cdac9d3" + integrity sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA== + dependencies: + semver "^7.3.5" + node-abort-controller@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + node-dir@^0.1.17: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -14754,6 +15043,13 @@ node-fetch@2.6.1, node-fetch@^2.2.0, node-fetch@^2.6.0: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.7, node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^2.6.1: version "2.6.9" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" @@ -14761,18 +15057,27 @@ node-fetch@^2.6.1: dependencies: whatwg-url "^5.0.0" -node-fetch@^2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== +node-gyp@8.x: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -14860,6 +15165,18 @@ node-stream-zip@^1.9.1: resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.13.4.tgz#baafc329ffb9e27de84b6882d74e9f2cbe77e2a5" integrity sha512-M2nPvnSWFFH+fgLIRZDqmhshmuzXcr+ce9BsHQX/30pXR+cEz/USMYmx9ZAFYy837W2QoDoNzhFtbZhfzaMk9A== +noop-fn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/noop-fn/-/noop-fn-1.0.0.tgz#5f33d47f13d2150df93e0cb036699e982f78ffbf" + integrity sha512-pQ8vODlgXt2e7A3mIbFDlizkr46r75V+BJxVAyat8Jl7YmI513gG5cfyRL0FedKraoZ+VAouI1h4/IWpus5pcQ== + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -14926,6 +15243,16 @@ npmlog@^5.0.1: gauge "^3.0.0" set-blocking "^2.0.0" +npmlog@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + nth-check@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" @@ -16401,6 +16728,251 @@ postinstall-postinstall@^2.1.0: resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== +pouchdb-abstract-mapreduce@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-abstract-mapreduce/-/pouchdb-abstract-mapreduce-8.0.1.tgz#f45aa1424fdd272a76ceb5722a53b38c10a0ca81" + integrity sha512-BxJRHdfiC8gID8h4DPS0Xy6wsa2VBHRHMv9hsm0BhGTWTqS4k8ivItVSeU2dMoXiTBYp+7SerYmovUQNGSX1GA== + dependencies: + pouchdb-binary-utils "8.0.1" + pouchdb-collate "8.0.1" + pouchdb-collections "8.0.1" + pouchdb-errors "8.0.1" + pouchdb-fetch "8.0.1" + pouchdb-mapreduce-utils "8.0.1" + pouchdb-md5 "8.0.1" + pouchdb-utils "8.0.1" + +pouchdb-adapter-http@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-adapter-http/-/pouchdb-adapter-http-8.0.1.tgz#d44b8e2299602fcfb1bf456eae43256b39b35316" + integrity sha512-krs5T8QVsswIyDRQHZCBBMF0l7HKQkxZwEigUTVvhq6kuHuqUheLGv18sPiVvmXlpdZwRl/d1432MkG72ywqIw== + dependencies: + pouchdb-binary-utils "8.0.1" + pouchdb-errors "8.0.1" + pouchdb-fetch "8.0.1" + pouchdb-utils "8.0.1" + +pouchdb-adapter-react-native-sqlite@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-adapter-react-native-sqlite/-/pouchdb-adapter-react-native-sqlite-3.0.1.tgz#f20e8e23389ecc57607668104aca018b2d9b7c05" + integrity sha512-oODToI/j0Q/C3VbJMR/sStdjrXwcANFzpwaWdMWrQK95V0NrbtTHlOKycHW+6or1asu2giSjwbvJYMpL/nmi3g== + dependencies: + "@craftzdog/pouchdb-adapter-websql-core" "^7.2.3" + +pouchdb-adapter-utils@^7.2.2: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-adapter-utils/-/pouchdb-adapter-utils-7.3.1.tgz#7237cb597f8d337057df15d4859bfe3c881d8832" + integrity sha512-uKLG6dClwTs/sLIJ4WkLAi9wlnDBpOnfyhpeAgOjlOGN/XLz5nKHrA4UJRnURDyc+uv79S9r/Unc4hVpmbSPUw== + dependencies: + pouchdb-binary-utils "7.3.1" + pouchdb-collections "7.3.1" + pouchdb-errors "7.3.1" + pouchdb-md5 "7.3.1" + pouchdb-merge "7.3.1" + pouchdb-utils "7.3.1" + +pouchdb-binary-utils@7.3.1, pouchdb-binary-utils@^7.2.2: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-binary-utils/-/pouchdb-binary-utils-7.3.1.tgz#eea22d9a5f880fcd95062476f4f5484cdf61496f" + integrity sha512-crZJNfAEOnUoRk977Qtmk4cxEv6sNKllQ6vDDKgQrQLFjMUXma35EHzNyIJr1s76J77Q4sqKQAmxz9Y40yHGtw== + dependencies: + buffer-from "1.1.2" + +pouchdb-binary-utils@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-binary-utils/-/pouchdb-binary-utils-8.0.1.tgz#aa77248b9c0ad2a68f75f8f7cf2b594fe3dbd559" + integrity sha512-WsuR/S0aoUlcA0Alt99czkXsfuXWcrYXAcvGiTW02zawVXOafCnb/qHjA09TUaV0oy5HeHmYaNnDckoOUqspeA== + dependencies: + buffer-from "1.1.2" + +pouchdb-changes-filter@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-changes-filter/-/pouchdb-changes-filter-8.0.1.tgz#691b71b209dda12e950672ce44de2680606a98e4" + integrity sha512-UKgH6YRA9PnvIGHb0FuDEEqeTewgHugbbBt5vpVo0QmbWKxNiau/JiTC9mY5Hj9l7ghaIUpO0TFG95a6RXWsQA== + dependencies: + pouchdb-errors "8.0.1" + pouchdb-selector-core "8.0.1" + pouchdb-utils "8.0.1" + +pouchdb-checkpointer@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-checkpointer/-/pouchdb-checkpointer-8.0.1.tgz#beb4f922b8dbd59f851fe95c7fe540d69f870bf8" + integrity sha512-zWIFEpQqY/7Fx75VkWtxbMYlL5gzPN9SUYqFCCUPPIThEM3evREI/4u4V0ayNkd9Ma3OGyItsj0D1ibBghWenA== + dependencies: + pouchdb-collate "8.0.1" + pouchdb-utils "8.0.1" + +pouchdb-collate@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-collate/-/pouchdb-collate-8.0.1.tgz#14b6ad9330d2831fd8ef135de680060366502a1b" + integrity sha512-DTuNz1UJjBTGZMUlWS1klSE1rPsmHy8IIDie3MFH1ZTz/C+SwGgGwkiAyUDv/n00D18EMLgXq5mu+r7L6K1BwQ== + +pouchdb-collections@7.3.1, pouchdb-collections@^7.2.2: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-collections/-/pouchdb-collections-7.3.1.tgz#4f1819cf4dd6936a422c29f7fa26a9b5dca428f5" + integrity sha512-yUyDqR+OJmtwgExOSJegpBJXDLAEC84TWnbAYycyh+DZoA51Yw0+XVQF5Vh8Ii90/Ut2xo88fmrmp0t6kqom8w== + +pouchdb-collections@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-collections/-/pouchdb-collections-8.0.1.tgz#d6a31ab3a917fa5da49d46a804c04761a0e19655" + integrity sha512-TlkQ2GGHJApJgL0b7bJMQcwX6eMfVenLeoK9mqHfC2fJssui+HWJJ5LYKHOWan11SeB90BQVFbO6rHN6CJQeDg== + +pouchdb-core@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-core/-/pouchdb-core-8.0.1.tgz#3066f4f24dd90e669da0ea3f67205cbc1e3bfa71" + integrity sha512-Qkcmh3eoMHiKUma5Y/rH0Z7kjxXrr6p54j/WOH+TZ/RlJAchmdVY1TRfqay5CoK+8Ka0m8eibP+wD1DKZKJbDg== + dependencies: + pouchdb-changes-filter "8.0.1" + pouchdb-collections "8.0.1" + pouchdb-errors "8.0.1" + pouchdb-fetch "8.0.1" + pouchdb-merge "8.0.1" + pouchdb-utils "8.0.1" + uuid "8.3.2" + +pouchdb-errors@7.3.1, pouchdb-errors@^7.2.2: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-errors/-/pouchdb-errors-7.3.1.tgz#78be36721e2edc446fac158a236a9218c7bcdb14" + integrity sha512-Zktz4gnXEUcZcty8FmyvtYUYsHskoST05m6H5/E2gg/0mCfEXq/XeyyLkZHaZmqD0ZPS9yNmASB1VaFWEKEaDw== + dependencies: + inherits "2.0.4" + +pouchdb-errors@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-errors/-/pouchdb-errors-8.0.1.tgz#d57afd57e07490d8d0b4ef19c10bfc85dbf27ae5" + integrity sha512-H+ZsQxcG/JV3Tn29gnM6c9+lRPCN91ZYOkoIICsLjVRYgOTzN1AvNUD/G5JCB+81aI/u3fxZec0LEaZh6g6NHA== + +pouchdb-fetch@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-fetch/-/pouchdb-fetch-8.0.1.tgz#30a27a45eb24c20f346a04d906ba82bca5a3a525" + integrity sha512-Px5HLT8MxqTujc8bpPRKoouznDTJa9XBGqCbhl95q6rhjWRfwZEvXjV92z0B5BALAM6D6avMyG0DjuNfUWnMuA== + dependencies: + abort-controller "3.0.0" + fetch-cookie "0.11.0" + node-fetch "2.6.7" + +pouchdb-generate-replication-id@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-generate-replication-id/-/pouchdb-generate-replication-id-8.0.1.tgz#2c473bdb7f5e5bc08337b3042841f186bf3d0b2b" + integrity sha512-MxctzPSF9c3w0tPUPCR4Ihs3+N4751kFidgFnYVLRLZbWy3BNuY8GXjXQojLke7d2lMqQyuu4kKyCdOZ/kT8gQ== + dependencies: + pouchdb-collate "8.0.1" + pouchdb-md5 "8.0.1" + +pouchdb-json@^7.2.2: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-json/-/pouchdb-json-7.3.1.tgz#a80a3060aa2914959e4dca7a4e2022ab20c7119a" + integrity sha512-AyOKsmc85/GtHjMZyEacqzja8qLVfycS1hh1oskR+Bm5PIITX52Fb8zyi0hEetV6VC0yuGbn0RqiLjJxQePeqQ== + dependencies: + vuvuzela "1.0.3" + +pouchdb-mapreduce-utils@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-mapreduce-utils/-/pouchdb-mapreduce-utils-8.0.1.tgz#a8acb1542aaf63a007b60f7a281dbbb94b49fcab" + integrity sha512-asZcFLy1DA3oe5CeXIRCpfVrBHaHRvSb3Tc/LPD1dZDDtpEkeCuXGtJm+praN0jl41jTBEm0uMdD/YI0J5ZFXw== + dependencies: + pouchdb-collections "8.0.1" + pouchdb-utils "8.0.1" + +pouchdb-mapreduce@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-mapreduce/-/pouchdb-mapreduce-8.0.1.tgz#f796d7436228572f94179d43ae75a19b5bf89f7a" + integrity sha512-cS36ANhxD8J8cWhqV9OYE5HFU0+jIli2Ow+FV6xKAcFnSogcLPAawGJMsg0mCyhaBqlCNOp1eMWMWz8jk/TStg== + dependencies: + pouchdb-abstract-mapreduce "8.0.1" + pouchdb-mapreduce-utils "8.0.1" + pouchdb-utils "8.0.1" + +pouchdb-md5@7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-md5/-/pouchdb-md5-7.3.1.tgz#70fae44f9d27eb4c6a8e7106156b4593d31c1762" + integrity sha512-aDV8ui/mprnL3xmt0gT/81DFtTtJiKyn+OxIAbwKPMfz/rDFdPYvF0BmDC9QxMMzGfkV+JJUjU6at0PPs2mRLg== + dependencies: + pouchdb-binary-utils "7.3.1" + spark-md5 "3.0.2" + +pouchdb-md5@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-md5/-/pouchdb-md5-8.0.1.tgz#5a3df4939102423727e0594c2a9bb91b82a444ed" + integrity sha512-shVcs/K/iilrcAhDEERpLIrGm/cnDVsXiocOzs7kycJEuBqYnLD9nj58VwWDcum26wfa8T9cznvEGE1jlYVNPQ== + dependencies: + pouchdb-binary-utils "8.0.1" + spark-md5 "3.0.2" + +pouchdb-merge@7.3.1, pouchdb-merge@^7.2.2: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-merge/-/pouchdb-merge-7.3.1.tgz#97aae682d7d8499b62b6ce234dcb9527c7bf6f02" + integrity sha512-FeK3r35mKimokf2PQ2tUI523QWyZ4lYZ0Yd75FfSch/SPY6wIokz5XBZZ6PHdu5aOJsEKzoLUxr8CpSg9DhcAw== + +pouchdb-merge@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-merge/-/pouchdb-merge-8.0.1.tgz#7f948298274908546d89050c26ec52ac2f3cd252" + integrity sha512-79dw6+K7js2+/kt9u4hKOkGCnz+ov0+yft2k21n6M+ylFEQyMKuWHEZRoFWr72o1vxwjhIXhUM1PB2PIdxIh0Q== + dependencies: + pouchdb-utils "8.0.1" + +pouchdb-replication@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-replication/-/pouchdb-replication-8.0.1.tgz#31593356ce95564cc340f3e35ac3b02bdf2769d2" + integrity sha512-BIWhJsw2si3+HX3U5ElHJ6mv/cp5mA3pHynJYhufmbUe2vdLf4cpc7G5FG/ElMFEs0aoweGXPhFTqfqjykSZQA== + dependencies: + pouchdb-checkpointer "8.0.1" + pouchdb-errors "8.0.1" + pouchdb-generate-replication-id "8.0.1" + pouchdb-utils "8.0.1" + +pouchdb-selector-core@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-selector-core/-/pouchdb-selector-core-8.0.1.tgz#d52119b79bd34b69c27f4596dbb9e16373bbd92e" + integrity sha512-dHWsnR+mLGyfVld1vSHJI1xKTwS1xk1G2dggjfXfUrLehI+wysjTUOwiSNytyPzG6DpT+o86wyUpwzPwsDCLBw== + dependencies: + pouchdb-collate "8.0.1" + pouchdb-utils "8.0.1" + +pouchdb-utils@7.3.1, pouchdb-utils@^7.2.2: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-utils/-/pouchdb-utils-7.3.1.tgz#d25f0a034427f388ba5ae37d9ae3fbed210e8720" + integrity sha512-R3hHBo1zTdTu/NFs3iqkcaQAPwhIH0gMIdfVKd5lbDYlmP26rCG5pdS+v7NuoSSFLJ4xxnaGV+Gjf4duYsJ8wQ== + dependencies: + argsarray "0.0.1" + clone-buffer "1.0.0" + immediate "3.3.0" + inherits "2.0.4" + pouchdb-collections "7.3.1" + pouchdb-errors "7.3.1" + pouchdb-md5 "7.3.1" + uuid "8.3.2" + +pouchdb-utils@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/pouchdb-utils/-/pouchdb-utils-8.0.1.tgz#3fb0c9fe00f52e4d8404278a609f277c1c155b70" + integrity sha512-pWgxdk9EHVWJmjQoEvTe+ZlPXyjcuQ/vgLITN+RjGwcYhoQYUE1M0PksQd2dUP3V8lGS4+wrg9lEM/qSJPYcpw== + dependencies: + clone-buffer "1.0.0" + immediate "3.3.0" + pouchdb-collections "8.0.1" + pouchdb-errors "8.0.1" + pouchdb-md5 "8.0.1" + uuid "8.3.2" + +prebuild-install@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" + integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -16494,6 +17066,14 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + promise.allsettled@^1.0.0: version "1.0.7" resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.7.tgz#b9dd51e9cffe496243f5271515652c468865f2d8" @@ -16772,6 +17352,16 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-app-polyfill@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz#a0bea50f078b8a082970a9d853dc34b6dcc6a3cf" @@ -16963,6 +17553,13 @@ react-native-gesture-handler@1.10.3: invariant "^2.2.4" prop-types "^15.7.2" +react-native-get-random-values@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz#1ca70d1271f4b08af92958803b89dccbda78728d" + integrity sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ== + dependencies: + fast-base64-decode "^1.0.0" + "react-native-google-play-integrity@github:cozy/react-native-google-play-integrity#1.0.1": version "1.0.1" resolved "https://codeload.github.com/cozy/react-native-google-play-integrity/tar.gz/ad182ed9e5c83a38e7cd5ee55f1fc06b5ca93e11" @@ -17045,6 +17642,25 @@ react-native-print@0.11.0: resolved "https://registry.yarnpkg.com/react-native-print/-/react-native-print-0.11.0.tgz#3dc69fb05552f0c5da5be0dd345a93e1604621d1" integrity sha512-hFDzb9dVJVT8c3VHNNWgkPfLF3Jfyw0WVJ3o7wUmSMmi5hJx6b++veNJ3J95d3lW3Y0sZUpGbn9+yzXrLANnJQ== +react-native-quick-base64@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/react-native-quick-base64/-/react-native-quick-base64-2.1.2.tgz#062b09b165c1530095fe99b94544c948318dbe99" + integrity sha512-xghaXpWdB0ji8OwYyo0fWezRroNxiNFCNFpGUIyE7+qc4gA/IGWnysIG5L0MbdoORv8FkTKUvfd6yCUN5R2VFA== + dependencies: + base64-js "^1.5.1" + +react-native-quick-sqlite@8.0.6: + version "8.0.6" + resolved "https://registry.yarnpkg.com/react-native-quick-sqlite/-/react-native-quick-sqlite-8.0.6.tgz#ac1b662efe5641bc51eb7a8ee8ffac8af29532c4" + integrity sha512-XtwXnfZ1a6zRzAHoWFyVJsP3p8etx3/xww1oFKGdiOSj054PdYIeup9A53rtQ6ENyg+aqSKOgmxoN/PetulGOQ== + +react-native-quick-websql@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/react-native-quick-websql/-/react-native-quick-websql-0.3.0.tgz#acb53da8263b36fc5644e5faa06e2e069233a104" + integrity sha512-ysP+9HzEfiq0ex9plgmKddLz42RGPCiGsTNqnah3K8FCyKzYWRyVd8SboIdOiSFpB3vulwc/qMOBLVdmgjsaIw== + dependencies: + websql "^2.0.3" + react-native-restart@^0.0.27: version "0.0.27" resolved "https://registry.yarnpkg.com/react-native-restart/-/react-native-restart-0.0.27.tgz#43aa8210312c9dfa5ec7bd4b2f35238ad7972b19" @@ -17876,7 +18492,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -18236,6 +18852,20 @@ signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-plist@^1.1.0: version "1.3.1" resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017" @@ -18285,6 +18915,11 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + snake-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" @@ -18344,6 +18979,23 @@ sockjs@^0.3.21: uuid "^3.4.0" websocket-driver "^0.7.4" +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" @@ -18426,6 +19078,11 @@ sourcemap-codec@^1.4.4: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +spark-md5@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc" + integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw== + spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -18487,11 +19144,28 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +sqlite3@^5.0.2: + version "5.1.7" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" + integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog== + dependencies: + bindings "^1.5.0" + node-addon-api "^7.0.0" + prebuild-install "^7.1.1" + tar "^6.1.11" + optionalDependencies: + node-gyp "8.x" + ssri@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" @@ -18499,7 +19173,7 @@ ssri@^6.0.1: dependencies: figgy-pudding "^3.5.1" -ssri@^8.0.1: +ssri@^8.0.0, ssri@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== @@ -18866,6 +19540,11 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + strnum@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" @@ -19029,6 +19708,27 @@ tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^6.0.2: version "6.1.0" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" @@ -19041,6 +19741,18 @@ tar@^6.0.2: mkdirp "^1.0.3" yallist "^4.0.0" +tar@^6.1.11, tar@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + telejson@^6.0.8: version "6.0.8" resolved "https://registry.yarnpkg.com/telejson/-/telejson-6.0.8.tgz#1c432db7e7a9212c1fbd941c3e5174ec385148f7" @@ -19208,6 +19920,11 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-queue@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046" + integrity sha512-EijGsv7kzd9I9g0ByCl6h42BWNGUZrlCSejfrb3AKeHC33SGbASu1VDf5O3rRiiUOhAC9CHdZxFPbZu0HmR70A== + tinycolor2@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" @@ -19285,6 +20002,16 @@ token-types@^4.1.1: "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" +"tough-cookie@^2.3.3 || ^3.0.1 || ^4.0.0": + version "4.1.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -19382,6 +20109,13 @@ tty-browserify@0.0.0: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -19599,6 +20333,11 @@ universalify@^0.1.0, universalify@^0.1.2: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -19680,6 +20419,14 @@ url-parse@^1.4.3, url-parse@^1.5.1: querystringify "^2.1.1" requires-port "^1.0.0" +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + url-search-params-polyfill@^8.0.0: version "8.1.1" resolved "https://registry.yarnpkg.com/url-search-params-polyfill/-/url-search-params-polyfill-8.1.1.tgz#9e69e4dba300a71ae7ad3cead62c7717fd99329f" @@ -19783,6 +20530,11 @@ uuid-browser@^3.1.0: resolved "https://registry.yarnpkg.com/uuid-browser/-/uuid-browser-3.1.0.tgz#0f05a40aef74f9e5951e20efbf44b11871e56410" integrity sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg== +uuid@8.3.2, uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -19793,11 +20545,6 @@ uuid@^7.0.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== -uuid@^8.3.0: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -19854,6 +20601,11 @@ void-elements@3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== +vuvuzela@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/vuvuzela/-/vuvuzela-1.0.3.tgz#3be145e58271c73ca55279dd851f12a682114b0b" + integrity sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ== + w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" @@ -20198,6 +20950,18 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +websql@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/websql/-/websql-2.0.3.tgz#5a747dc01fd1bc27633f64a2971d410500f70568" + integrity sha512-bSYpuhQ4ODKrWLb6S+9BG2T4AMqHLjCQA9r8UWCapPvTZYXoembz0O14Ga4EAfJuO1wkmFcJjgU/6tzvPfGbmA== + dependencies: + argsarray "^0.0.1" + immediate "^3.2.2" + noop-fn "^1.0.0" + tiny-queue "^0.2.1" + optionalDependencies: + sqlite3 "^5.0.2" + whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -20294,7 +21058,7 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.2: +wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== From 26b76136f9427787b630bdda000b3cb7981ab69d Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 28 May 2024 20:16:14 +0200 Subject: [PATCH 02/92] feat: Configure PouchDB to run on the project Related article: https://dev.to/craftzdog/a-performant-way-to-use-pouchdb7-on-react-native-in-2022-24ej --- __tests__/jestSetupFile.js | 1 + src/index.js | 2 ++ src/pouchdb/pouchdb.js | 16 ++++++++++++++++ src/pouchdb/shim.js | 6 ++++++ 4 files changed, 25 insertions(+) create mode 100644 src/pouchdb/pouchdb.js create mode 100644 src/pouchdb/shim.js diff --git a/__tests__/jestSetupFile.js b/__tests__/jestSetupFile.js index a546def4a..1b7f0e0e9 100644 --- a/__tests__/jestSetupFile.js +++ b/__tests__/jestSetupFile.js @@ -128,4 +128,5 @@ jest.mock('../src/core/tools/env', () => ({ shouldDisableAutolock: jest.fn().mockReturnValue(false) })) +jest.mock('../src/pouchdb/pouchdb', () => ({})) jest.mock('react-native-quick-websql', () => ({})) diff --git a/src/index.js b/src/index.js index bdea29771..93ea42c09 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,5 @@ +import '/pouchdb/shim' + import { AppRegistry } from 'react-native' import App from './App' diff --git a/src/pouchdb/pouchdb.js b/src/pouchdb/pouchdb.js new file mode 100644 index 000000000..c7ae74dae --- /dev/null +++ b/src/pouchdb/pouchdb.js @@ -0,0 +1,16 @@ +// eslint-disable-next-line import/order +import 'react-native-get-random-values' + +import HttpPouch from 'pouchdb-adapter-http' +import SQLiteAdapterFactory from 'pouchdb-adapter-react-native-sqlite' +import PouchDB from 'pouchdb-core' +import mapreduce from 'pouchdb-mapreduce' +import replication from 'pouchdb-replication' +import WebSQLite from 'react-native-quick-websql' + +const SQLiteAdapter = SQLiteAdapterFactory(WebSQLite) + +export default PouchDB.plugin(HttpPouch) + .plugin(replication) + .plugin(mapreduce) + .plugin(SQLiteAdapter) diff --git a/src/pouchdb/shim.js b/src/pouchdb/shim.js new file mode 100644 index 000000000..bbd24a463 --- /dev/null +++ b/src/pouchdb/shim.js @@ -0,0 +1,6 @@ +import { shim } from 'react-native-quick-base64' + +shim() + +// Avoid using node dependent modules +process.browser = true From 2adeb50b2cd3c8784ca0e758a44c6104f0c46a9b Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 28 May 2024 20:19:52 +0200 Subject: [PATCH 03/92] feat: Add cozy-pouch-link module --- __tests__/jestSetupFile.js | 10 ++++ package.json | 3 +- yarn.lock | 98 ++++++++++++++++++++++++++++++++++---- 3 files changed, 101 insertions(+), 10 deletions(-) diff --git a/__tests__/jestSetupFile.js b/__tests__/jestSetupFile.js index 1b7f0e0e9..026e3a601 100644 --- a/__tests__/jestSetupFile.js +++ b/__tests__/jestSetupFile.js @@ -130,3 +130,13 @@ jest.mock('../src/core/tools/env', () => ({ 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() + }) +}) diff --git a/package.json b/package.json index cd6adc753..262058377 100644 --- a/package.json +++ b/package.json @@ -56,13 +56,14 @@ "@sentry/integrations": "7.114.0", "@sentry/react-native": "5.33.1", "base-64": "^1.0.0", - "cozy-client": "^48.5.0", + "cozy-client": "^49.0.0", "cozy-clisk": "^0.38.1", "cozy-device-helper": "^2.7.0", "cozy-flags": "^3.2.0", "cozy-intent": "^2.22.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", diff --git a/yarn.lock b/yarn.lock index 5d7feefce..a115d3b56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8675,16 +8675,16 @@ cosmiconfig@^8.1.3: parse-json "^5.2.0" path-type "^4.0.0" -cozy-client@^48.5.0: - version "48.5.0" - resolved "https://registry.yarnpkg.com/cozy-client/-/cozy-client-48.5.0.tgz#c030ac3578466550cb30b545818aac9d5331eaa8" - integrity sha512-Bt+VH6mvif3qqOhAm5i9Ua7a5c7x6FSeLSWzrHm0fp8ALCsHw3LpKHVa+qGQNSNrkwG5ctBHO5wSR6ShmSxbZA== +cozy-client@^49.0.0: + version "49.0.0" + resolved "https://registry.yarnpkg.com/cozy-client/-/cozy-client-49.0.0.tgz#c38f3bdefd58cbf827a368fe126cc9e27deb996d" + integrity sha512-AY22bxwQV+46+4rSVc+zYci8pPHspolBJ1IcqvxJ+DknZrehJ5AKJ55F9DzN3aPXVNCQzmw+wpNFgVP4gShFAw== dependencies: "@cozy/minilog" "1.0.0" "@types/jest" "^26.0.20" "@types/lodash" "^4.14.170" btoa "^1.2.1" - cozy-stack-client "^48.5.0" + cozy-stack-client "^49.0.0" date-fns "2.29.3" json-stable-stringify "^1.0.1" lodash "^4.17.13" @@ -8751,10 +8751,19 @@ cozy-minilog@3.3.1, cozy-minilog@^3.3.1: dependencies: microee "0.0.6" -cozy-stack-client@^48.5.0: - version "48.5.0" - resolved "https://registry.yarnpkg.com/cozy-stack-client/-/cozy-stack-client-48.5.0.tgz#017f4c1ebcf7fdf9261aaa40ce4ea263421806d1" - integrity sha512-McwK/thwpbQTH2R+fOZzwVLnaScFzo0E91fbIcZOkFXRT6VM8WaUdQDeowlOaaw+w9R8167h0qwri+yeFbT/Ew== +cozy-pouch-link@^49.0.0: + version "49.0.0" + resolved "https://registry.yarnpkg.com/cozy-pouch-link/-/cozy-pouch-link-49.0.0.tgz#05644fbf7bbb7bf1af0869bca2b959e0ef2bd284" + integrity sha512-objXCSNvyGGNhzqb4PXwKNSqv54+E6p1eNhkEBlzrO6miXAeIYX5T0db3OQUuhiQdB69aYiExAVj5sCfUst3kw== + dependencies: + cozy-client "^49.0.0" + pouchdb-browser "^7.2.2" + pouchdb-find "^7.2.2" + +cozy-stack-client@^49.0.0: + version "49.0.0" + resolved "https://registry.yarnpkg.com/cozy-stack-client/-/cozy-stack-client-49.0.0.tgz#1bda328d0d62b00bb8895be5b991d59ad6b41cfc" + integrity sha512-mlh/hR9KsIve+et17P6WXlO33FjXftzXK8ovWAKr8zk+5FcD/wy/yxV/9Mr3Q+SSabdUbFbBIqu8kZncafUzdg== dependencies: detect-node "^2.0.4" mime "^2.4.0" @@ -16728,6 +16737,20 @@ postinstall-postinstall@^2.1.0: resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== +pouchdb-abstract-mapreduce@7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-abstract-mapreduce/-/pouchdb-abstract-mapreduce-7.3.1.tgz#96ff4a0f41cbe273f3f52fde003b719005a2093c" + integrity sha512-0zKXVFBvrfc1KnN0ggrB762JDmZnUpePHywo9Bq3Jy+L1FnoG7fXM5luFfvv5/T0gEw+ZTIwoocZECMnESBI9w== + dependencies: + pouchdb-binary-utils "7.3.1" + pouchdb-collate "7.3.1" + pouchdb-collections "7.3.1" + pouchdb-errors "7.3.1" + pouchdb-fetch "7.3.1" + pouchdb-mapreduce-utils "7.3.1" + pouchdb-md5 "7.3.1" + pouchdb-utils "7.3.1" + pouchdb-abstract-mapreduce@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/pouchdb-abstract-mapreduce/-/pouchdb-abstract-mapreduce-8.0.1.tgz#f45aa1424fdd272a76ceb5722a53b38c10a0ca81" @@ -16785,6 +16808,18 @@ pouchdb-binary-utils@8.0.1: dependencies: buffer-from "1.1.2" +pouchdb-browser@^7.2.2: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-browser/-/pouchdb-browser-7.3.1.tgz#6b2f9f35f42d2c83fc205de5e0403c0aae7046aa" + integrity sha512-qZ8awkXl/woBHvEVqNHjDtwPDA7A9v4ItHtX1y1eVpKel4mlYqnIJ8K6pRcFUZmVaHinJW8K3uS32eHC1q0yOA== + dependencies: + argsarray "0.0.1" + immediate "3.3.0" + inherits "2.0.4" + spark-md5 "3.0.2" + uuid "8.3.2" + vuvuzela "1.0.3" + pouchdb-changes-filter@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/pouchdb-changes-filter/-/pouchdb-changes-filter-8.0.1.tgz#691b71b209dda12e950672ce44de2680606a98e4" @@ -16802,6 +16837,11 @@ pouchdb-checkpointer@8.0.1: pouchdb-collate "8.0.1" pouchdb-utils "8.0.1" +pouchdb-collate@7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-collate/-/pouchdb-collate-7.3.1.tgz#19d7b87dd173d1c765da8cc9987c5aa9eb24f11f" + integrity sha512-o4gyGqDMLMSNzf6EDTr3eHaH/JRMoqRhdc+eV+oA8u00nTBtr9wD+jypVe2LbgKLJ4NWqx2qVkXiTiQdUFtsLQ== + pouchdb-collate@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/pouchdb-collate/-/pouchdb-collate-8.0.1.tgz#14b6ad9330d2831fd8ef135de680060366502a1b" @@ -16842,6 +16882,15 @@ pouchdb-errors@8.0.1: resolved "https://registry.yarnpkg.com/pouchdb-errors/-/pouchdb-errors-8.0.1.tgz#d57afd57e07490d8d0b4ef19c10bfc85dbf27ae5" integrity sha512-H+ZsQxcG/JV3Tn29gnM6c9+lRPCN91ZYOkoIICsLjVRYgOTzN1AvNUD/G5JCB+81aI/u3fxZec0LEaZh6g6NHA== +pouchdb-fetch@7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-fetch/-/pouchdb-fetch-7.3.1.tgz#d54b1807be0f0a5d4b6d06e416c7d54952bbc348" + integrity sha512-205xAtvdHRPQ4fp1h9+RmT9oQabo9gafuPmWsS9aEl3ER54WbY8Vaj1JHZGbU4KtMTYvW7H5088zLS7Nrusuag== + dependencies: + abort-controller "3.0.0" + fetch-cookie "0.11.0" + node-fetch "2.6.7" + pouchdb-fetch@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/pouchdb-fetch/-/pouchdb-fetch-8.0.1.tgz#30a27a45eb24c20f346a04d906ba82bca5a3a525" @@ -16851,6 +16900,19 @@ pouchdb-fetch@8.0.1: fetch-cookie "0.11.0" node-fetch "2.6.7" +pouchdb-find@^7.2.2: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-find/-/pouchdb-find-7.3.1.tgz#07a633d5ee2bd731dae9f991281cd25212088d29" + integrity sha512-AeqUfAVY1c7IFaY36BRT0vIz9r4VTKq/YOWTmiqndOZUQ/pDGxyO2fNFal6NN3PyYww0JijlD377cPvhnrhJVA== + dependencies: + pouchdb-abstract-mapreduce "7.3.1" + pouchdb-collate "7.3.1" + pouchdb-errors "7.3.1" + pouchdb-fetch "7.3.1" + pouchdb-md5 "7.3.1" + pouchdb-selector-core "7.3.1" + pouchdb-utils "7.3.1" + pouchdb-generate-replication-id@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/pouchdb-generate-replication-id/-/pouchdb-generate-replication-id-8.0.1.tgz#2c473bdb7f5e5bc08337b3042841f186bf3d0b2b" @@ -16866,6 +16928,16 @@ pouchdb-json@^7.2.2: dependencies: vuvuzela "1.0.3" +pouchdb-mapreduce-utils@7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-mapreduce-utils/-/pouchdb-mapreduce-utils-7.3.1.tgz#f0ac2c8400fbedb705e9226082453ac7d3f2a066" + integrity sha512-oUMcq82+4pTGQ6dtrhgORHOVHZSr6w/5tFIUGlv7RABIDvJarL4snMawADjlpiEwPdiQ/ESG8Fqt8cxqvqsIgg== + dependencies: + argsarray "0.0.1" + inherits "2.0.4" + pouchdb-collections "7.3.1" + pouchdb-utils "7.3.1" + pouchdb-mapreduce-utils@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/pouchdb-mapreduce-utils/-/pouchdb-mapreduce-utils-8.0.1.tgz#a8acb1542aaf63a007b60f7a281dbbb94b49fcab" @@ -16921,6 +16993,14 @@ pouchdb-replication@^8.0.1: pouchdb-generate-replication-id "8.0.1" pouchdb-utils "8.0.1" +pouchdb-selector-core@7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/pouchdb-selector-core/-/pouchdb-selector-core-7.3.1.tgz#08245662de3d61f16ab8dae2b56ef622935b3fb3" + integrity sha512-HBX+nNGXcaL9z0uNpwSMRq2GNZd3EZXW+fe9rJHS0hvJohjZL7aRJLoaXfEdHPRTNW+CpjM3Rny60eGekQdI/w== + dependencies: + pouchdb-collate "7.3.1" + pouchdb-utils "7.3.1" + pouchdb-selector-core@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/pouchdb-selector-core/-/pouchdb-selector-core-8.0.1.tgz#d52119b79bd34b69c27f4596dbb9e16373bbd92e" From f3fcb042acc569ee193a27c2699bec945babf3b4 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 16:44:49 +0200 Subject: [PATCH 04/92] feat: Configure CozyClient to use CozyPouchLink We want the Flagship app to work when offline To make this possible we configure cozy-client with CozyPouchLink which role will be to synchronize necessary doctypes into a local PouchDB and serve them from it instead of from the cozy-stack when the device is offline For now the list of synchronized doctypes is hardcoded but in the future we expect to implement a dynamic list based on cozy-apps' manifests Related PR: cozy/cozy-client#1507 --- src/libs/client.js | 5 ++- src/libs/client.spec.js | 3 +- src/libs/clientHelpers/createClient.ts | 6 +++- src/pouchdb/getLinks.ts | 39 +++++++++++++++++++++ src/pouchdb/platformReactNative.appState.ts | 34 ++++++++++++++++++ src/pouchdb/platformReactNative.events.ts | 28 +++++++++++++++ src/pouchdb/platformReactNative.isOnline.ts | 5 +++ src/pouchdb/platformReactNative.netInfo.ts | 24 +++++++++++++ src/pouchdb/platformReactNative.storage.ts | 14 ++++++++ src/pouchdb/platformReactNative.ts | 12 +++++++ 10 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 src/pouchdb/getLinks.ts create mode 100644 src/pouchdb/platformReactNative.appState.ts create mode 100644 src/pouchdb/platformReactNative.events.ts create mode 100644 src/pouchdb/platformReactNative.isOnline.ts create mode 100644 src/pouchdb/platformReactNative.netInfo.ts create mode 100644 src/pouchdb/platformReactNative.storage.ts create mode 100644 src/pouchdb/platformReactNative.ts diff --git a/src/libs/client.js b/src/libs/client.js index 49206c162..f304bba49 100644 --- a/src/libs/client.js +++ b/src/libs/client.js @@ -30,6 +30,7 @@ export { } from '/libs/clientHelpers/initClient' export { call2FAInitClient } from '/libs/clientHelpers/twoFactorAuthentication' import { CozyPersistedStorageKeys, getData } from '/libs/localStore/storage' +import { getLinks } from '/pouchdb/getLinks' const log = Minilog('LoginScreen') @@ -44,6 +45,7 @@ export const getClient = async () => { return false } const { uri, oauthOptions, token } = oauthData + const links = getLinks() const client = new CozyClient({ uri, oauth: { token }, @@ -51,7 +53,8 @@ export const getClient = async () => { appMetadata: { slug: 'flagship', version: packageJSON.version - } + }, + links }) listenTokenRefresh(client) client.getStackClient().setOAuthOptions(oauthOptions) diff --git a/src/libs/client.spec.js b/src/libs/client.spec.js index 9de17df7a..6de3a03f8 100644 --- a/src/libs/client.spec.js +++ b/src/libs/client.spec.js @@ -62,7 +62,8 @@ describe('client', () => { appMetadata: { slug: 'flagship', version: packageJSON.version - } + }, + links: expect.anything() }) }) diff --git a/src/libs/clientHelpers/createClient.ts b/src/libs/clientHelpers/createClient.ts index 0a3a30bb6..8e16bc5bd 100644 --- a/src/libs/clientHelpers/createClient.ts +++ b/src/libs/clientHelpers/createClient.ts @@ -13,6 +13,7 @@ import googleServicesJson from '/../android/app/src/prod/google-services.json' import packageJSON from '../../../package.json' import { startListening } from '/app/domain/authentication/services/AuthService' +import { getLinks } from '/pouchdb/getLinks' /** * Create a CozyClient for the given Cozy instance and register it @@ -21,6 +22,8 @@ import { startListening } from '/app/domain/authentication/services/AuthService' * @returns {CozyClient} - The created and registered CozyClient */ export const createClient = async (instance: string): Promise => { + const links = getLinks() + const options = { scope: ['*'], oauth: { @@ -37,7 +40,8 @@ export const createClient = async (instance: string): Promise => { appMetadata: { slug: 'flagship', version: packageJSON.version - } + }, + links } const client = new CozyClient(options) diff --git a/src/pouchdb/getLinks.ts b/src/pouchdb/getLinks.ts new file mode 100644 index 000000000..5d3a91190 --- /dev/null +++ b/src/pouchdb/getLinks.ts @@ -0,0 +1,39 @@ +import { platformReactNative } from '/pouchdb/platformReactNative' + +import { default as PouchLink } from 'cozy-pouch-link' + +export const offlineDoctypes = [ + // cozy-home + 'io.cozy.accounts', + 'io.cozy.apps', + 'io.cozy.contacts', + 'io.cozy.files', + 'io.cozy.files.shortcuts', + 'io.cozy.home.settings', + 'io.cozy.jobs', + 'io.cozy.konnectors', + 'io.cozy.settings', + 'io.cozy.apps.suggestions', + 'io.cozy.triggers', + 'io.cozy.apps_registry', + + // mespapiers + 'io.cozy.bills', + 'io.cozy.sharings', + 'io.cozy.mespapiers.settings', + 'io.cozy.permissions' +] + +export const getLinks = () => { + const pouchLinkOptions = { + doctypes: offlineDoctypes, + initialSync: true, + platform: platformReactNative + } + + const pouchLink = new PouchLink({ + ...pouchLinkOptions + }) + + return [pouchLink] +} diff --git a/src/pouchdb/platformReactNative.appState.ts b/src/pouchdb/platformReactNative.appState.ts new file mode 100644 index 000000000..41d93b11c --- /dev/null +++ b/src/pouchdb/platformReactNative.appState.ts @@ -0,0 +1,34 @@ +import EventEmitter from 'events' + +import { AppState, AppStateStatus, NativeEventSubscription } from 'react-native' + +import Minilog from 'cozy-minilog' + +const log = Minilog('🛋️ PlatormReactNative.appState') + +let appState = AppState.currentState +let appStateHandler: NativeEventSubscription | undefined = undefined + +export const listenAppState = (eventEmitter: EventEmitter): void => { + appStateHandler = AppState.addEventListener('change', nextAppState => { + log.debug('🛋️ AppState event', nextAppState) + if (isGoingToSleep(nextAppState)) { + eventEmitter.emit('resume') + } + if (isGoingToWakeUp(nextAppState)) { + eventEmitter.emit('pause') + } + + appState = nextAppState + }) +} + +export const stopListeningAppState = (): void => { + appStateHandler?.remove() +} + +const isGoingToSleep = (nextAppState: AppStateStatus): boolean => + Boolean(appState.match(/active/) && nextAppState === 'background') + +const isGoingToWakeUp = (nextAppState: AppStateStatus): boolean => + Boolean(appState.match(/background/) && nextAppState === 'active') diff --git a/src/pouchdb/platformReactNative.events.ts b/src/pouchdb/platformReactNative.events.ts new file mode 100644 index 000000000..8382f7853 --- /dev/null +++ b/src/pouchdb/platformReactNative.events.ts @@ -0,0 +1,28 @@ +import { EventEmitter } from 'events' + +import { listenAppState } from '/pouchdb/platformReactNative.appState' +import { listenNetInfo } from '/pouchdb/platformReactNative.netInfo' + +export const pouchDbEmitter = new EventEmitter() + +const listenPouchEvents = (): void => { + listenAppState(pouchDbEmitter) + listenNetInfo(pouchDbEmitter) +} + +listenPouchEvents() + +export const events = { + addEventListener: ( + eventName: string, + handler: (...args: unknown[]) => void + ): void => { + pouchDbEmitter.addListener(eventName, handler) + }, + removeEventListener: ( + eventName: string, + handler: (...args: unknown[]) => void + ): void => { + pouchDbEmitter.removeListener(eventName, handler) + } +} diff --git a/src/pouchdb/platformReactNative.isOnline.ts b/src/pouchdb/platformReactNative.isOnline.ts new file mode 100644 index 000000000..8081dad3c --- /dev/null +++ b/src/pouchdb/platformReactNative.isOnline.ts @@ -0,0 +1,5 @@ +import { NetService } from '/libs/services/NetService' + +export const isOnline = async (): Promise => { + return (await NetService.isConnected()) ?? true +} diff --git a/src/pouchdb/platformReactNative.netInfo.ts b/src/pouchdb/platformReactNative.netInfo.ts new file mode 100644 index 000000000..2d52a3b8a --- /dev/null +++ b/src/pouchdb/platformReactNative.netInfo.ts @@ -0,0 +1,24 @@ +import EventEmitter from 'events' + +import NetInfo, { NetInfoSubscription } from '@react-native-community/netinfo' + +import Minilog from 'cozy-minilog' + +const log = Minilog('🛋️ PlatormReactNative.netInfo') + +let netInfoHandler: NetInfoSubscription | undefined = undefined + +export const listenNetInfo = (eventEmitter: EventEmitter): void => { + netInfoHandler = NetInfo.addEventListener(state => { + log.debug('🛋️ NetInfo event', state.isConnected) + if (state.isConnected) { + eventEmitter.emit('online') + } else { + eventEmitter.emit('offline') + } + }) +} + +export const stopListeningNetInfo = (): void => { + netInfoHandler?.() +} diff --git a/src/pouchdb/platformReactNative.storage.ts b/src/pouchdb/platformReactNative.storage.ts new file mode 100644 index 000000000..f294bc4ca --- /dev/null +++ b/src/pouchdb/platformReactNative.storage.ts @@ -0,0 +1,14 @@ +import AsyncStorage from '@react-native-async-storage/async-storage' + +export const storage = { + getItem: async (key: string): Promise => { + return AsyncStorage.getItem(key) + }, + setItem: async (key: string, value: string | undefined): Promise => { + if (value === undefined) return + return AsyncStorage.setItem(key, value) + }, + removeItem: async (key: string): Promise => { + return AsyncStorage.removeItem(key) + } +} diff --git a/src/pouchdb/platformReactNative.ts b/src/pouchdb/platformReactNative.ts new file mode 100644 index 000000000..9241e70d8 --- /dev/null +++ b/src/pouchdb/platformReactNative.ts @@ -0,0 +1,12 @@ +import { events } from '/pouchdb/platformReactNative.events' +import { isOnline } from '/pouchdb/platformReactNative.isOnline' +import { storage } from '/pouchdb/platformReactNative.storage' +import PouchDB from '/pouchdb/pouchdb' + +export const platformReactNative = { + storage, + events, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + pouchAdapter: PouchDB, + isOnline +} From 509711ecdbee4dbc7abbfaa4fce0a4064cc36d09 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 16:44:40 +0200 Subject: [PATCH 05/92] feat: Configure CozyClient to also use StackLink (when online) In previous commit we configured cozy-client to use CozyPouchLink for its queries This commit also adds StackLink as the first Link so by default it will do its queries through the remote cozy-stack CozyClient has been modified to handle offline mode and redirect to the next link when it is detected Related PR: cozy/cozy-client#1507 --- src/pouchdb/getLinks.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pouchdb/getLinks.ts b/src/pouchdb/getLinks.ts index 5d3a91190..ac6e565f9 100644 --- a/src/pouchdb/getLinks.ts +++ b/src/pouchdb/getLinks.ts @@ -1,5 +1,6 @@ import { platformReactNative } from '/pouchdb/platformReactNative' +import { CozyLink, StackLink } from 'cozy-client' import { default as PouchLink } from 'cozy-pouch-link' export const offlineDoctypes = [ @@ -24,16 +25,20 @@ export const offlineDoctypes = [ 'io.cozy.permissions' ] -export const getLinks = () => { +export const getLinks = (): CozyLink[] => { const pouchLinkOptions = { doctypes: offlineDoctypes, initialSync: true, platform: platformReactNative } + const stackLink = new StackLink({ + platform: platformReactNative + }) + const pouchLink = new PouchLink({ ...pouchLinkOptions }) - return [pouchLink] + return [stackLink, pouchLink] } From 6224bf10c75c29ab676648f121d409eb3364567f Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 16:44:24 +0200 Subject: [PATCH 06/92] feat: Add PouchDBFind plugin to the PouchDB configuration This plugin is required by cozy-client to process `find` queries Related PR: cozy/cozy-client#1507 --- src/pouchdb/pouchdb.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pouchdb/pouchdb.js b/src/pouchdb/pouchdb.js index c7ae74dae..c14b272e2 100644 --- a/src/pouchdb/pouchdb.js +++ b/src/pouchdb/pouchdb.js @@ -4,6 +4,7 @@ import 'react-native-get-random-values' import HttpPouch from 'pouchdb-adapter-http' import SQLiteAdapterFactory from 'pouchdb-adapter-react-native-sqlite' import PouchDB from 'pouchdb-core' +import PouchDBFind from 'pouchdb-find' import mapreduce from 'pouchdb-mapreduce' import replication from 'pouchdb-replication' import WebSQLite from 'react-native-quick-websql' @@ -11,6 +12,7 @@ import WebSQLite from 'react-native-quick-websql' const SQLiteAdapter = SQLiteAdapterFactory(WebSQLite) export default PouchDB.plugin(HttpPouch) + .plugin(PouchDBFind) .plugin(replication) .plugin(mapreduce) .plugin(SQLiteAdapter) From c50ada909a31ba3479641faf17c0bd99c912ec77 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 11 Jul 2024 18:23:34 +0200 Subject: [PATCH 07/92] feat: Add clientCachedStorage to handle CozyClient's requests cache PouchDB can handle only `client.query()` requests For all other requests, like `client.fetchJSON()` we want to be able to store the request result in the device's AsyncStorage so we can re-use this result when the device is offline Data is stored using the client's url and the request content as key (it can be a string or a full request object) --- src/libs/intents/localMethods.ts | 2 + .../localStore/clientCachedStorage.spec.ts | 202 ++++++++++++++++++ src/libs/localStore/clientCachedStorage.ts | 95 ++++++++ 3 files changed, 299 insertions(+) create mode 100644 src/libs/localStore/clientCachedStorage.spec.ts create mode 100644 src/libs/localStore/clientCachedStorage.ts diff --git a/src/libs/intents/localMethods.ts b/src/libs/intents/localMethods.ts index 48df7e8e5..362d1366b 100644 --- a/src/libs/intents/localMethods.ts +++ b/src/libs/intents/localMethods.ts @@ -22,6 +22,7 @@ import { setHomeThemeIntent } from '/libs/intents/setHomeThemeIntent' import strings from '/constants/strings.json' import { EnvService } from '/core/tools/env' import { clearCookies } from '/libs/httpserver/httpCookieManager' +import { clearClientCachedData } from '/libs/localStore/clientCachedStorage' import { clearCozyData } from '/libs/localStore/storage' import { getSharedMemoryIntent, @@ -72,6 +73,7 @@ export const asyncLogout = async (client?: CozyClient): Promise => { } await sendKonnectorsLogs(client) + await clearClientCachedData(client) await client.logout() await stopTrackingAndClearData() await deleteKeychain() diff --git a/src/libs/localStore/clientCachedStorage.spec.ts b/src/libs/localStore/clientCachedStorage.spec.ts new file mode 100644 index 000000000..ab82d502e --- /dev/null +++ b/src/libs/localStore/clientCachedStorage.spec.ts @@ -0,0 +1,202 @@ +import AsyncStorage from '@react-native-async-storage/async-storage' + +import CozyClient from 'cozy-client' + +import { + clearClientCachedData, + getClientCachedData, + storeClientCachedData +} from '/libs/localStore/clientCachedStorage' +import { AppData } from '/libs/httpserver/models' +import { + clearAllData, + CozyPersistedStorageKeys, + storeData +} from '/libs/localStore/storage' + +const { getAllKeys } = AsyncStorage + +describe('clientCachedStorage', () => { + beforeEach(async () => { + await clearAllData() + }) + + describe('key tests', () => { + it('should use raw request for key when string', async () => { + const request = 'SomeRequestName' + + await storeClientCachedData(aliceClient, request, getFakeAppData()) + const keys = await getAllKeys() + + expect(keys).toStrictEqual([ + '@ccCache_alice.mycozy.cloud_SomeRequestName' + ]) + }) + + it('should convert request to base64 for key when request CozyClientRequest object', async () => { + const request = { + method: 'GET', + path: '/some/path', + body: {}, + options: {} + } + + await storeClientCachedData(aliceClient, request, getFakeAppData()) + const keys = await getAllKeys() + + expect(keys).toStrictEqual([ + '@ccCache_alice.mycozy.cloud_eyJtZXRob2QiOiJHRVQiLCJwYXRoIjoiL3NvbWUvcGF0aCIsImJvZHkiOnt9LCJvcHRpb25zIjp7fX0=' + ]) + }) + }) + + describe('isolation tests', () => { + it('should store as alice and retrieve as alice', async () => { + const request = 'SomeRequestName' + + await storeClientCachedData(aliceClient, request, getFakeAppData()) + const result = await getClientCachedData(aliceClient, request) + expect(result).toStrictEqual(getFakeAppData()) + }) + + it('should store as alice and not retrieve as alice with port', async () => { + const request = 'SomeRequestName' + await storeClientCachedData(aliceClient, request, getFakeAppData()) + const result = await getClientCachedData(aliceClientWithPort, request) + expect(result).toStrictEqual(null) + }) + + it('should store as alice and bob and retrieve as alice and bob', async () => { + const aliceRequest = 'SomeRequestName' + const bobRequest = 'SomeRequestName' + await storeClientCachedData( + aliceClient, + aliceRequest, + getFakeAppData('TestAlice') + ) + await storeClientCachedData( + bobClient, + bobRequest, + getFakeAppData('TestBob') + ) + const aliceResult = await getClientCachedData(aliceClient, aliceRequest) + const bobResult = await getClientCachedData(bobClient, bobRequest) + expect(aliceResult).toStrictEqual(getFakeAppData('TestAlice')) + expect(bobResult).toStrictEqual(getFakeAppData('TestBob')) + }) + + it('should store as alice and not retrieve as bob', async () => { + const request = 'SomeRequestName' + await storeClientCachedData( + aliceClient, + request, + getFakeAppData('TestAlice') + ) + const result = await getClientCachedData(bobClient, request) + expect(result).toStrictEqual(null) + }) + }) + + describe('clearClientCachedData', () => { + it('should clear only data relative to clientCachedStorage', async () => { + await storeData( + // @ts-expect-error Type validation is handled on clientCachedStorage + '@ccCache_alice.mycozy.cloud_SomeRequestName', + 'SomeValue' + ) + await storeData( + // @ts-expect-error Type validation is handled on clientCachedStorage + '@ccCache_alice.mycozy.cloud_eyJtZXRob2QiOiJHRVQiLCJwYXRoIjoiL3NvbWUvcGF0aCIsImJvZHkiOnt9LCJvcHRpb25zIjp7fX0=', + 'SomeValue' + ) + await storeData(CozyPersistedStorageKeys.Activities, 'SomeValue') + await storeData(CozyPersistedStorageKeys.AutoLockEnabled, 'SomeValue') + + const keys = await getAllKeys() + expect(keys).toStrictEqual([ + '@ccCache_alice.mycozy.cloud_SomeRequestName', + '@ccCache_alice.mycozy.cloud_eyJtZXRob2QiOiJHRVQiLCJwYXRoIjoiL3NvbWUvcGF0aCIsImJvZHkiOnt9LCJvcHRpb25zIjp7fX0=', + CozyPersistedStorageKeys.Activities, + CozyPersistedStorageKeys.AutoLockEnabled + ]) + + await clearClientCachedData(aliceClient) + + const keys2 = await getAllKeys() + expect(keys2).toStrictEqual([ + CozyPersistedStorageKeys.Activities, + CozyPersistedStorageKeys.AutoLockEnabled + ]) + }) + + it('should clear only data relative to current client', async () => { + await storeData( + // @ts-expect-error Type validation is handled on clientCachedStorage + '@ccCache_alice.mycozy.cloud_SomeRequestName', + 'SomeValue' + ) + await storeData( + // @ts-expect-error Type validation is handled on clientCachedStorage + '@ccCache_alice.mycozy.cloud_eyJtZXRob2QiOiJHRVQiLCJwYXRoIjoiL3NvbWUvcGF0aCIsImJvZHkiOnt9LCJvcHRpb25zIjp7fX0=', + 'SomeValue' + ) + await storeData( + // @ts-expect-error Type validation is handled on clientCachedStorage + '@ccCache_bob.mycozy.cloud_SomeRequestName', + 'SomeValue' + ) + + const keys = await getAllKeys() + expect(keys).toStrictEqual([ + '@ccCache_alice.mycozy.cloud_SomeRequestName', + '@ccCache_alice.mycozy.cloud_eyJtZXRob2QiOiJHRVQiLCJwYXRoIjoiL3NvbWUvcGF0aCIsImJvZHkiOnt9LCJvcHRpb25zIjp7fX0=', + '@ccCache_bob.mycozy.cloud_SomeRequestName' + ]) + + await clearClientCachedData(aliceClient) + + const keys2 = await getAllKeys() + expect(keys2).toStrictEqual(['@ccCache_bob.mycozy.cloud_SomeRequestName']) + }) + }) +}) + +const aliceClient = { + getStackClient: () => ({ uri: 'https://alice.mycozy.cloud' }) +} as CozyClient + +const aliceClientWithPort = { + getStackClient: () => ({ uri: 'https://alice.mycozy.cloud:8080' }) +} as CozyClient + +const bobClient = { + getStackClient: () => ({ uri: 'https://bob.mycozy.cloud' }) +} as CozyClient + +const getFakeAppData = (id = 'SomeId'): AppData => ({ + attributes: { + AppEditor: 'SomeAppEditor', + AppName: 'SomeAppName', + AppNamePrefix: 'SomeAppNamePrefix', + AppSlug: 'SomeAppSlug', + Capabilities: 'SomeCapabilities', + Cookie: 'SomeCookie', + CozyBar: 'SomeCozyBar', + CozyClientJS: 'SomeCozyClientJS', + CozyFonts: 'SomeCozyFonts', + DefaultWallpaper: 'SomeDefaultWallpaper', + Domain: 'SomeDomain', + Favicon: 'SomeFavicon', + Flags: 'SomeFlags', + IconPath: 'SomeIconPath', + Locale: 'SomeLocale', + SubDomain: 'SomeSubDomain', + ThemeCSS: 'SomeThemeCSS', + Token: 'SomeToken', + Tracking: 'SomeTracking' + }, + id: id, + links: { self: 'SomeSelf' }, + meta: {}, + type: 'SomeType' +}) diff --git a/src/libs/localStore/clientCachedStorage.ts b/src/libs/localStore/clientCachedStorage.ts new file mode 100644 index 000000000..15dec9ab5 --- /dev/null +++ b/src/libs/localStore/clientCachedStorage.ts @@ -0,0 +1,95 @@ +import AsyncStorage from '@react-native-async-storage/async-storage' + +import CozyClient from 'cozy-client' +import Minilog from 'cozy-minilog' + +import { normalizeFqdn } from '/libs/functions/stringHelpers' +import { AppData } from '/libs/httpserver/models' +import { getData, storeData } from '/libs/localStore/storage' + +const log = Minilog('clientCachedStorage.ts') + +const clientCachePrefix = '@ccCache_' + +type CozyClientCacheKey = `@ccCache_${string}` + +type CacheableObject = AppData + +interface CozyClientRequest { + method: unknown + path: unknown + body: unknown + options: unknown +} + +const { getAllKeys, removeItem } = AsyncStorage + +export const storeClientCachedData = async ( + client: CozyClient | null, + request: string | CozyClientRequest, + result: CacheableObject +): Promise => { + if (!client) return + + const key = formatKey(client, request) + + // @ts-expect-error Keys and values are already checked here as an CozyClientCacheKey + await storeData(key, result) +} + +export const getClientCachedData = async ( + client: CozyClient | null, + request: string | CozyClientRequest +): Promise => { + if (!client) return null + + const key = formatKey(client, request) + + // @ts-expect-error Keys and values are already checked here as an CozyClientCacheKey + const result = await getData(key) + + return result +} + +export const clearClientCachedData = async ( + client: CozyClient | null +): Promise => { + try { + if (!client) return + + const normalizedFqdn = getNormalizedFqdn(client) + const keys = await getAllKeys() + + const clientCacheKeys = keys.filter(k => + k.startsWith(`${clientCachePrefix}${normalizedFqdn}`) + ) + + for (const key of clientCacheKeys) { + await removeItem(key) + } + } catch (error) { + log.error(`Failed to clear ClientCache data from persistent storage`, error) + } +} + +const formatKey = ( + client: CozyClient, + request: string | CozyClientRequest +): CozyClientCacheKey => { + const normalizedFqdn = getNormalizedFqdn(client) + + const key = + typeof request === 'string' ? request : btoa(JSON.stringify(request)) + + return `${clientCachePrefix}${normalizedFqdn}_${key}` +} + +const getNormalizedFqdn = (client: CozyClient): string => { + const rootURL = client.getStackClient().uri + + const { host: fqdn } = new URL(rootURL) + + const normalizedFqdn = normalizeFqdn(fqdn) + + return normalizedFqdn +} From f14949fb8bb5805b8cae211b42b00023cb99262e Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 11 Jul 2024 18:29:11 +0200 Subject: [PATCH 08/92] feat: Cache `/apps/:slug/open` request for offline support `/apps/:slug/open` request is based on `client.fetchJSON()` and is on the critical execution path for the application to boot So we want to use the new caching mechanism from previous commit in order to retrieve this request's cached result if the application is booted while the device is offline --- src/libs/client.js | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/libs/client.js b/src/libs/client.js index f304bba49..a5612c9c8 100644 --- a/src/libs/client.js +++ b/src/libs/client.js @@ -29,6 +29,10 @@ export { callOnboardingInitClient } from '/libs/clientHelpers/initClient' export { call2FAInitClient } from '/libs/clientHelpers/twoFactorAuthentication' +import { + getClientCachedData, + storeClientCachedData +} from '/libs/localStore/clientCachedStorage' import { CozyPersistedStorageKeys, getData } from '/libs/localStore/storage' import { getLinks } from '/pouchdb/getLinks' @@ -110,6 +114,7 @@ export const fetchPublicData = async client => { */ export const fetchCozyDataForSlug = async (slug, client, cookie) => { + const cacheKey = `CozyData_${slug}` const stackClient = client.getStackClient() const options = cookie @@ -123,14 +128,28 @@ export const fetchCozyDataForSlug = async (slug, client, cookie) => { } : undefined - const result = await stackClient.fetchJSON( - 'GET', - `/apps/${slug}/open`, - undefined, - options - ) + try { + const result = await stackClient.fetchJSON( + 'GET', + `/apps/${slug}/open`, + undefined, + options + ) + + storeClientCachedData(client, cacheKey, result) - return result + return result + } catch (err) { + if (err.message === 'Network request failed') { + const cachedResult = await getClientCachedData(client, cacheKey) + + if (cachedResult) { + return cachedResult + } + } + + throw err + } } /** From b5caa41154ef715fbf3f6cd78c7094bc7bb50d38 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 24 Sep 2024 12:52:48 +0200 Subject: [PATCH 09/92] feat: Upgrade cozy-intent to `2.23.0` `cozy-intent` has been upgraded to `2.23.0` to retrieve new interfaces for FlagshipLink and files downloading Related PR: cozy/cozy-libs#2562 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 262058377..4a8ea65a9 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "cozy-clisk": "^0.38.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", diff --git a/yarn.lock b/yarn.lock index a115d3b56..29a8be8af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8728,10 +8728,10 @@ cozy-flags@^3.2.0: dependencies: microee "^0.0.6" -cozy-intent@^2.22.0: - version "2.22.0" - resolved "https://registry.yarnpkg.com/cozy-intent/-/cozy-intent-2.22.0.tgz#a4333463ca934d2a5cfe34a23b0d6f0e50d27934" - integrity sha512-aCIlwLuia5llX36eubgbkah3vR3709V7VFDWl2hndvdmyjZBHh1siJUlJxpA5nYayqJkLy7aC4bdfGpTetgoCQ== +cozy-intent@^2.23.0: + version "2.23.0" + resolved "https://registry.yarnpkg.com/cozy-intent/-/cozy-intent-2.23.0.tgz#b6f3a407413df05c108e848b9dcb074b8780824b" + integrity sha512-DFn0ny4B4HpOE+3PYuZTTa074gRnFHqID+XaJ3gY2OrPL2xUQKEZmmFLp2bPVWThi5FvgvsU3EQeWPHZNQPbaQ== dependencies: cozy-minilog "^3.3.1" post-me "0.4.5" From ba477b85bb719050fc266f94427c1c7b0d890c71 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 30 Jul 2024 19:14:51 +0200 Subject: [PATCH 10/92] feat: Implement FlagshipLinkRequest interface through localMethods In previous commits we configured the application's cozy-client to use CozyPouchLink This link is configured for all react-native side queries, but not for cozy-app queries as they are served inside of WebViews and use a different cozy-client instance We want cozy-apps to benefits from the CozyPouchLink by redirecting their queries to the react-native side using the new FlagshipLink interface With this interface, all cozy-apps queries can be redirected to the react-native side using cozy-intent/post-me In order to intercept them, then we should declare `FlagshipLinkRequest()` method `localMethods` Related PR: cozy/cozy-client#1505 --- src/@types/cozy-client.d.ts | 12 +++++++++++- src/libs/intents/flagshipLink.ts | 33 ++++++++++++++++++++++++++++++++ src/libs/intents/localMethods.ts | 5 ++++- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/libs/intents/flagshipLink.ts diff --git a/src/@types/cozy-client.d.ts b/src/@types/cozy-client.d.ts index 481e60b27..323ff019c 100644 --- a/src/@types/cozy-client.d.ts +++ b/src/@types/cozy-client.d.ts @@ -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 { @@ -171,6 +177,10 @@ declare module 'cozy-client' { on: (event: string, callback: () => void) => void removeListener: (event: string, callback: () => void) => void logout: () => Promise + query: ( + queryDefinition: QueryDefinition, + options?: QueryOptions + ) => Promise } export const createMockClient = (options?: ClientOptions): CozyClient => diff --git a/src/libs/intents/flagshipLink.ts b/src/libs/intents/flagshipLink.ts new file mode 100644 index 000000000..1248cd2a3 --- /dev/null +++ b/src/libs/intents/flagshipLink.ts @@ -0,0 +1,33 @@ +import CozyClient, { QueryDefinition } from 'cozy-client' +import type { QueryResult } from 'cozy-client/types/types' +import Minilog from 'cozy-minilog' + +import { getErrorMessage } from '/libs/functions/getErrorMessage' + +const log = Minilog('⛳🔗 flagshipLink') + +export const flagshipLinkRequest = async ( + operation: QueryDefinition, + client: CozyClient | undefined +): Promise => { + try { + if (!client) { + throw new Error( + 'FlagshipLinkRequest should not be called with undefined client' + ) + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const result = await client.query(operation) + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return result + } catch (error) { + const errorMessage = getErrorMessage(error) + log.error( + `Something when wrong while processing FlagshipLinkRequest: ${errorMessage}`, + operation + ) + throw error + } +} diff --git a/src/libs/intents/localMethods.ts b/src/libs/intents/localMethods.ts index 362d1366b..0c68a441b 100644 --- a/src/libs/intents/localMethods.ts +++ b/src/libs/intents/localMethods.ts @@ -1,7 +1,7 @@ import { Linking } from 'react-native' import { getDeviceName } from 'react-native-device-info' -import CozyClient from 'cozy-client' +import CozyClient, { QueryDefinition } from 'cozy-client' import { FlagshipUI, NativeMethodsRegisterWithOptions, @@ -36,6 +36,7 @@ import { openApp } from '/libs/functions/openApp' import { routes } from '/constants/routes' import { sendKonnectorsLogs } from '/libs/konnectors/sendKonnectorsLogs' import { setDefaultRedirection } from '/libs/defaultRedirection/defaultRedirection' +import { flagshipLinkRequest } from '/libs/intents/flagshipLink' import { setFlagshipUI } from '/libs/intents/setFlagshipUI' import { showInAppBrowser, closeInAppBrowser } from '/libs/intents/InAppBrowser' import { isBiometryDenied } from '/app/domain/authentication/services/BiometryService' @@ -398,6 +399,8 @@ export const localMethods = ( _options: PostMeMessageOptions, colorScheme: string | undefined ) => Promise.resolve(setColorScheme(colorScheme)), + flagshipLinkRequest: (_options, operation) => + flagshipLinkRequest(operation as QueryDefinition, client), ...mergedMethods } } From 5eb7cf14aedf8c81655314cd7eea4b6f884a4907 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 11 Jul 2024 18:50:31 +0200 Subject: [PATCH 11/92] feat: Enable CacheMode for CozyProxyWebView on android when offline When a cozy-app's WebView is served offline, then the cozy-app bundle is served from local folder using the CozyProxyWebview, and since previous commits, the cozy-client's queries are served through `FlagshipLink` With those mechanisms, nearly all data are available offline except two things: - cozy-stack static assets (i.e. some css files, avatar, partners logos etc) - `fetchJSON()` requests This commit tries to handle the static assets part Those assets are often served with HTTP cache activated, and so the WebView should be able to load them correctly when the device is offline. However on Android, the WebView's cache seems not to persist after the application is closed Activating the `cacheMode=LOAD_CACHE_ELSE_NETWORK` seems to fix this behavior. But it prioritizes too much the cache over fresh data, so we want to activate it only when strictly necessary, which is when the application is offline This also allows to load partners logos that are not cached by the cozy-stack (as of today). But we expect to modify the cozy-stack to enable cache on them --- .../webviews/CozyProxyWebView.functions.js | 4 ++- src/components/webviews/CozyProxyWebView.js | 3 ++ .../webviews/CozyProxyWebView.spec.js | 5 +++- src/libs/client.js | 19 +++++++++++-- src/libs/httpserver/httpServerProvider.tsx | 28 +++++++++++++++---- src/libs/httpserver/indexDataFetcher.spec.ts | 21 ++++++++++++-- src/libs/httpserver/indexDataFetcher.ts | 4 ++- 7 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/components/webviews/CozyProxyWebView.functions.js b/src/components/webviews/CozyProxyWebView.functions.js index eb6f6c84f..adece7ad4 100644 --- a/src/components/webviews/CozyProxyWebView.functions.js +++ b/src/components/webviews/CozyProxyWebView.functions.js @@ -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 && @@ -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 diff --git a/src/components/webviews/CozyProxyWebView.js b/src/components/webviews/CozyProxyWebView.js index 10cb03c42..93b977c86 100644 --- a/src/components/webviews/CozyProxyWebView.js +++ b/src/components/webviews/CozyProxyWebView.js @@ -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} diff --git a/src/components/webviews/CozyProxyWebView.spec.js b/src/components/webviews/CozyProxyWebView.spec.js index 9a4808230..c2fdea139 100644 --- a/src/components/webviews/CozyProxyWebView.spec.js +++ b/src/components/webviews/CozyProxyWebView.spec.js @@ -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 = {} diff --git a/src/libs/client.js b/src/libs/client.js index a5612c9c8..d0efd3c35 100644 --- a/src/libs/client.js +++ b/src/libs/client.js @@ -103,6 +103,13 @@ export const fetchPublicData = async client => { } } +/** + * @template T + * @typedef {object} CozyDataStackOrCache + * @property {'stack'|'cache'} source - Source of the retrieved data + * @property {T} data - The appliation data + */ + /** * Fetches the data that is used to display a cozy application. * @@ -110,7 +117,7 @@ export const fetchPublicData = async client => { * @param {string} slug - The application slug * @param {object} client - A CozyClient instance * @param {object} [cookie] - An object containing a name and value property - * @returns {Promise} - The application data + * @returns {Promise>} - The application data */ export const fetchCozyDataForSlug = async (slug, client, cookie) => { @@ -138,13 +145,19 @@ export const fetchCozyDataForSlug = async (slug, client, cookie) => { storeClientCachedData(client, cacheKey, result) - return result + return { + source: 'stack', + data: result.data + } } catch (err) { if (err.message === 'Network request failed') { const cachedResult = await getClientCachedData(client, cacheKey) if (cachedResult) { - return cachedResult + return { + source: 'cache', + data: cachedResult.data + } } } diff --git a/src/libs/httpserver/httpServerProvider.tsx b/src/libs/httpserver/httpServerProvider.tsx index 3cf204e17..ac72d316a 100644 --- a/src/libs/httpserver/httpServerProvider.tsx +++ b/src/libs/httpserver/httpServerProvider.tsx @@ -35,6 +35,11 @@ const DEFAULT_PORT = Config.HTTP_SERVER_DEFAULT_PORT ? Number(Config.HTTP_SERVER_DEFAULT_PORT) : 5759 +interface IndexHtmlForSlug { + source: 'stack' | 'cache' + html: string +} + interface HttpServerState { server: HttpServer | undefined securityKey: string @@ -43,7 +48,7 @@ interface HttpServerState { getIndexHtmlForSlug: ( slug: string, client: CozyClient - ) => Promise + ) => Promise } interface SecurityKeyResult { @@ -118,7 +123,7 @@ export const HttpServerProvider = ( const getIndexHtmlForSlug = async ( slug: string, client: CozyClient - ): Promise => { + ): Promise => { try { if (!serverInstance) { throw new Error('ServerInstance is null, should not happen') @@ -128,7 +133,10 @@ export const HttpServerProvider = ( const { host: fqdn } = new URL(rootURL) - const { cookie, templateValues } = await fetchAppDataForSlug(slug, client) + const { cookie, source, templateValues } = await fetchAppDataForSlug( + slug, + client + ) await setCookie(cookie, client) const rawHtml = await getIndexForFqdnAndSlug(fqdn, slug) @@ -146,20 +154,30 @@ export const HttpServerProvider = ( indexData: templateValues }) if (slug === 'home') { - return flow( + const html = flow( addColorSchemeMetaIfNecessary, addBarStyles, addBodyClasses, addMetaAttributes )(computedHtml) + + return { + source, + html + } } else { // We do not need the bar styles for other app. We only need it for // the Home application since this is the only "immersive app" - return flow( + const html = flow( addColorSchemeMetaIfNecessary, addBodyClasses, addMetaAttributes )(computedHtml) + + return { + source, + html + } } } catch (err) { const errorMessage = getErrorMessage(err) diff --git a/src/libs/httpserver/indexDataFetcher.spec.ts b/src/libs/httpserver/indexDataFetcher.spec.ts index 138642aef..9e9737b72 100644 --- a/src/libs/httpserver/indexDataFetcher.spec.ts +++ b/src/libs/httpserver/indexDataFetcher.spec.ts @@ -12,7 +12,7 @@ jest.mock('@react-native-cookies/cookies', () => ({ })) const mockedFetchCozyDataForSlug = fetchCozyDataForSlug as jest.MockedFunction< - typeof fetchCozyDataForSlug + typeof fetchCozyDataForSlug > describe('indexDataFetcher', () => { @@ -37,7 +37,23 @@ describe('indexDataFetcher', () => { }) }) -const mockStackResult = { +interface StackResultData { + type: string + id: string + attributes: Record + meta: unknown + links: { + self: string + } +} + +interface StackResult { + source: 'stack' + data: StackResultData +} + +const mockStackResult: StackResult = { + source: 'stack', data: { type: 'io.cozy.apps.open', id: 'home', @@ -72,6 +88,7 @@ const mockStackResult = { const expectedResult = { cookie: 'SOME_COOKIE', + source: 'stack', templateValues: { AppEditor: 'Cozy', AppName: 'Home', diff --git a/src/libs/httpserver/indexDataFetcher.ts b/src/libs/httpserver/indexDataFetcher.ts index 25161ca0c..b42f037b4 100644 --- a/src/libs/httpserver/indexDataFetcher.ts +++ b/src/libs/httpserver/indexDataFetcher.ts @@ -11,6 +11,7 @@ export type TemplateValues = Omit & { } interface FetchAppDataForSlugResult { + source: 'stack' | 'cache' cookie: AppAttributes['Cookie'] templateValues: TemplateValues } @@ -23,7 +24,7 @@ export const fetchAppDataForSlug = async ( ): Promise => { try { const storedCookie = await getCookie(client) - const cozyDataResult = await fetchCozyDataForSlug<{ data: AppData }>( + const cozyDataResult = await fetchCozyDataForSlug( slug, client, storedCookie @@ -57,6 +58,7 @@ export const fetchAppDataForSlug = async ( return { cookie, + source: cozyDataResult.source, templateValues: { ...cozyDataAttributes, // The following attributes have to be sanitized to avoid semantic quotes. From 96e14fb5bc15ce847b87eecd19852dd00f203859 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 30 Jul 2024 19:19:22 +0200 Subject: [PATCH 12/92] feat: Ignore queries warmup on PouchLink With previous changes, we now expect the CozyPouchLink to be the last link of the chain, so forwarding the query would result to an exception thrown This mean we cannot forward the query when warmup queries are not finished yet So we want to allow CozyPouchLink to ignore the verification step and process the query independently of the warmup queries status To allow this we introduce the `ignoreWarmup` parameter. When set to `true` the CozyPouchLink will process the query even if warmup is not finished yet Related PR: cozy/cozy-client#1506 --- src/pouchdb/getLinks.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pouchdb/getLinks.ts b/src/pouchdb/getLinks.ts index ac6e565f9..5ad1d6abe 100644 --- a/src/pouchdb/getLinks.ts +++ b/src/pouchdb/getLinks.ts @@ -29,7 +29,8 @@ export const getLinks = (): CozyLink[] => { const pouchLinkOptions = { doctypes: offlineDoctypes, initialSync: true, - platform: platformReactNative + platform: platformReactNative, + ignoreWarmup: true } const stackLink = new StackLink({ From 3e9f251103bf0cdf3fbe12c4dcc9c0d0976c807f Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 26 Jul 2024 17:34:58 +0200 Subject: [PATCH 13/92] feat: Declare schemas into cozy-client instance Some cozy-apps declare a data `schema` into cozy-client instance This schema is then used for processing queries As we now process cozy-app queries on the react-native side through FlagshipLinkRequest, we want to declared this schema in the Flagship app's cozy-client For now we only need to implement the one from cozy-home The ideal implementation would be to extract it from the cozy-app's bundle, but we did not implement anything to support this (we should first investigate how to do this). So for now it is hardcoded --- src/components/webviews/CozyWebview.spec.js | 1 + src/libs/client.js | 4 +- src/libs/client.spec.js | 3 +- src/libs/clientHelpers/createClient.ts | 4 +- src/pouchdb/schema.js | 56 ++++++++++++++++++++ src/screens/konnectors/LauncherView.spec.jsx | 1 + 6 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 src/pouchdb/schema.js diff --git a/src/components/webviews/CozyWebview.spec.js b/src/components/webviews/CozyWebview.spec.js index fa1b6e786..8efaf03b1 100644 --- a/src/components/webviews/CozyWebview.spec.js +++ b/src/components/webviews/CozyWebview.spec.js @@ -63,6 +63,7 @@ jest.mock('cozy-intent', () => ({ })) jest.mock('cozy-client', () => ({ + ...jest.requireActual('cozy-client'), useClient: jest.fn().mockReturnValue({}), useInstanceInfo: jest.fn().mockReturnValue({}) })) diff --git a/src/libs/client.js b/src/libs/client.js index d0efd3c35..76a32e393 100644 --- a/src/libs/client.js +++ b/src/libs/client.js @@ -35,6 +35,7 @@ import { } from '/libs/localStore/clientCachedStorage' import { CozyPersistedStorageKeys, getData } from '/libs/localStore/storage' import { getLinks } from '/pouchdb/getLinks' +import schema from '/pouchdb/schema' const log = Minilog('LoginScreen') @@ -58,7 +59,8 @@ export const getClient = async () => { slug: 'flagship', version: packageJSON.version }, - links + links, + schema }) listenTokenRefresh(client) client.getStackClient().setOAuthOptions(oauthOptions) diff --git a/src/libs/client.spec.js b/src/libs/client.spec.js index 6de3a03f8..0dc42695b 100644 --- a/src/libs/client.spec.js +++ b/src/libs/client.spec.js @@ -63,7 +63,8 @@ describe('client', () => { slug: 'flagship', version: packageJSON.version }, - links: expect.anything() + links: expect.anything(), + schema: expect.anything() }) }) diff --git a/src/libs/clientHelpers/createClient.ts b/src/libs/clientHelpers/createClient.ts index 8e16bc5bd..e53bdb70b 100644 --- a/src/libs/clientHelpers/createClient.ts +++ b/src/libs/clientHelpers/createClient.ts @@ -14,6 +14,7 @@ import packageJSON from '../../../package.json' import { startListening } from '/app/domain/authentication/services/AuthService' import { getLinks } from '/pouchdb/getLinks' +import schema from '/pouchdb/schema' /** * Create a CozyClient for the given Cozy instance and register it @@ -41,7 +42,8 @@ export const createClient = async (instance: string): Promise => { slug: 'flagship', version: packageJSON.version }, - links + links, + schema } const client = new CozyClient(options) diff --git a/src/pouchdb/schema.js b/src/pouchdb/schema.js new file mode 100644 index 000000000..b382eb36e --- /dev/null +++ b/src/pouchdb/schema.js @@ -0,0 +1,56 @@ +import { QueryDefinition, HasMany } from 'cozy-client' + +const CONTACTS_DOCTYPE = 'io.cozy.contacts' +const FILES_DOCTYPE = 'io.cozy.files' +const BILLS_DOCTYPE = 'io.cozy.bills' + +class HasManyBills extends HasMany { + get data() { + const refs = this.target.relationships.referenced_by.data + return refs + ? refs + .map(ref => { + if (ref.type === BILLS_DOCTYPE) { + return this.get(ref.type, ref.id) + } + }) + .filter(Boolean) + : [] + } + + static query(doc, client, assoc) { + if ( + !doc.relationships || + !doc.relationships.referenced_by || + !doc.relationships.referenced_by.data + ) { + return null + } + + const included = doc.relationships.referenced_by.data + const ids = included + .filter(inc => inc.type === assoc.doctype) + .map(inc => inc.id) + + return new QueryDefinition({ doctype: assoc.doctype, ids }) + } +} + +// the documents schema, necessary for CozyClient +export default { + contacts: { + doctype: CONTACTS_DOCTYPE, + attributes: {}, + relationships: {} + }, + files: { + doctype: FILES_DOCTYPE, + attributes: {}, + relationships: { + bills: { + type: HasManyBills, + doctype: BILLS_DOCTYPE + } + } + } +} diff --git a/src/screens/konnectors/LauncherView.spec.jsx b/src/screens/konnectors/LauncherView.spec.jsx index b1631ffa2..959a4d9be 100644 --- a/src/screens/konnectors/LauncherView.spec.jsx +++ b/src/screens/konnectors/LauncherView.spec.jsx @@ -10,6 +10,7 @@ jest.mock('@fengweichong/react-native-gzip', () => { }) jest.mock('cozy-client', () => ({ + ...jest.requireActual('cozy-client'), withClient: jest.fn().mockReturnValue({}) })) From 30229133e7379e4a0e281b4a5a528846542e4c87 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 26 Jul 2024 17:37:59 +0200 Subject: [PATCH 14/92] feat: Add react-native-mail plugin --- __tests__/jestSetupFile.js | 8 ++++++++ ios/Podfile.lock | 12 ++++++++++++ package.json | 2 ++ yarn.lock | 27 ++++++++++++++++++++++++++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/__tests__/jestSetupFile.js b/__tests__/jestSetupFile.js index 026e3a601..d405b1a72 100644 --- a/__tests__/jestSetupFile.js +++ b/__tests__/jestSetupFile.js @@ -140,3 +140,11 @@ jest.mock('cozy-pouch-link', () => { return new mockPouchLink() }) }) + +jest.mock('react-native-mail', () => ({ + mail: jest.fn() +})) + +jest.mock('rn-fetch-blob', () => ({ + mail: jest.fn() +})) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 074c8d3ee..0e7bf7dc8 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1086,6 +1086,8 @@ PODS: - 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 @@ -1284,6 +1286,8 @@ PODS: - React-jsi (= 0.73.10) - React-logger (= 0.73.10) - React-perflogger (= 0.73.10) + - rn-fetch-blob (0.12.0): + - React-Core - RNBackgroundFetch (4.2.5): - React-Core - RNBackgroundGeolocation (4.16.4): @@ -1420,6 +1424,7 @@ DEPENDENCIES: - 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`) @@ -1450,6 +1455,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`) @@ -1595,6 +1601,8 @@ EXTERNAL SOURCES: :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: @@ -1655,6 +1663,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: @@ -1780,6 +1790,7 @@ SPEC CHECKSUMS: 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 @@ -1810,6 +1821,7 @@ SPEC CHECKSUMS: React-runtimescheduler: 639000d08f94ee18017fad4c691c572bb6dfbe50 React-utils: c3a9bede4e9aa5c0a8184958cd4c09ff461fd60f ReactCommon: 92194c3d756b82bff5b75450544b2863b4df0d5f + rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba RNBackgroundFetch: 2f800a04434620db15ede2e8f21886608a2d1743 RNBackgroundGeolocation: 7df16548756b443aac809663cba8a4e03f0b8732 RNBootSplash: 4844706cbb56a3270556c9b94e59dedadccd47e4 diff --git a/package.json b/package.json index 4a8ea65a9..f12959545 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "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", @@ -129,6 +130,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" }, diff --git a/yarn.lock b/yarn.lock index 29a8be8af..05b68f452 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7508,7 +7508,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base-64@^0.1.0: +base-64@0.1.0, base-64@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs= @@ -11418,6 +11418,18 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== +glob@7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" + integrity sha512-f8c0rE8JiCxpa52kWPAOa3ZaYEnzofDzCQLCn3Vdk0Z5OVLq3BsRFJI4S4ykpeVW6QMGBUkMeUpoEgWnMTnw5Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.2" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -17683,6 +17695,11 @@ react-native-localize@2.2.6: resolved "https://registry.yarnpkg.com/react-native-localize/-/react-native-localize-2.2.6.tgz#484f8c700bc629f230066e819265f80f6dd3ef58" integrity sha512-EZETlC1ZlW/4g6xfsNCwAkAw5BDL2A6zk/08JjFR/GRGxYuKRD7iP1hHn1+h6DEu+xROjPpoNeXfMER2vkTVIQ== +react-native-mail@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/react-native-mail/-/react-native-mail-6.1.1.tgz#f1b1f8034c84d2510a93e4a2a795f0db5a13595e" + integrity sha512-pTs180wwyh7hN/iyTC9SfOX579U4YhDlHOLxi47IGvhPJENqO/QFdBq+wWKxyhNqdQuVSy+LoeIxLreWnIeYmg== + react-native-mask-input@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/react-native-mask-input/-/react-native-mask-input-1.2.1.tgz#2f01683844ded3693d0d845bd1beeb2b8367f2e2" @@ -18477,6 +18494,14 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rn-fetch-blob@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/rn-fetch-blob/-/rn-fetch-blob-0.12.0.tgz#ec610d2f9b3f1065556b58ab9c106eeb256f3cba" + integrity sha512-+QnR7AsJ14zqpVVUbzbtAjq0iI8c9tCg49tIoKO2ezjzRunN7YL6zFSFSWZm6d+mE/l9r+OeDM3jmb2tBb2WbA== + dependencies: + base-64 "0.1.0" + glob "7.0.6" + rn-flipper-async-storage-advanced@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/rn-flipper-async-storage-advanced/-/rn-flipper-async-storage-advanced-1.0.4.tgz#ca3d0c315a75f379fef3771cc6ee3bef2d774265" From 6357c9d71b9d75979b4a0aa47077b0c874fe0c53 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 26 Jul 2024 18:35:44 +0200 Subject: [PATCH 15/92] feat: Allow to send Pouch databases through email for debug purpose By adding offline support through PouchDB, we expect database related bugs to happens in the future In order to ease debugging them, we want to allow exploring the local PouchDB files The easier way is to add the ability to extract them from the device and send them through email to cozy's support team --- src/hooks/useAppBootstrap.js | 5 + src/hooks/useAppBootstrap.spec.js | 1 + src/pouchdb/deeplinkHandler.ts | 23 +++++ src/pouchdb/sendDbByEmail.ts | 146 ++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 src/pouchdb/deeplinkHandler.ts create mode 100644 src/pouchdb/sendDbByEmail.ts diff --git a/src/hooks/useAppBootstrap.js b/src/hooks/useAppBootstrap.js index c00d813a9..f2921b993 100644 --- a/src/hooks/useAppBootstrap.js +++ b/src/hooks/useAppBootstrap.js @@ -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' @@ -160,6 +161,10 @@ export const useAppBootstrap = client => { return } + if (handleDbDeepLink(url, client)) { + return + } + if (!client) { const action = parseOnboardLink(url) diff --git a/src/hooks/useAppBootstrap.spec.js b/src/hooks/useAppBootstrap.spec.js index 799fd4c5f..64935073d 100644 --- a/src/hooks/useAppBootstrap.spec.js +++ b/src/hooks/useAppBootstrap.spec.js @@ -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({}) diff --git a/src/pouchdb/deeplinkHandler.ts b/src/pouchdb/deeplinkHandler.ts new file mode 100644 index 000000000..0867436a6 --- /dev/null +++ b/src/pouchdb/deeplinkHandler.ts @@ -0,0 +1,23 @@ +import CozyClient from 'cozy-client' + +import strings from '/constants/strings.json' +import { sendDbByEmail } from '/pouchdb/sendDbByEmail' + +export const handleDbDeepLink = (url: string, client?: CozyClient): boolean => { + if (isSendDbDeepLink(url)) { + void sendDbByEmail(client) + + return true + } + + return false +} + +const isSendDbDeepLink = (url: string): boolean => { + const deepLinks = [ + `${strings.COZY_SCHEME}senddb`, + `${strings.UNIVERSAL_LINK_BASE}/senddb` + ] + + return deepLinks.includes(url.toLowerCase()) +} diff --git a/src/pouchdb/sendDbByEmail.ts b/src/pouchdb/sendDbByEmail.ts new file mode 100644 index 000000000..e13ad654a --- /dev/null +++ b/src/pouchdb/sendDbByEmail.ts @@ -0,0 +1,146 @@ +import { Alert, PermissionsAndroid, Platform } from 'react-native' +import Mailer from 'react-native-mail' +import RNFS from 'react-native-fs' +import RNFetchBlob from 'rn-fetch-blob' +import DeviceInfo from 'react-native-device-info' + +import type CozyClient from 'cozy-client' +import Minilog from 'cozy-minilog' + +import { fetchSupportMail } from '/app/domain/logger/supportEmail' +import { + hideSplashScreen, + showSplashScreen, + splashScreens +} from '/app/theme/SplashScreenService' +import { getInstanceAndFqdnFromClient } from '/libs/client' +import { getErrorMessage } from '/libs/functions/getErrorMessage' + +const log = Minilog('🗒️ DB Mailer') + +export const sendDbByEmail = async (client?: CozyClient): Promise => { + log.info('Send DB by email') + + if (!client) { + log.info('SendDbByEmail called with no client, return') + return + } + + try { + const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE + await PermissionsAndroid.request(permission) + + const supportEmail = await fetchSupportMail(client) + + const { fqdn } = getInstanceAndFqdnFromClient(client) + + const instance = client.getStackClient().uri ?? 'not logged app' + + const subject = `DB files for ${instance}` + + const files = await RNFS.readDir(RNFS.DocumentDirectoryPath) + + const dbFiles = files.filter(f => f.name.startsWith(`${fqdn}_`)) + + const externalFiles = [] + for (const dbFile of dbFiles) { + const dirs = RNFetchBlob.fs.dirs + + const internalPath = dbFile.path + + if (Platform.OS === 'android') { + const date = Number(new Date()) + const externalPath = `${dirs.DCIMDir}/DbFile_${dbFile.name}${date}.sqlite` + + await RNFS.copyFile(internalPath, externalPath) + + externalFiles.push({ + path: externalPath + }) + } else { + externalFiles.push({ + path: dbFile.path, + type: 'pdf' // there is no compatible MIME type, so we use PDF one as replacement, this should change nothing expect the email aspect + }) + } + } + + await showSplashScreen(splashScreens.SEND_LOG_EMAIL) + log.info('Start email intent', externalFiles) + await sendMailPromise(subject, supportEmail, externalFiles).catch( + (errorData: sendMailError) => { + const { error, event } = errorData + Alert.alert( + error, + event, + [ + { + text: 'Ok', + onPress: (): void => log.debug('OK: Email Error Response') + }, + { + text: 'Cancel', + onPress: (): void => log.debug('CANCEL: Email Error Response') + } + ], + { cancelable: true } + ) + } + ) + log.info('Did finish email intent') + await hideSplashScreen(splashScreens.SEND_LOG_EMAIL) + } catch (error) { + const errorMessage = getErrorMessage(error) + log.error('Error while trying to send DB email', errorMessage) + } +} + +const sendMailPromise = ( + subject: string, + email: string, + attachments: Attachment[] +): Promise => { + return new Promise((resolve, reject) => { + Mailer.mail( + { + subject: subject, + recipients: [email], + body: buildMessageBody(), + isHTML: true, + attachments: attachments + }, + (error, event) => { + if (error) { + reject({ error, event }) + } else { + resolve() + } + } + ) + }) +} + +const buildMessageBody = (): string => { + const appVersion = DeviceInfo.getVersion() + const appBuild = DeviceInfo.getBuildNumber() + const bundle = DeviceInfo.getBundleId() + const deviceBrand = DeviceInfo.getBrand() + const deviceModel = DeviceInfo.getModel() + const os = DeviceInfo.getSystemName() + const version = DeviceInfo.getSystemVersion() + + const appInfo = `App info: ${appVersion} (${appBuild})` + const bundleInfo = `App bundle: ${bundle}` + const deviceInfo = `Device info: ${deviceBrand} ${deviceModel} ${os} ${version}` + + return `${appInfo}\n${bundleInfo}\n${deviceInfo}` +} + +interface sendMailError { + error: string + event?: string +} + +interface Attachment { + path: string +} From 7b9e8d1085ca370538ee40f0184b4a8fcf9fb638 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 30 Jul 2024 20:35:01 +0200 Subject: [PATCH 16/92] feat: Declare offline support in cozy-apps injected metadata --- src/components/webviews/jsInteractions/jsCozyInjection.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/webviews/jsInteractions/jsCozyInjection.ts b/src/components/webviews/jsInteractions/jsCozyInjection.ts index 6c1374da6..400a5ea9d 100644 --- a/src/components/webviews/jsInteractions/jsCozyInjection.ts +++ b/src/components/webviews/jsInteractions/jsCozyInjection.ts @@ -17,6 +17,7 @@ const makeMetadata = (routeName?: string): string => { return JSON.stringify({ immersive: routeName ? immersiveRoutes.includes(routeName) : false, navbarHeight, + offline_available: true, platform: Platform, routeName, statusBarHeight, From 3137ff1103ead149e9aa40989a631bcb77134213 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 13 Sep 2024 17:03:18 +0200 Subject: [PATCH 17/92] feat: Prevent local PouchDB modification to be replicated to remote DB For now we don't want to replicate local changes into remote PouchDB as we first want to ensure everything works as expected locally before activating two-way replication in the future --- src/pouchdb/getLinks.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pouchdb/getLinks.ts b/src/pouchdb/getLinks.ts index 5ad1d6abe..b4ffe5781 100644 --- a/src/pouchdb/getLinks.ts +++ b/src/pouchdb/getLinks.ts @@ -30,7 +30,17 @@ export const getLinks = (): CozyLink[] => { doctypes: offlineDoctypes, initialSync: true, platform: platformReactNative, - ignoreWarmup: true + ignoreWarmup: true, + doctypesReplicationOptions: Object.fromEntries( + offlineDoctypes.map(doctype => { + return [ + doctype, + { + strategy: 'fromRemote' + } + ] + }) + ) } const stackLink = new StackLink({ From b6e541d50d687c060f54cbd7988d00e66e375878 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 8 Aug 2024 15:13:08 +0200 Subject: [PATCH 18/92] feat: Add `downloadFile` in localMethods and handle offline files When the application is offline, we want the user to be able to access their files that are accessible offline To make this possible, we want the Flagship app to provide a new `downloadFileAndPreview()` interface that can be called from cozy-apps Internally this method should allow to download and preview files, but also to store them in the device's storage in order to make them accessible later even when the app is offline This method will be called from cozy-client through `file` model For now we make files accessible only if they have already downloaded once In the following commit we will make important files accessible in a proactive way --- src/@types/cozy-client.d.ts | 1 + .../services/BiometryService.spec.ts | 4 + .../services/LockScreenService.spec.ts | 4 + .../domain/io.cozy.files/offlineFiles.spec.ts | 246 ++++++++++++++++++ src/app/domain/io.cozy.files/offlineFiles.ts | 115 ++++++++ .../offlineFilesConfiguration.ts | 55 ++++ src/app/domain/io.cozy.files/remoteFiles.ts | 31 +++ .../settings/services/SettingsService.spec.ts | 3 + src/app/view/Secure/PinPrompt.spec.tsx | 3 + src/hooks/useGlobalAppState.spec.ts | 4 + src/libs/intents/localMethods.spec.ts | 3 + src/libs/intents/localMethods.ts | 4 + src/libs/localStore/storage.ts | 5 +- 13 files changed, 477 insertions(+), 1 deletion(-) create mode 100644 src/app/domain/io.cozy.files/offlineFiles.spec.ts create mode 100644 src/app/domain/io.cozy.files/offlineFiles.ts create mode 100644 src/app/domain/io.cozy.files/offlineFilesConfiguration.ts create mode 100644 src/app/domain/io.cozy.files/remoteFiles.ts diff --git a/src/@types/cozy-client.d.ts b/src/@types/cozy-client.d.ts index 323ff019c..90c4de695 100644 --- a/src/@types/cozy-client.d.ts +++ b/src/@types/cozy-client.d.ts @@ -111,6 +111,7 @@ declare module 'cozy-client' { export interface FileCollectionGetResult { data: { _id: string + _rev: string name: string path: string metadata?: { diff --git a/src/app/domain/authentication/services/BiometryService.spec.ts b/src/app/domain/authentication/services/BiometryService.spec.ts index 3a75df0e7..fe3033404 100644 --- a/src/app/domain/authentication/services/BiometryService.spec.ts +++ b/src/app/domain/authentication/services/BiometryService.spec.ts @@ -28,6 +28,10 @@ jest.mock('react-native-keychain') jest.mock('@react-native-cookies/cookies', () => ({})) +jest.mock('react-native-file-viewer', () => ({ + open: jest.fn() +})) + it('should not throw with a null hasBiometry', async () => { const result = await makeFlagshipMetadataInjection() expect(result).toStrictEqual( diff --git a/src/app/domain/authorization/services/LockScreenService.spec.ts b/src/app/domain/authorization/services/LockScreenService.spec.ts index 3dff83eaa..fedeb5e33 100644 --- a/src/app/domain/authorization/services/LockScreenService.spec.ts +++ b/src/app/domain/authorization/services/LockScreenService.spec.ts @@ -40,6 +40,10 @@ jest.mock('cozy-client', () => ({ })) })) +jest.mock('react-native-file-viewer', () => ({ + open: jest.fn() +})) + describe('validatePassword', () => { // Mocks and common setup let client: CozyClient diff --git a/src/app/domain/io.cozy.files/offlineFiles.spec.ts b/src/app/domain/io.cozy.files/offlineFiles.spec.ts new file mode 100644 index 000000000..5c6e750e1 --- /dev/null +++ b/src/app/domain/io.cozy.files/offlineFiles.spec.ts @@ -0,0 +1,246 @@ +import RNFS from 'react-native-fs' + +import CozyClient, { createMockClient } from 'cozy-client' +import { FileDocument } from 'cozy-client/types/types' + +import { + downloadFile, + getFilesFolder +} from '/app/domain/io.cozy.files/offlineFiles' +import { + addOfflineFileToConfiguration, + getOfflineFileFromConfiguration, + OfflineFile +} from '/app/domain/io.cozy.files/offlineFilesConfiguration' +import { + getDownloadUrlById, + getFileById +} from '/app/domain/io.cozy.files/remoteFiles' + +jest.mock('react-native-file-viewer', () => ({ + open: jest.fn() +})) +jest.mock('/app/domain/io.cozy.files/offlineFilesConfiguration', () => ({ + addOfflineFileToConfiguration: jest.fn(), + getOfflineFileFromConfiguration: jest.fn() +})) +jest.mock('/app/domain/io.cozy.files/remoteFiles', () => ({ + getFileById: jest.fn(), + getDownloadUrlById: jest.fn() +})) + +jest.mock('react-native-fs', () => { + return { + DocumentDirectoryPath: '/mockedDocumentDirectoryPath', + downloadFile: jest.fn().mockImplementation(() => ({ + promise: Promise.resolve({ + jobId: 1, + statusCode: 200, + bytesWritten: 100, + path: () => 'mocked-file-path' + }) + })), + mkdir: jest.fn(), + unlink: jest.fn() + } +}) + +const mockGetOfflineFileFromConfiguration = + getOfflineFileFromConfiguration as jest.MockedFunction< + typeof getOfflineFileFromConfiguration + > +const mockGetFileById = getFileById as jest.MockedFunction +const mockGetDownloadUrlById = getDownloadUrlById as jest.MockedFunction< + typeof getDownloadUrlById +> + +describe('offlineFiles', () => { + let mockClient: jest.Mocked + + beforeEach(() => { + const options = { + clientOptions: { + uri: 'http://claude.mycozy.cloud' + } + } as object + mockClient = createMockClient( + options + ) as Partial as jest.Mocked + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + describe('getFilesFolder', () => { + it('should return File path relative to instance url', () => { + const result = getFilesFolder(mockClient) + + expect(result).toBe( + '/mockedDocumentDirectoryPath/claude.mycozy.cloud/Files' + ) + }) + }) + + describe('downloadFile', () => { + it('should download file if not existing', async () => { + mockRemoteFile({ + _id: 'SOME_FILE_ID', + _rev: 'SOME_REV', + name: 'SOME_FILE_NAME' + }) + mockDownloadUrl('SOME_FILE_DOWNLOAD_URL') + mockFileFromConfiguration(undefined) + const file = { + _id: 'SOME_FILE_ID', + name: 'SOME_FILE_NAME' + } as unknown as FileDocument + const result = await downloadFile(file, mockClient) + + expect(RNFS.downloadFile).toHaveBeenCalledWith({ + fromUrl: 'SOME_FILE_DOWNLOAD_URL', + toFile: + '/mockedDocumentDirectoryPath/claude.mycozy.cloud/Files/SOME_FILE_NAME', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + begin: expect.anything(), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + progress: expect.anything(), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + progressInterval: expect.anything() + }) + expect(result).toBe( + '/mockedDocumentDirectoryPath/claude.mycozy.cloud/Files/SOME_FILE_NAME' + ) + expect(addOfflineFileToConfiguration).toHaveBeenCalledWith({ + id: 'SOME_FILE_ID', + path: '/mockedDocumentDirectoryPath/claude.mycozy.cloud/Files/SOME_FILE_NAME', + rev: 'SOME_REV' + }) + }) + + it('should not download file if existing', async () => { + mockRemoteFile({ + _id: 'SOME_FILE_ID', + _rev: 'SOME_REV', + name: 'SOME_FILE_NAME' + }) + mockFileFromConfiguration({ + id: 'SOME_FILE_ID', + rev: 'SOME_REV', + path: 'SOME_EXISTING_PATH_FROM_CACHE', + lastOpened: new Date() + }) + const file = { + _id: 'SOME_FILE_ID', + name: 'SOME_FILE_NAME' + } as unknown as FileDocument + const result = await downloadFile(file, mockClient) + + expect(RNFS.downloadFile).not.toHaveBeenCalled() + expect(result).toBe('SOME_EXISTING_PATH_FROM_CACHE') + expect(addOfflineFileToConfiguration).not.toHaveBeenCalled() + }) + + it('should download file if existing but with different rev', async () => { + mockRemoteFile({ + _id: 'SOME_FILE_ID', + _rev: 'SOME_NEW_REV', + name: 'SOME_FILE_NAME' + }) + mockDownloadUrl('SOME_NEW_FILE_DOWNLOAD_URL') + mockFileFromConfiguration({ + id: 'SOME_FILE_ID', + rev: 'SOME_REV', + path: 'SOME_EXISTING_PATH_FROM_CACHE', + lastOpened: new Date() + }) + const file = { + _id: 'SOME_FILE_ID', + name: 'SOME_FILE_NAME' + } as unknown as FileDocument + const result = await downloadFile(file, mockClient) + + expect(RNFS.downloadFile).toHaveBeenCalledWith({ + fromUrl: 'SOME_NEW_FILE_DOWNLOAD_URL', + toFile: + '/mockedDocumentDirectoryPath/claude.mycozy.cloud/Files/SOME_FILE_NAME', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + begin: expect.anything(), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + progress: expect.anything(), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + progressInterval: expect.anything() + }) + expect(result).toBe( + '/mockedDocumentDirectoryPath/claude.mycozy.cloud/Files/SOME_FILE_NAME' + ) + expect(addOfflineFileToConfiguration).toHaveBeenCalledWith({ + id: 'SOME_FILE_ID', + path: '/mockedDocumentDirectoryPath/claude.mycozy.cloud/Files/SOME_FILE_NAME', + rev: 'SOME_NEW_REV' + }) + }) + + it('should use new file name new rev with different name', async () => { + mockRemoteFile({ + _id: 'SOME_FILE_ID', + _rev: 'SOME_NEW_REV', + name: 'SOME_NEW_FILE_NAME' + }) + mockDownloadUrl('SOME_NEW_FILE_DOWNLOAD_URL') + mockFileFromConfiguration({ + id: 'SOME_FILE_ID', + rev: 'SOME_REV', + path: 'SOME_EXISTING_PATH_FROM_CACHE', + lastOpened: new Date() + }) + const file = { + _id: 'SOME_FILE_ID', + name: 'SOME_FILE_NAME' + } as unknown as FileDocument + const result = await downloadFile(file, mockClient) + + expect(RNFS.downloadFile).toHaveBeenCalledWith({ + fromUrl: 'SOME_NEW_FILE_DOWNLOAD_URL', + toFile: + '/mockedDocumentDirectoryPath/claude.mycozy.cloud/Files/SOME_NEW_FILE_NAME', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + begin: expect.anything(), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + progress: expect.anything(), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + progressInterval: expect.anything() + }) + expect(RNFS.unlink).toHaveBeenCalledWith('SOME_EXISTING_PATH_FROM_CACHE') + expect(result).toBe( + '/mockedDocumentDirectoryPath/claude.mycozy.cloud/Files/SOME_NEW_FILE_NAME' + ) + expect(addOfflineFileToConfiguration).toHaveBeenCalledWith({ + id: 'SOME_FILE_ID', + path: '/mockedDocumentDirectoryPath/claude.mycozy.cloud/Files/SOME_NEW_FILE_NAME', + rev: 'SOME_NEW_REV' + }) + }) + }) +}) + +const mockFileFromConfiguration = (file: OfflineFile | undefined): void => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + mockGetOfflineFileFromConfiguration.mockResolvedValue(file) +} + +const mockRemoteFile = (file: RemoteFile): void => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + mockGetFileById.mockResolvedValue(file as unknown as FileDocument) +} + +const mockDownloadUrl = (url: string): void => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + mockGetDownloadUrlById.mockResolvedValue(url) +} + +interface RemoteFile { + _id: string + _rev: string + name: string +} diff --git a/src/app/domain/io.cozy.files/offlineFiles.ts b/src/app/domain/io.cozy.files/offlineFiles.ts new file mode 100644 index 000000000..66afe5a22 --- /dev/null +++ b/src/app/domain/io.cozy.files/offlineFiles.ts @@ -0,0 +1,115 @@ +import FileViewer from 'react-native-file-viewer' +import RNFS from 'react-native-fs' + +import CozyClient from 'cozy-client' +import type { FileDocument } from 'cozy-client/types/types' +import Minilog from 'cozy-minilog' + +import { + addOfflineFileToConfiguration, + getOfflineFileFromConfiguration +} from '/app/domain/io.cozy.files/offlineFilesConfiguration' +import { + getDownloadUrlById, + getFileById +} from '/app/domain/io.cozy.files/remoteFiles' +import { getInstanceAndFqdnFromClient } from '/libs/client' +import { normalizeFqdn } from '/libs/functions/stringHelpers' + +const log = Minilog('📁 Offline Files') + +export const getFilesFolder = (client: CozyClient): string => { + const { fqdn } = getInstanceAndFqdnFromClient(client) + + const normalizedFqdn = normalizeFqdn(fqdn) + + const destinationPath = `${RNFS.DocumentDirectoryPath}/${normalizedFqdn}/Files` + + return destinationPath +} + +const ensureFilesFolder = async (client: CozyClient): Promise => { + await RNFS.mkdir(getFilesFolder(client)) +} + +export const downloadFile = async ( + file: FileDocument, + client: CozyClient, + setDownloadProgress?: (progress: number) => void +): Promise => { + try { + log.debug('Download file', file._id) + + await ensureFilesFolder(client) + + const filesFolder = getFilesFolder(client) + + const existingFile = await getOfflineFileFromConfiguration(file._id) + + const remoteFile = await getFileById(client, file._id) + + if (existingFile && existingFile.rev === remoteFile._rev) { + log.debug(`File ${file._id} already exist, returning existing one`) + return existingFile.path + } + + const filename = remoteFile.name + + const downloadURL = await getDownloadUrlById(client, file._id, filename) + + const destinationPath = `${filesFolder}/${filename}` + + const result = await RNFS.downloadFile({ + fromUrl: downloadURL, + toFile: `${filesFolder}/${filename}`, + begin: () => undefined, + progress: res => { + const progressPercent = res.bytesWritten / res.contentLength + setDownloadProgress?.(progressPercent) + }, + progressInterval: 100 + }).promise + + log.debug(`Donload result is ${JSON.stringify(result)}`) + + const { statusCode } = result + + if (statusCode < 200 || statusCode >= 300) { + throw new Error(`Status code: ${statusCode}`) + } + + setDownloadProgress?.(0) + + await addOfflineFileToConfiguration({ + id: file._id, + rev: remoteFile._rev, + path: destinationPath + }) + + // If the file's new Rev has a new name, we should delete old file version as file path also changed + if (existingFile && existingFile.path !== destinationPath) { + await RNFS.unlink(existingFile.path) + } + + return destinationPath + // return destinationPath + } catch (error) { + log.error('Something went wrong while downloading file', error) + throw error + } +} + +export const downloadFileAndPreview = async ( + file: FileDocument, + client: CozyClient | undefined +): Promise => { + if (!client) { + throw new Error('You must be logged in to use backup feature') + } + + const filePath = await downloadFile(file, client) + + await FileViewer.open(filePath) + + return null +} diff --git a/src/app/domain/io.cozy.files/offlineFilesConfiguration.ts b/src/app/domain/io.cozy.files/offlineFilesConfiguration.ts new file mode 100644 index 000000000..07e094c0b --- /dev/null +++ b/src/app/domain/io.cozy.files/offlineFilesConfiguration.ts @@ -0,0 +1,55 @@ +import { CozyPersistedStorageKeys, getData, storeData } from '/libs/localStore' + +export interface OfflineFile { + id: string + rev: string + path: string +} + +export type OfflineFilesConfiguration = Map +export type SerializedOfflineFilesConfiguration = [string, OfflineFile][] + +export const getOfflineFilesConfiguration = + async (): Promise => { + const filesArray = await getData( + CozyPersistedStorageKeys.OfflineFiles + ) + + const files = new Map(filesArray) + + return files + } + +export const addOfflineFileToConfiguration = async (file: OfflineFile) => { + const files = await getOfflineFilesConfiguration() + + files.set(file.id, file) + + const filesArray = Array.from(files.entries()) + + return storeData(CozyPersistedStorageKeys.OfflineFiles, filesArray) +} + +export const removeOfflineFileFromConfiguration = async ( + fileId: string +): Promise => { + const files = await getOfflineFilesConfiguration() + + files.delete(fileId) + + const filesArray = Array.from(files.entries()) + + return storeData(CozyPersistedStorageKeys.OfflineFiles, filesArray) +} + +export const getOfflineFileFromConfiguration = async ( + fileId: string +): Promise => { + const files = await getOfflineFilesConfiguration() + + if (files.has(fileId)) { + return files.get(fileId) + } + + return undefined +} diff --git a/src/app/domain/io.cozy.files/remoteFiles.ts b/src/app/domain/io.cozy.files/remoteFiles.ts new file mode 100644 index 000000000..3f03700c0 --- /dev/null +++ b/src/app/domain/io.cozy.files/remoteFiles.ts @@ -0,0 +1,31 @@ +import CozyClient, { FileCollectionGetResult, Q } from 'cozy-client' +import { FileDocument } from 'cozy-client/types/types' + +export const DOCTYPE_FILES = 'io.cozy.files' + +export const getFileById = async ( + client: CozyClient, + id: string +): Promise => { + const query = Q(DOCTYPE_FILES).getById(id) + const { data } = (await client.query(query)) as FileCollectionGetResult + + return data as unknown as FileDocument +} + +export const getDownloadUrlById = async ( + client: CozyClient, + id: string, + filename: string +): Promise => { + const fileCollection = client.collection( + DOCTYPE_FILES + ) as unknown as FileCollection + const downloadURL = await fileCollection.getDownloadLinkById(id, filename) + + return downloadURL +} + +interface FileCollection { + getDownloadLinkById: (id: string, filename: string) => Promise +} diff --git a/src/app/domain/settings/services/SettingsService.spec.ts b/src/app/domain/settings/services/SettingsService.spec.ts index c21d8fb2e..1aaad4619 100644 --- a/src/app/domain/settings/services/SettingsService.spec.ts +++ b/src/app/domain/settings/services/SettingsService.spec.ts @@ -9,6 +9,9 @@ import { ensureAutoLockIsEnabled } from '/app/domain/settings/services/SettingsS jest.mock('@react-native-cookies/cookies', () => ({ clearAll: jest.fn() })) +jest.mock('react-native-file-viewer', () => ({ + open: jest.fn() +})) describe('ensureAutoLockIsEnabled', () => { beforeEach(async () => await clearCozyData()) diff --git a/src/app/view/Secure/PinPrompt.spec.tsx b/src/app/view/Secure/PinPrompt.spec.tsx index 1ad5ab771..322de14c6 100644 --- a/src/app/view/Secure/PinPrompt.spec.tsx +++ b/src/app/view/Secure/PinPrompt.spec.tsx @@ -22,6 +22,9 @@ jest.mock('/app/view/Lock/useLockScreenWrapper', () => ({ hideSecurityScreen: jest.fn(), showSecurityScreen: jest.fn() })) +jest.mock('react-native-file-viewer', () => ({ + open: jest.fn() +})) // This is our main test suite for the PinPrompt component describe('PinPrompt', () => { diff --git a/src/hooks/useGlobalAppState.spec.ts b/src/hooks/useGlobalAppState.spec.ts index e02d69144..e7f20269c 100644 --- a/src/hooks/useGlobalAppState.spec.ts +++ b/src/hooks/useGlobalAppState.spec.ts @@ -2,6 +2,10 @@ import { Platform } from 'react-native' import { _shouldLockApp } from '/app/domain/authorization/services/SecurityService' +jest.mock('react-native-file-viewer', () => ({ + open: jest.fn() +})) + afterAll(jest.resetModules) it('should always lock the application on iOS devices when running through the autolock check, ignoring timer', () => { diff --git a/src/libs/intents/localMethods.spec.ts b/src/libs/intents/localMethods.spec.ts index f4b6d73d2..b0912d820 100644 --- a/src/libs/intents/localMethods.spec.ts +++ b/src/libs/intents/localMethods.spec.ts @@ -17,6 +17,9 @@ jest.mock('../RootNavigation') jest.mock('@react-native-cookies/cookies', () => ({ clearAll: jest.fn() })) +jest.mock('react-native-file-viewer', () => ({ + open: jest.fn() +})) describe('asyncLogout', () => { const client = { diff --git a/src/libs/intents/localMethods.ts b/src/libs/intents/localMethods.ts index 0c68a441b..9fe369690 100644 --- a/src/libs/intents/localMethods.ts +++ b/src/libs/intents/localMethods.ts @@ -2,6 +2,7 @@ import { Linking } from 'react-native' import { getDeviceName } from 'react-native-device-info' import CozyClient, { QueryDefinition } from 'cozy-client' +import type { FileDocument } from 'cozy-client/types/types' import { FlagshipUI, NativeMethodsRegisterWithOptions, @@ -60,6 +61,7 @@ import { sendGeolocationTrackingLogs, forceUploadGeolocationTrackingData } from '/app/domain/geolocation/services/tracking' +import { downloadFileAndPreview } from '/app/domain/io.cozy.files/offlineFiles' import { checkPermissions, requestPermissions @@ -401,6 +403,8 @@ export const localMethods = ( ) => Promise.resolve(setColorScheme(colorScheme)), flagshipLinkRequest: (_options, operation) => flagshipLinkRequest(operation as QueryDefinition, client), + downloadFile: (_options, file) => + downloadFileAndPreview(file as FileDocument, client), ...mergedMethods } } diff --git a/src/libs/localStore/storage.ts b/src/libs/localStore/storage.ts index d3e8dd9d4..930d5eb8b 100644 --- a/src/libs/localStore/storage.ts +++ b/src/libs/localStore/storage.ts @@ -4,6 +4,7 @@ import { BiometryType } from 'react-native-biometrics' import { logger } from '/libs/functions/logger' import type { FirstTimeserie } from '/app/domain/geolocation/helpers/quota' +import { SerializedOfflineFilesConfiguration } from '/app/domain/io.cozy.files/offlineFilesConfiguration' import type { OnboardingPartner } from '/screens/welcome/install-referrer/onboardingPartner' import { OauthData } from '/libs/clientHelpers/persistClient' @@ -36,7 +37,8 @@ export enum CozyPersistedStorageKeys { ServiceWebhookURL = 'CozyGPSMemory.ServiceWebhookURL', Oauth = '@cozy_AmiralAppOAuthConfig', Cookie = '@cozy_AmiralAppCookieConfig', - SessionCreated = '@cozy_AmiralAppSessionCreated' + SessionCreated = '@cozy_AmiralAppSessionCreated', + OfflineFiles = '@cozy_AmiralAppOfflineFiles' } /* @@ -62,6 +64,7 @@ export interface StorageItems { cookie: Cookies clouderyEnv: string logsEnabled: number + offlineFiles: SerializedOfflineFilesConfiguration } export const storeData = async ( From 9e09d1f23877ef1d5bc9bd6e1f036003408a5aaa Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 8 Aug 2024 15:23:04 +0200 Subject: [PATCH 19/92] feat: Make import files automatically available offline We want the user's important files to always be accessible offline To make this possible, we want the app to download them in background on startup We consider important files as files from `mespapiers` with specific qualification labels like passport, identification papers, vehicle registration, etc. --- src/App.js | 8 ++++ .../domain/io.cozy.files/importantFiles.ts | 44 +++++++++++++++++++ src/app/domain/io.cozy.files/remoteFiles.ts | 37 ++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/app/domain/io.cozy.files/importantFiles.ts diff --git a/src/App.js b/src/App.js index beb1f458b..f942a9413 100644 --- a/src/App.js +++ b/src/App.js @@ -59,6 +59,7 @@ import { LauncherContextProvider } from '/screens/home/hooks/useLauncherContext' import LauncherView from '/screens/konnectors/LauncherView' +import { makeImportantFilesAvailableOfflineInBackground } from '/app/domain/io.cozy.files/importantFiles' import { useShareFiles } from '/app/domain/osReceive/services/shareFilesService' import { ClouderyOffer } from '/app/view/IAP/ClouderyOffer' import { useDimensions } from '/libs/dimensions' @@ -112,6 +113,13 @@ const App = ({ setClient }) => { setLauncherContext } = useLauncherContext() + useEffect(() => { + if (!client) { + return + } + makeImportantFilesAvailableOfflineInBackground(client) + }, [client]) + if (isLoading) { return null } diff --git a/src/app/domain/io.cozy.files/importantFiles.ts b/src/app/domain/io.cozy.files/importantFiles.ts new file mode 100644 index 000000000..50742295f --- /dev/null +++ b/src/app/domain/io.cozy.files/importantFiles.ts @@ -0,0 +1,44 @@ +import CozyClient from 'cozy-client' +import Minilog from 'cozy-minilog' + +import { downloadFile } from '/app/domain/io.cozy.files/offlineFiles' +import { + getOfflineFilesConfiguration, + removeOfflineFileFromConfiguration +} from '/app/domain/io.cozy.files/offlineFilesConfiguration' +import { getImportantFiles } from '/app/domain/io.cozy.files/remoteFiles' +import { getErrorMessage } from '/libs/functions/getErrorMessage' + +const log = Minilog('📁 Offline Files') + +const IMPORTANT_FILES_DOWNLOAD_DELAY_IN_MS = 100 + +export const makeImportantFilesAvailableOfflineInBackground = ( + client: CozyClient +): Promise => { + return new Promise(resolve => { + setTimeout(() => { + makeImportantFilesAvailableOffline(client) + .then(resolve) + .catch(error => { + const errorMessage = getErrorMessage(error) + log.error( + `Something went wrong while making important files available offline: ${errorMessage}`, + resolve() + ) + }) + }, IMPORTANT_FILES_DOWNLOAD_DELAY_IN_MS) + }) +} + +const makeImportantFilesAvailableOffline = async ( + client: CozyClient +): Promise => { + log.debug('Start downloading important files for offline support') + const importantFiles = await getImportantFiles(client) + + for (const importantFile of importantFiles) { + log.debug(`Start downloading file ${importantFile._id}`) + await downloadFile(importantFile, client) + } +} diff --git a/src/app/domain/io.cozy.files/remoteFiles.ts b/src/app/domain/io.cozy.files/remoteFiles.ts index 3f03700c0..ab325cc4c 100644 --- a/src/app/domain/io.cozy.files/remoteFiles.ts +++ b/src/app/domain/io.cozy.files/remoteFiles.ts @@ -3,6 +3,18 @@ import { FileDocument } from 'cozy-client/types/types' export const DOCTYPE_FILES = 'io.cozy.files' +const IMPORTANT_PAPERS = [ + 'bank_details', + 'driver_license', + 'identity_photo', + 'national_health_insurance_card', + 'national_id_card', + 'passport', + 'residence_permit', + 'resume', + 'vehicle_registration' +] + export const getFileById = async ( client: CozyClient, id: string @@ -26,6 +38,31 @@ export const getDownloadUrlById = async ( return downloadURL } +export const getImportantFiles = async ( + client: CozyClient +): Promise => { + const query = Q(DOCTYPE_FILES) + .where({ + created_at: { + $gt: null + }, + 'metadata.qualification.label': { + $in: IMPORTANT_PAPERS + } + }) + .partialIndex({ + type: 'file', + trashed: false, + 'cozyMetadata.createdByApp': { $exists: true } + }) + .indexFields(['created_at', 'metadata.qualification.label']) + .sortBy([{ created_at: 'desc' }]) + + const { data } = (await client.query(query)) as FileCollectionGetResult + + return data as unknown as FileDocument[] +} + interface FileCollection { getDownloadLinkById: (id: string, filename: string) => Promise } From 9992198c1a520c1f178be84bf115df5c28f257c3 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 8 Aug 2024 15:25:51 +0200 Subject: [PATCH 20/92] feat: Clean non-important offline files when too old As we store offline accessible files in the device's storage, we want to prevent them to fill the entire device's storage We chose to keep only files that were accessed in the previous month. All other files would be deleted Important files are never deleted as long as they exist on the Cozy instance --- .../domain/io.cozy.files/importantFiles.ts | 44 +++++++++++++++++++ .../domain/io.cozy.files/offlineFiles.spec.ts | 27 +++++++++++- src/app/domain/io.cozy.files/offlineFiles.ts | 4 +- .../offlineFilesConfiguration.ts | 26 +++++++++-- 4 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/app/domain/io.cozy.files/importantFiles.ts b/src/app/domain/io.cozy.files/importantFiles.ts index 50742295f..3a1d0d0f1 100644 --- a/src/app/domain/io.cozy.files/importantFiles.ts +++ b/src/app/domain/io.cozy.files/importantFiles.ts @@ -1,4 +1,8 @@ +import { differenceInMonths } from 'date-fns' +import RNFS from 'react-native-fs' + import CozyClient from 'cozy-client' +import type { FileDocument } from 'cozy-client/types/types' import Minilog from 'cozy-minilog' import { downloadFile } from '/app/domain/io.cozy.files/offlineFiles' @@ -12,6 +16,7 @@ import { getErrorMessage } from '/libs/functions/getErrorMessage' const log = Minilog('📁 Offline Files') const IMPORTANT_FILES_DOWNLOAD_DELAY_IN_MS = 100 +const NB_OF_MONTH_BEFORE_EXPIRATION = 1 export const makeImportantFilesAvailableOfflineInBackground = ( client: CozyClient @@ -37,8 +42,47 @@ const makeImportantFilesAvailableOffline = async ( log.debug('Start downloading important files for offline support') const importantFiles = await getImportantFiles(client) + await cleanOldNonImportantFiles(importantFiles) + for (const importantFile of importantFiles) { log.debug(`Start downloading file ${importantFile._id}`) await downloadFile(importantFile, client) } } + +const cleanOldNonImportantFiles = async ( + importantFiles: FileDocument[] +): Promise => { + try { + const offlineFiles = await getOfflineFilesConfiguration() + const offlineFilesMap = Array.from(offlineFiles) + + const importantFilesIds = importantFiles.map(file => file._id) + + const now = new Date() + + for (const [, offlineFile] of offlineFilesMap) { + const lastOpened = offlineFile.lastOpened + if ( + !importantFilesIds.includes(offlineFile.id) && + (differenceInMonths(lastOpened, now) > NB_OF_MONTH_BEFORE_EXPIRATION || + !lastOpened) + ) { + log.debug( + `Remove old unimportant file ${ + offlineFile.id + } (last opened on ${lastOpened.toString()})` + ) + if (await RNFS.exists(offlineFile.path)) { + await RNFS.unlink(offlineFile.path) + } + await removeOfflineFileFromConfiguration(offlineFile.id) + } + } + } catch (error) { + const errorMessage = getErrorMessage(error) + log.error( + `Something went wrong while cleaning non-important files: ${errorMessage}` + ) + } +} diff --git a/src/app/domain/io.cozy.files/offlineFiles.spec.ts b/src/app/domain/io.cozy.files/offlineFiles.spec.ts index 5c6e750e1..69863ad01 100644 --- a/src/app/domain/io.cozy.files/offlineFiles.spec.ts +++ b/src/app/domain/io.cozy.files/offlineFiles.spec.ts @@ -10,7 +10,8 @@ import { import { addOfflineFileToConfiguration, getOfflineFileFromConfiguration, - OfflineFile + OfflineFile, + updateLastOpened } from '/app/domain/io.cozy.files/offlineFilesConfiguration' import { getDownloadUrlById, @@ -22,7 +23,8 @@ jest.mock('react-native-file-viewer', () => ({ })) jest.mock('/app/domain/io.cozy.files/offlineFilesConfiguration', () => ({ addOfflineFileToConfiguration: jest.fn(), - getOfflineFileFromConfiguration: jest.fn() + getOfflineFileFromConfiguration: jest.fn(), + updateLastOpened: jest.fn() })) jest.mock('/app/domain/io.cozy.files/remoteFiles', () => ({ getFileById: jest.fn(), @@ -141,6 +143,27 @@ describe('offlineFiles', () => { expect(addOfflineFileToConfiguration).not.toHaveBeenCalled() }) + it('should update lastOpened attribute if file exists', async () => { + mockRemoteFile({ + _id: 'SOME_FILE_ID', + _rev: 'SOME_REV', + name: 'SOME_FILE_NAME' + }) + mockFileFromConfiguration({ + id: 'SOME_FILE_ID', + rev: 'SOME_REV', + path: 'SOME_EXISTING_PATH_FROM_CACHE', + lastOpened: new Date() + }) + const file = { + _id: 'SOME_FILE_ID', + name: 'SOME_FILE_NAME' + } as unknown as FileDocument + await downloadFile(file, mockClient) + + expect(updateLastOpened).toHaveBeenCalledWith('SOME_FILE_ID') + }) + it('should download file if existing but with different rev', async () => { mockRemoteFile({ _id: 'SOME_FILE_ID', diff --git a/src/app/domain/io.cozy.files/offlineFiles.ts b/src/app/domain/io.cozy.files/offlineFiles.ts index 66afe5a22..2c33ae58b 100644 --- a/src/app/domain/io.cozy.files/offlineFiles.ts +++ b/src/app/domain/io.cozy.files/offlineFiles.ts @@ -7,7 +7,8 @@ import Minilog from 'cozy-minilog' import { addOfflineFileToConfiguration, - getOfflineFileFromConfiguration + getOfflineFileFromConfiguration, + updateLastOpened } from '/app/domain/io.cozy.files/offlineFilesConfiguration' import { getDownloadUrlById, @@ -50,6 +51,7 @@ export const downloadFile = async ( if (existingFile && existingFile.rev === remoteFile._rev) { log.debug(`File ${file._id} already exist, returning existing one`) + await updateLastOpened(file._id) return existingFile.path } diff --git a/src/app/domain/io.cozy.files/offlineFilesConfiguration.ts b/src/app/domain/io.cozy.files/offlineFilesConfiguration.ts index 07e094c0b..f6ba26bb5 100644 --- a/src/app/domain/io.cozy.files/offlineFilesConfiguration.ts +++ b/src/app/domain/io.cozy.files/offlineFilesConfiguration.ts @@ -4,6 +4,7 @@ export interface OfflineFile { id: string rev: string path: string + lastOpened: Date } export type OfflineFilesConfiguration = Map @@ -20,11 +21,16 @@ export const getOfflineFilesConfiguration = return files } -export const addOfflineFileToConfiguration = async (file: OfflineFile) => { +export const addOfflineFileToConfiguration = async ( + file: Omit +): Promise => { const files = await getOfflineFilesConfiguration() - files.set(file.id, file) - + files.set(file.id, { + ...file, + lastOpened: new Date() + }) + const filesArray = Array.from(files.entries()) return storeData(CozyPersistedStorageKeys.OfflineFiles, filesArray) @@ -53,3 +59,17 @@ export const getOfflineFileFromConfiguration = async ( return undefined } + +export const updateLastOpened = async (fileId: string): Promise => { + const file = await getOfflineFileFromConfiguration(fileId) + + if (!file) { + throw new Error( + `Cannot update 'lastOpened' attribute for not existing file ${fileId}` + ) + } + + file.lastOpened = new Date() + + await addOfflineFileToConfiguration(file) +} From aab377354c0e55dd953c76252a01532b94219df5 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 22 Aug 2024 16:13:29 +0200 Subject: [PATCH 21/92] feat: Use NetStatusBoundary only when logged out `NetStatusBoundary` is used to display an `offline` error screen when the app is offline Now that we implemented offline mode, we now want to disable the `NetStatusBoundary` for cozy-apps as they should now be accessible when offline `NetStatusBoundary` is kept only on onboarding screens --- src/App.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/App.js b/src/App.js index f942a9413..72f302fc9 100644 --- a/src/App.js +++ b/src/App.js @@ -225,7 +225,13 @@ const WrappedApp = () => { [client] ) - if (client === null) return