diff --git a/.prettierignore b/.prettierignore index 6fd05c848..6b4f155a0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,14 @@ +# all build coverage node_modules dist -native/ -public/an.js \ No newline at end of file + +# mobile +apps/mobile/native/ + +# web +apps/web/public/an.js + +# editor +packages/editor/styles/ \ No newline at end of file diff --git a/apps/mobile/.eslintrc.js b/apps/mobile/.eslintrc.js index 831ede52e..c6a70e997 100644 --- a/apps/mobile/.eslintrc.js +++ b/apps/mobile/.eslintrc.js @@ -1,16 +1,16 @@ module.exports = { - parser: '@typescript-eslint/parser', + parser: "@typescript-eslint/parser", env: { browser: true, es2021: true, - 'react-native/react-native': true + "react-native/react-native": true }, extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:prettier/recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended' + "eslint:recommended", + "plugin:react/recommended", + "plugin:prettier/recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" ], parserOptions: { ecmaFeatures: { @@ -18,22 +18,31 @@ module.exports = { }, ecmaVersion: 12, es6: true, - sourceType: 'module' + sourceType: "module" }, - plugins: ['react', 'react-native', 'prettier', 'unused-imports', '@typescript-eslint'], + plugins: [ + "react", + "react-native", + "prettier", + "unused-imports", + "@typescript-eslint" + ], rules: { - 'react/display-name': 0, - 'no-unused-vars': 'off', - 'react/no-unescaped-entities': 'off', - 'unused-imports/no-unused-vars': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/no-unused-vars': 'off', - 'prefer-const': 'off', - 'no-empty': 'off', - 'react/prop-types': 0, - 'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx', 'ts', 'tsx'] }], - 'prettier/prettier': [ - 'error', + "react/display-name": 0, + "no-unused-vars": "off", + "react/no-unescaped-entities": "off", + "unused-imports/no-unused-vars": "off", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-unused-vars": "off", + "prefer-const": "off", + "no-empty": "off", + "react/prop-types": 0, + "react/jsx-filename-extension": [ + 1, + { extensions: [".js", ".jsx", "ts", "tsx"] } + ], + "prettier/prettier": [ + "error", {}, { usePrettierrc: true diff --git a/apps/mobile/.github/workflows/android-public-release.yml b/apps/mobile/.github/workflows/android-public-release.yml index 4cc17217c..de27c8485 100644 --- a/apps/mobile/.github/workflows/android-public-release.yml +++ b/apps/mobile/.github/workflows/android-public-release.yml @@ -28,13 +28,13 @@ jobs: - uses: actions/setup-node@master with: - node-version: '16' + node-version: "16" - name: Use specific Java version for the builds uses: joschi/setup-jdk@v2 with: - java-version: '11' - architecture: 'x64' + java-version: "11" + architecture: "x64" - name: Install node modules run: | diff --git a/apps/mobile/.github/workflows/android-release.yml b/apps/mobile/.github/workflows/android-release.yml index 8946e4211..a4057d7b3 100644 --- a/apps/mobile/.github/workflows/android-release.yml +++ b/apps/mobile/.github/workflows/android-release.yml @@ -28,13 +28,13 @@ jobs: - uses: actions/setup-node@master with: - node-version: '16' + node-version: "16" - name: Use specific Java version for the builds uses: joschi/setup-jdk@v2 with: - java-version: '11' - architecture: 'x64' + java-version: "11" + architecture: "x64" - name: Install node modules run: | diff --git a/apps/mobile/.prettierrc.js b/apps/mobile/.prettierrc.js deleted file mode 100644 index 4a66763d4..000000000 --- a/apps/mobile/.prettierrc.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - arrowParens: 'avoid', - bracketSpacing: true, - jsxBracketSameLine: false, - jsxSingleQuote: false, - quoteProps: 'as-needed', - singleQuote: true, - semi: true, - printWidth: 100, - useTabs: false, - tabWidth: 2, - trailingComma: 'none' -}; diff --git a/apps/mobile/.vscode/launch.json b/apps/mobile/.vscode/launch.json index 7a623a22a..1a48c7f0c 100644 --- a/apps/mobile/.vscode/launch.json +++ b/apps/mobile/.vscode/launch.json @@ -4,8 +4,6 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - - { "name": "Debug Android", "cwd": "${workspaceFolder}", @@ -27,4 +25,4 @@ "request": "attach" } ] -} \ No newline at end of file +} diff --git a/apps/mobile/.watchmanconfig b/apps/mobile/.watchmanconfig index 9e26dfeeb..0967ef424 100644 --- a/apps/mobile/.watchmanconfig +++ b/apps/mobile/.watchmanconfig @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/apps/mobile/__tests__/App-test.js b/apps/mobile/__tests__/App-test.js index a98d92c8a..8fd0553bf 100644 --- a/apps/mobile/__tests__/App-test.js +++ b/apps/mobile/__tests__/App-test.js @@ -2,21 +2,21 @@ * @format */ -import React from 'react'; -import 'react-native'; +import React from "react"; +import "react-native"; // Note: test renderer must be required after react-native. -import renderer from 'react-test-renderer'; -import Heading from '../app/components/ui/typography/heading'; -import Paragraph from '../app/components/ui/typography/paragraph'; +import renderer from "react-test-renderer"; +import Heading from "../app/components/ui/typography/heading"; +import Paragraph from "../app/components/ui/typography/paragraph"; -it('Heading renders correctly', done => { +it("Heading renders correctly", (done) => { let instance = renderer.create(Heading); - expect(instance.root.props.children).toBe('Heading'); + expect(instance.root.props.children).toBe("Heading"); done(); }); -it('Paragraph renders correctly', done => { +it("Paragraph renders correctly", (done) => { let instance = renderer.create(Paragraph); - expect(instance.root.props.children).toBe('Paragraph'); + expect(instance.root.props.children).toBe("Paragraph"); done(); }); diff --git a/apps/mobile/app/app.js b/apps/mobile/app/app.js index 9bed15ffc..0e90d6544 100644 --- a/apps/mobile/app/app.js +++ b/apps/mobile/app/app.js @@ -1,14 +1,14 @@ -import React, { useEffect } from 'react'; -import { GestureHandlerRootView } from 'react-native-gesture-handler'; -import { SafeAreaProvider } from 'react-native-safe-area-context'; -import { withErrorBoundry } from './components/exception-handler'; -import Launcher from './components/launcher'; -import { ApplicationHolder } from './navigation'; -import Notifications from './services/notifications'; -import SettingsService from './services/settings'; -import { TipManager } from './services/tip-manager'; -import { useUserStore } from './stores/use-user-store'; -import { useAppEvents } from './hooks/use-app-events'; +import React, { useEffect } from "react"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { SafeAreaProvider } from "react-native-safe-area-context"; +import { withErrorBoundry } from "./components/exception-handler"; +import Launcher from "./components/launcher"; +import { ApplicationHolder } from "./navigation"; +import Notifications from "./services/notifications"; +import SettingsService from "./services/settings"; +import { TipManager } from "./services/tip-manager"; +import { useUserStore } from "./stores/use-user-store"; +import { useAppEvents } from "./hooks/use-app-events"; SettingsService.init(); SettingsService.checkOrientation(); @@ -16,7 +16,7 @@ const App = () => { useAppEvents(); useEffect(() => { let { appLockMode } = SettingsService.get(); - if (appLockMode && appLockMode !== 'none') { + if (appLockMode && appLockMode !== "none") { useUserStore.getState().setVerifyUser(true); } setTimeout(() => { @@ -39,4 +39,4 @@ const App = () => { ); }; -export default withErrorBoundry(App, 'App'); +export default withErrorBoundry(App, "App"); diff --git a/apps/mobile/app/assets/images/assets.js b/apps/mobile/app/assets/images/assets.js index f78210ad6..0a66eec9b 100644 --- a/apps/mobile/app/assets/images/assets.js +++ b/apps/mobile/app/assets/images/assets.js @@ -1,12 +1,12 @@ -export const LAUNCH_ROCKET = color => +export const LAUNCH_ROCKET = (color) => ``; -export const COMMUNITY_SVG = color => ` +export const COMMUNITY_SVG = (color) => ` `; export const SUPPORT_SVG = () => ``; -export const WELCOME_SVG = color => +export const WELCOME_SVG = (color) => ``; diff --git a/apps/mobile/app/common/analytics/index.js b/apps/mobile/app/common/analytics/index.js index 7fa40c8c2..e908ad7ee 100644 --- a/apps/mobile/app/common/analytics/index.js +++ b/apps/mobile/app/common/analytics/index.js @@ -1,12 +1,12 @@ -import { Platform } from 'react-native'; -import { MMKV } from '../database/mmkv'; -import { useSettingStore } from '../../stores/use-setting-store'; +import { Platform } from "react-native"; +import { MMKV } from "../database/mmkv"; +import { useSettingStore } from "../../stores/use-setting-store"; const WEBSITE_ID = `3c6890ce-8410-49d5-8831-15fb2eb28a21`; const baseUrl = `https://analytics.streetwriters.co/api/collect`; const UA = - Platform.OS === 'ios' + Platform.OS === "ios" ? `Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1` : ` Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36`; @@ -19,37 +19,37 @@ Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHT */ async function canUpdateAnalytics(route, conditions = []) { if (!useSettingStore?.getState()?.settings?.telemetry) return false; - let eventsList = MMKV.getString('notesnookUserEvents'); + let eventsList = MMKV.getString("notesnookUserEvents"); if (eventsList) { eventsList = JSON.parse(eventsList); } if (eventsList && eventsList[route]) { - console.log('analytics: event already sent', route); + console.log("analytics: event already sent", route); return false; } - if (route !== '/welcome') { + if (route !== "/welcome") { for (let cond of conditions) { if (!eventsList || !eventsList[cond]) { - console.log('analytics: conditions not met for event', route, cond); + console.log("analytics: conditions not met for event", route, cond); return false; } } } - console.log('analytics: will send event', route); + console.log("analytics: will send event", route); return true; } async function saveAnalytics(route, value = true) { - let eventsList = MMKV.getString('notesnookUserEvents'); + let eventsList = MMKV.getString("notesnookUserEvents"); if (eventsList) { eventsList = JSON.parse(eventsList); } else { eventsList = {}; } eventsList[route] = value; - MMKV.setString('notesnookUserEvents', JSON.stringify(eventsList)); + MMKV.setString("notesnookUserEvents", JSON.stringify(eventsList)); } /** @@ -61,7 +61,12 @@ async function saveAnalytics(route, value = true) { * @returns */ -async function pageView(route, prevRoute = '', conditions = ['/welcome'], once = true) { +async function pageView( + route, + prevRoute = "", + conditions = ["/welcome"], + once = true +) { if (__DEV__) return; if (!(await canUpdateAnalytics(route, conditions)) && once) return; let body = { @@ -70,22 +75,22 @@ async function pageView(route, prevRoute = '', conditions = ['/welcome'], once = url: `notesnook-${Platform.OS}${prevRoute}${route}`, referrer: `https://notesnook.com/notesnook-${Platform.OS}${prevRoute}`, hostname: `notesnook-${Platform.OS}`, - language: 'en-US', - screen: '1920x1080' + language: "en-US", + screen: "1920x1080" }, - type: 'pageview' + type: "pageview" }; try { let response = await fetch(baseUrl, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'User-Agent': UA + "Content-Type": "application/json", + "User-Agent": UA }, body: JSON.stringify(body) }); - console.log('analytics: event sent', route); + console.log("analytics: event sent", route); await saveAnalytics(route); return await response.text(); } catch (e) { @@ -99,22 +104,22 @@ async function sendEvent(type, value, once = true) { let body = { payload: { website: WEBSITE_ID, - url: '/', + url: "/", event_type: type, event_value: value, - hostname: 'notesnook-android-app', - language: 'en-US', - screen: '1920x1080' + hostname: "notesnook-android-app", + language: "en-US", + screen: "1920x1080" }, - type: 'event' + type: "event" }; try { let response = await fetch(baseUrl, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'User-Agent': UA + "Content-Type": "application/json", + "User-Agent": UA }, body: JSON.stringify(body) }); diff --git a/apps/mobile/app/common/database/encryption.js b/apps/mobile/app/common/database/encryption.js index 9f8e7bf92..f93c64b9b 100644 --- a/apps/mobile/app/common/database/encryption.js +++ b/apps/mobile/app/common/database/encryption.js @@ -1,8 +1,8 @@ -import { Platform } from 'react-native'; -import 'react-native-get-random-values'; -import * as Keychain from 'react-native-keychain'; -import { generateSecureRandom } from 'react-native-securerandom'; -import Sodium from 'react-native-sodium'; +import { Platform } from "react-native"; +import "react-native-get-random-values"; +import * as Keychain from "react-native-keychain"; +import { generateSecureRandom } from "react-native-securerandom"; +import Sodium from "react-native-sodium"; const KEYSTORE_CONFIG = Platform.select({ ios: { @@ -14,15 +14,23 @@ const KEYSTORE_CONFIG = Platform.select({ export async function deriveCryptoKey(name, data) { try { let credentials = await Sodium.deriveKey(data.password, data.salt); - await Keychain.setInternetCredentials('notesnook', name, credentials.key, KEYSTORE_CONFIG); + await Keychain.setInternetCredentials( + "notesnook", + name, + credentials.key, + KEYSTORE_CONFIG + ); return credentials.key; } catch (e) {} } export async function getCryptoKey(name) { try { - if (await Keychain.hasInternetCredentials('notesnook')) { - let credentials = await Keychain.getInternetCredentials('notesnook', KEYSTORE_CONFIG); + if (await Keychain.hasInternetCredentials("notesnook")) { + let credentials = await Keychain.getInternetCredentials( + "notesnook", + KEYSTORE_CONFIG + ); return credentials.password; } else { return null; @@ -32,7 +40,7 @@ export async function getCryptoKey(name) { export async function removeCryptoKey(name) { try { - let result = await Keychain.resetInternetCredentials('notesnook'); + let result = await Keychain.resetInternetCredentials("notesnook"); return result; } catch (e) {} } @@ -51,7 +59,7 @@ export async function generateCryptoKey(password, salt) { let credentials = await Sodium.deriveKey(password, salt || null); return credentials; } catch (e) { - console.log('generateCryptoKey: ', e); + console.log("generateCryptoKey: ", e); } } @@ -61,30 +69,32 @@ export function getAlgorithm(base64Variant) { export async function decrypt(password, data) { if (!password.password && !password.key) return undefined; - if (password.password && password.password === '' && !password.key) return undefined; + if (password.password && password.password === "" && !password.key) + return undefined; let _data = { ...data }; - _data.output = 'plain'; + _data.output = "plain"; return await Sodium.decrypt(password, _data); } export function parseAlgorithm(alg) { if (!alg) return {}; - const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split('-'); + const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-"); return { encryptionAlgorithm: enc, kdfAlgorithm: kdf, compressionAlgorithm: compressionAlg, - isCompress: compressed === '1', + isCompress: compressed === "1", base64_variant: base64variant }; } export async function encrypt(password, data) { if (!password.password && !password.key) return undefined; - if (password.password && password.password === '' && !password.key) return undefined; + if (password.password && password.password === "" && !password.key) + return undefined; let message = { - type: 'plain', + type: "plain", data: data }; let result = await Sodium.encrypt(password, message); diff --git a/apps/mobile/app/common/database/index.js b/apps/mobile/app/common/database/index.js index 3a426d612..fbd6c8db7 100644 --- a/apps/mobile/app/common/database/index.js +++ b/apps/mobile/app/common/database/index.js @@ -1,12 +1,17 @@ -import Database from '@streetwriters/notesnook-core/api/index'; -import { initalize, logger as dbLogger } from '@streetwriters/notesnook-core/logger'; -import { Platform } from 'react-native'; -import { MMKVLoader } from 'react-native-mmkv-storage'; -import filesystem from '../filesystem'; -import EventSource from '../../utils/sse/even-source-ios'; -import AndroidEventSource from '../../utils/sse/event-source'; -import Storage, { KV } from './storage'; -const LoggerStorage = new MMKVLoader().withInstanceID('notesnook_logs').initialize(); +import Database from "@streetwriters/notesnook-core/api/index"; +import { + initalize, + logger as dbLogger +} from "@streetwriters/notesnook-core/logger"; +import { Platform } from "react-native"; +import { MMKVLoader } from "react-native-mmkv-storage"; +import filesystem from "../filesystem"; +import EventSource from "../../utils/sse/even-source-ios"; +import AndroidEventSource from "../../utils/sse/event-source"; +import Storage, { KV } from "./storage"; +const LoggerStorage = new MMKVLoader() + .withInstanceID("notesnook_logs") + .initialize(); console.log(LoggerStorage); initalize(new KV(LoggerStorage)); export const DatabaseLogger = dbLogger; @@ -16,18 +21,18 @@ export const DatabaseLogger = dbLogger; */ export var db = new Database( Storage, - Platform.OS === 'ios' ? EventSource : AndroidEventSource, + Platform.OS === "ios" ? EventSource : AndroidEventSource, filesystem ); db.host( __DEV__ ? { - API_HOST: 'https://api.notesnook.com', - AUTH_HOST: 'https://auth.streetwriters.co', - SSE_HOST: 'https://events.streetwriters.co', - SUBSCRIPTIONS_HOST: 'https://subscriptions.streetwriters.co', - ISSUES_HOST: 'https://issues.streetwriters.co' + API_HOST: "https://api.notesnook.com", + AUTH_HOST: "https://auth.streetwriters.co", + SSE_HOST: "https://events.streetwriters.co", + SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co", + ISSUES_HOST: "https://issues.streetwriters.co" // API_HOST: 'http://192.168.10.29:5264', // AUTH_HOST: 'http://192.168.10.29:8264', // SSE_HOST: 'http://192.168.10.29:7264', @@ -35,11 +40,11 @@ db.host( // ISSUES_HOST: 'http://192.168.10.29:2624' } : { - API_HOST: 'https://api.notesnook.com', - AUTH_HOST: 'https://auth.streetwriters.co', - SSE_HOST: 'https://events.streetwriters.co', - SUBSCRIPTIONS_HOST: 'https://subscriptions.streetwriters.co', - ISSUES_HOST: 'https://issues.streetwriters.co' + API_HOST: "https://api.notesnook.com", + AUTH_HOST: "https://auth.streetwriters.co", + SSE_HOST: "https://events.streetwriters.co", + SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co", + ISSUES_HOST: "https://issues.streetwriters.co" } ); diff --git a/apps/mobile/app/common/database/mmkv.js b/apps/mobile/app/common/database/mmkv.js index 5b3b4a933..347cd765a 100644 --- a/apps/mobile/app/common/database/mmkv.js +++ b/apps/mobile/app/common/database/mmkv.js @@ -1,8 +1,10 @@ -import { Platform } from 'react-native'; -import MMKVStorage, { ProcessingModes } from 'react-native-mmkv-storage'; +import { Platform } from "react-native"; +import MMKVStorage, { ProcessingModes } from "react-native-mmkv-storage"; export const MMKV = new MMKVStorage.Loader() .setProcessingMode( - Platform.OS === 'ios' ? ProcessingModes.MULTI_PROCESS : ProcessingModes.SINGLE_PROCESS + Platform.OS === "ios" + ? ProcessingModes.MULTI_PROCESS + : ProcessingModes.SINGLE_PROCESS ) .initialize(); diff --git a/apps/mobile/app/common/database/storage.js b/apps/mobile/app/common/database/storage.js index a73d5b1a6..97c114910 100644 --- a/apps/mobile/app/common/database/storage.js +++ b/apps/mobile/app/common/database/storage.js @@ -1,5 +1,5 @@ -import { Platform } from 'react-native'; -import RNFetchBlob from 'rn-fetch-blob'; +import { Platform } from "react-native"; +import RNFetchBlob from "rn-fetch-blob"; import { decrypt, deriveCryptoKey, @@ -9,8 +9,8 @@ import { getRandomBytes, hash, removeCryptoKey -} from './encryption'; -import { MMKV } from './mmkv'; +} from "./encryption"; +import { MMKV } from "./mmkv"; export class KV { storage = null; @@ -30,7 +30,10 @@ export class KV { } async write(key, data) { - this.storage.setString(key, typeof data === 'string' ? data : JSON.stringify(data)); + this.storage.setString( + key, + typeof data === "string" ? data : JSON.stringify(data) + ); return true; } @@ -64,12 +67,12 @@ export class KV { async getAllKeys() { let keys = (await this.storage.indexer.getKeys()) || []; keys = keys.filter( - k => - k !== 'stringIndex' && - k !== 'boolIndex' && - k !== 'mapIndex' && - k !== 'arrayIndex' && - k !== 'numberIndex' && + (k) => + k !== "stringIndex" && + k !== "boolIndex" && + k !== "mapIndex" && + k !== "arrayIndex" && + k !== "numberIndex" && k !== this.storage.instanceID ); return keys; @@ -79,14 +82,14 @@ export class KV { const DefaultStorage = new KV(MMKV); async function requestPermission() { - if (Platform.OS === 'ios') return true; + if (Platform.OS === "ios") return true; return true; } async function checkAndCreateDir(path) { let dir = - Platform.OS === 'ios' + Platform.OS === "ios" ? RNFetchBlob.fs.dirs.DocumentDir + path - : RNFetchBlob.fs.dirs.SDCardDir + '/Notesnook/' + path; + : RNFetchBlob.fs.dirs.SDCardDir + "/Notesnook/" + path; try { let exists = await RNFetchBlob.fs.exists(dir); @@ -102,10 +105,10 @@ async function checkAndCreateDir(path) { } export default { - read: key => DefaultStorage.read(key), + read: (key) => DefaultStorage.read(key), write: (key, value) => DefaultStorage.write(key, value), - readMulti: keys => DefaultStorage.readMulti(keys), - remove: key => DefaultStorage.remove(key), + readMulti: (keys) => DefaultStorage.readMulti(keys), + remove: (key) => DefaultStorage.remove(key), clear: () => DefaultStorage.clear(), getAllKeys: () => DefaultStorage.getAllKeys(), encrypt, diff --git a/apps/mobile/app/common/filesystem/download.js b/apps/mobile/app/common/filesystem/download.js index 211516c0d..b441e54b6 100644 --- a/apps/mobile/app/common/filesystem/download.js +++ b/apps/mobile/app/common/filesystem/download.js @@ -1,48 +1,51 @@ -import React from 'react'; -import { Platform } from 'react-native'; -import * as ScopedStorage from 'react-native-scoped-storage'; -import Sodium from 'react-native-sodium'; -import RNFetchBlob from 'rn-fetch-blob'; -import { ShareComponent } from '../../components/sheets/export-notes/share'; -import { useAttachmentStore } from '../../stores/use-attachment-store'; -import { presentSheet, ToastEvent } from '../../services/event-manager'; -import { db } from '../database'; -import Storage from '../database/storage'; -import { cacheDir, fileCheck } from './utils'; -import hosts from '@streetwriters/notesnook-core/utils/constants'; -import NetInfo from '@react-native-community/netinfo'; +import React from "react"; +import { Platform } from "react-native"; +import * as ScopedStorage from "react-native-scoped-storage"; +import Sodium from "react-native-sodium"; +import RNFetchBlob from "rn-fetch-blob"; +import { ShareComponent } from "../../components/sheets/export-notes/share"; +import { useAttachmentStore } from "../../stores/use-attachment-store"; +import { presentSheet, ToastEvent } from "../../services/event-manager"; +import { db } from "../database"; +import Storage from "../database/storage"; +import { cacheDir, fileCheck } from "./utils"; +import hosts from "@streetwriters/notesnook-core/utils/constants"; +import NetInfo from "@react-native-community/netinfo"; export async function downloadFile(filename, data, cancelToken) { if (!data) return false; let { url, headers } = data; - console.log('downloading file: ', filename, url); + console.log("downloading file: ", filename, url); let path = `${cacheDir}/${filename}`; try { let exists = await RNFetchBlob.fs.exists(path); if (exists) { - console.log('file is downloaded'); + console.log("file is downloaded"); return true; } let res = await fetch(url, { - method: 'GET', + method: "GET", headers }); - if (!res.ok) throw new Error(`${res.status}: Unable to resolve download url`); + if (!res.ok) + throw new Error(`${res.status}: Unable to resolve download url`); const downloadUrl = await res.text(); - if (!downloadUrl) throw new Error('Unable to resolve download url'); + if (!downloadUrl) throw new Error("Unable to resolve download url"); let totalSize = 0; let request = RNFetchBlob.config({ path: path, IOSBackgroundTask: true }) - .fetch('GET', downloadUrl, null) + .fetch("GET", downloadUrl, null) .progress((recieved, total) => { - useAttachmentStore.getState().setProgress(0, total, filename, recieved, 'download'); + useAttachmentStore + .getState() + .setProgress(0, total, filename, recieved, "download"); totalSize = total; - console.log('downloading: ', recieved, total); + console.log("downloading: ", recieved, total); }); cancelToken.cancel = request.cancel; @@ -53,21 +56,21 @@ export async function downloadFile(filename, data, cancelToken) { return status >= 200 && status < 300; } catch (e) { ToastEvent.show({ - heading: 'Error downloading file', + heading: "Error downloading file", message: e.message, - type: 'error', - context: 'global' + type: "error", + context: "global" }); ToastEvent.show({ - heading: 'Error downloading file', + heading: "Error downloading file", message: e.message, - type: 'error', - context: 'local' + type: "error", + context: "local" }); useAttachmentStore.getState().remove(filename); RNFetchBlob.fs.unlink(path).catch(console.log); - console.log('download file error: ', e, url, headers); + console.log("download file error: ", e, url, headers); return false; } } @@ -75,24 +78,30 @@ export async function downloadFile(filename, data, cancelToken) { export async function downloadAttachment(hash, global = true) { let attachment = db.attachments.attachment(hash); if (!attachment) { - console.log('attachment not found'); + console.log("attachment not found"); return; } let folder = {}; - if (Platform.OS === 'android') { + if (Platform.OS === "android") { folder = await ScopedStorage.openDocumentTree(); if (!folder) return; } else { - folder.uri = await Storage.checkAndCreateDir('/downloads/'); + folder.uri = await Storage.checkAndCreateDir("/downloads/"); } try { - await db.fs.downloadFile(attachment.metadata.hash, attachment.metadata.hash); - if (!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))) return; + await db.fs.downloadFile( + attachment.metadata.hash, + attachment.metadata.hash + ); + if ( + !(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`)) + ) + return; let key = await db.attachments.decryptKey(attachment.key); - console.log('attachment key', key); + console.log("attachment key", key); let info = { iv: attachment.iv, salt: attachment.salt, @@ -108,37 +117,48 @@ export async function downloadAttachment(hash, global = true) { let fileUri = await Sodium.decryptFile(key, info, false); ToastEvent.show({ - heading: 'Download successful', - message: attachment.metadata.filename + ' downloaded', - type: 'success' + heading: "Download successful", + message: attachment.metadata.filename + " downloaded", + type: "success" }); if (attachment.dateUploaded) { - console.log('Deleting attachment after download', attachment.dateUploaded); + console.log( + "Deleting attachment after download", + attachment.dateUploaded + ); RNFetchBlob.fs .unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`) .catch(console.log); } - if (Platform.OS === 'ios') { + if (Platform.OS === "ios") { fileUri = folder.uri + `/${attachment.metadata.filename}`; } - console.log('saved file uri: ', fileUri); + console.log("saved file uri: ", fileUri); presentSheet({ title: `File downloaded`, paragraph: `${attachment.metadata.filename} saved to ${ - Platform.OS === 'android' ? 'selected path' : 'File Manager/Notesnook/downloads' + Platform.OS === "android" + ? "selected path" + : "File Manager/Notesnook/downloads" }`, - icon: 'download', + icon: "download", context: global ? null : attachment.metadata.hash, - component: + component: ( + + ) }); return fileUri; } catch (e) { - console.log('download attachment error: ', e); + console.log("download attachment error: ", e); if (attachment.dateUploaded) { - console.log('Deleting attachment on error', attachment.dateUploaded); + console.log("Deleting attachment on error", attachment.dateUploaded); RNFetchBlob.fs .unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`) .catch(console.log); @@ -152,26 +172,27 @@ export async function getUploadedFileSize(hash) { const token = await db.user.tokenManager.getAccessToken(); const attachmentInfo = await fetch(url, { - method: 'HEAD', + method: "HEAD", headers: { Authorization: `Bearer ${token}` } }); - const contentLength = parseInt(attachmentInfo.headers?.get('content-length')); - console.log('contentLength:', contentLength, attachmentInfo.headers); + const contentLength = parseInt(attachmentInfo.headers?.get("content-length")); + console.log("contentLength:", contentLength, attachmentInfo.headers); return isNaN(contentLength) ? 0 : contentLength; } export async function checkAttachment(hash) { const internetState = await NetInfo.fetch(); - const isInternetReachable = internetState.isConnected && internetState.isInternetReachable; + const isInternetReachable = + internetState.isConnected && internetState.isInternetReachable; if (!isInternetReachable) return { success: true }; const attachment = db.attachments.attachment(hash); - if (!attachment) return { failed: 'Attachment not found.' }; + if (!attachment) return { failed: "Attachment not found." }; try { const size = await getUploadedFileSize(hash); - if (size <= 0) return { failed: 'File length is 0.' }; + if (size <= 0) return { failed: "File length is 0." }; } catch (e) { return { failed: e?.message }; } diff --git a/apps/mobile/app/common/filesystem/index.js b/apps/mobile/app/common/filesystem/index.js index c7e1753d5..2c9720277 100644 --- a/apps/mobile/app/common/filesystem/index.js +++ b/apps/mobile/app/common/filesystem/index.js @@ -1,7 +1,18 @@ -import { downloadAttachment, downloadFile, getUploadedFileSize, checkAttachment } from './download'; -import { clearFileStorage, deleteFile, exists, readEncrypted, writeEncrypted } from './io'; -import { uploadFile } from './upload'; -import { cancelable } from './utils'; +import { + downloadAttachment, + downloadFile, + getUploadedFileSize, + checkAttachment +} from "./download"; +import { + clearFileStorage, + deleteFile, + exists, + readEncrypted, + writeEncrypted +} from "./io"; +import { uploadFile } from "./upload"; +import { cancelable } from "./utils"; export default { readEncrypted, diff --git a/apps/mobile/app/common/filesystem/io.js b/apps/mobile/app/common/filesystem/io.js index beb5c5669..de3103fea 100644 --- a/apps/mobile/app/common/filesystem/io.js +++ b/apps/mobile/app/common/filesystem/io.js @@ -1,7 +1,7 @@ -import { Platform } from 'react-native'; -import Sodium from 'react-native-sodium'; -import RNFetchBlob from 'rn-fetch-blob'; -import { cacheDir, getRandomId } from './utils'; +import { Platform } from "react-native"; +import Sodium from "react-native-sodium"; +import RNFetchBlob from "rn-fetch-blob"; +import { cacheDir, getRandomId } from "./utils"; export async function readEncrypted(filename, key, cipherData) { let path = `${cacheDir}/${filename}`; @@ -19,28 +19,28 @@ export async function readEncrypted(filename, key, cipherData) { }, true ); - console.log('output length: ', output?.length); + console.log("output length: ", output?.length); return output; } catch (e) { RNFetchBlob.fs.unlink(path).catch(console.log); console.log(e); - console.log('error'); + console.log("error"); return false; } } export async function writeEncrypted(filename, { data, type, key }) { - console.log('file input: ', { type, key }); - let filepath = cacheDir + `/${getRandomId('imagecache_')}`; + console.log("file input: ", { type, key }); + let filepath = cacheDir + `/${getRandomId("imagecache_")}`; console.log(filepath); - await RNFetchBlob.fs.writeFile(filepath, data, 'base64'); + await RNFetchBlob.fs.writeFile(filepath, data, "base64"); let output = await Sodium.encryptFile(key, { - uri: Platform.OS === 'ios' ? filepath : `file://` + filepath, - type: 'url' + uri: Platform.OS === "ios" ? filepath : `file://` + filepath, + type: "url" }); RNFetchBlob.fs.unlink(filepath).catch(console.log); - console.log('encrypted file output: ', output); + console.log("encrypted file output: ", output); return { ...output, alg: `xcha-stream` @@ -57,7 +57,7 @@ export async function deleteFile(filename, data) { let { url, headers } = data; try { - let response = await RNFetchBlob.fetch('DELETE', url, headers); + let response = await RNFetchBlob.fetch("DELETE", url, headers); let status = response.info().status; let ok = status >= 200 && status < 300; if (ok) { @@ -65,7 +65,7 @@ export async function deleteFile(filename, data) { } return ok; } catch (e) { - console.log('delete file: ', e, url, headers); + console.log("delete file: ", e, url, headers); return false; } } diff --git a/apps/mobile/app/common/filesystem/upload.js b/apps/mobile/app/common/filesystem/upload.js index 5daa27527..4905fb0e0 100644 --- a/apps/mobile/app/common/filesystem/upload.js +++ b/apps/mobile/app/common/filesystem/upload.js @@ -1,36 +1,38 @@ -import RNFetchBlob from 'rn-fetch-blob'; -import { useAttachmentStore } from '../../stores/use-attachment-store'; -import { db } from '../database'; -import { cacheDir } from './utils'; +import RNFetchBlob from "rn-fetch-blob"; +import { useAttachmentStore } from "../../stores/use-attachment-store"; +import { db } from "../database"; +import { cacheDir } from "./utils"; export async function uploadFile(filename, data, cancelToken) { if (!data) return false; let { url, headers } = data; - console.log('uploading file: ', filename, headers); + console.log("uploading file: ", filename, headers); try { let res = await fetch(url, { - method: 'PUT', + method: "PUT", headers }); if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`); const uploadUrl = await res.text(); - if (!uploadUrl) throw new Error('Unable to resolve upload url'); + if (!uploadUrl) throw new Error("Unable to resolve upload url"); let request = RNFetchBlob.config({ IOSBackgroundTask: true }) .fetch( - 'PUT', + "PUT", uploadUrl, { - 'content-type': '' + "content-type": "" }, RNFetchBlob.wrap(`${cacheDir}/${filename}`) ) .uploadProgress((sent, total) => { - useAttachmentStore.getState().setProgress(sent, total, filename, 0, 'upload'); - console.log('uploading: ', sent, total); + useAttachmentStore + .getState() + .setProgress(sent, total, filename, 0, "upload"); + console.log("uploading: ", sent, total); }); cancelToken.cancel = request.cancel; let response = await request; @@ -42,7 +44,7 @@ export async function uploadFile(filename, data, cancelToken) { if (result) { let attachment = db.attachments.attachment(filename); if (!attachment) return result; - if (!attachment.metadata.type.startsWith('image/')) { + if (!attachment.metadata.type.startsWith("image/")) { RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log); } } @@ -50,7 +52,7 @@ export async function uploadFile(filename, data, cancelToken) { return result; } catch (e) { useAttachmentStore.getState().remove(filename); - console.log('upload file: ', e, url, headers); + console.log("upload file: ", e, url, headers); return false; } } diff --git a/apps/mobile/app/common/filesystem/utils.js b/apps/mobile/app/common/filesystem/utils.js index 05ca87d68..3d1cbbfa2 100644 --- a/apps/mobile/app/common/filesystem/utils.js +++ b/apps/mobile/app/common/filesystem/utils.js @@ -1,25 +1,28 @@ -import RNFetchBlob from 'rn-fetch-blob'; +import RNFetchBlob from "rn-fetch-blob"; export const cacheDir = RNFetchBlob.fs.dirs.CacheDir; export function getRandomId(prefix) { return Math.random() .toString(36) - .replace('0.', prefix || ''); + .replace("0.", prefix || ""); } export function extractValueFromXmlTag(code, xml) { if (!xml.includes(code)) return `Unknown ${code}`; - return xml.slice(xml.indexOf(`<${code}>`) + code.length + 2, xml.indexOf(``)); + return xml.slice( + xml.indexOf(`<${code}>`) + code.length + 2, + xml.indexOf(``) + ); } export async function fileCheck(response, totalSize) { if (totalSize < 1000) { let text = await response.text(); - if (text.startsWith(' { - const colors = useThemeStore(state => state.colors); - const announcements = useMessageStore(state => state.announcements); + const colors = useThemeStore((state) => state.colors); + const announcements = useMessageStore((state) => state.announcements); let announcement = announcements.length > 0 ? announcements[0] : null; - const selectionMode = useSelectionStore(state => state.selectionMode); + const selectionMode = useSelectionStore((state) => state.selectionMode); return !announcement || selectionMode ? null : ( { > { allowedOnPlatform(item.platforms))} + data={announcement?.body.filter((item) => + allowedOnPlatform(item.platforms) + )} renderItem={({ item, index }) => - renderItem({ item: item, index: index, color: colors[color], inline: true }) + renderItem({ + item: item, + index: index, + color: colors[color], + inline: true + }) } /> diff --git a/apps/mobile/app/components/announcements/body.js b/apps/mobile/app/components/announcements/body.js index eaceb2c60..7b5345384 100644 --- a/apps/mobile/app/components/announcements/body.js +++ b/apps/mobile/app/components/announcements/body.js @@ -1,10 +1,10 @@ -import React from 'react'; -import { useThemeStore } from '../../stores/use-theme-store'; -import Paragraph from '../ui/typography/paragraph'; -import { getStyle } from './functions'; +import React from "react"; +import { useThemeStore } from "../../stores/use-theme-store"; +import Paragraph from "../ui/typography/paragraph"; +import { getStyle } from "./functions"; export const Body = ({ text, style = {} }) => { - const colors = useThemeStore(state => state.colors); + const colors = useThemeStore((state) => state.colors); return ( { - const colors = useThemeStore(state => state.colors); - let buttons = actions.filter(item => allowedOnPlatform(item.platforms)) || []; + const colors = useThemeStore((state) => state.colors); + let buttons = + actions.filter((item) => allowedOnPlatform(item.platforms)) || []; - const onPress = async item => { + const onPress = async (item) => { if (!inline) { eSendEvent(eCloseAnnouncementDialog); await sleep(500); } - if (item.type === 'link') { + if (item.type === "link") { Linking.openURL(item.data).catch(console.log); - } else if (item.type === 'promo') { + } else if (item.type === "promo") { presentSheet({ component: ( { /> ) }); - } else if (item.type === 'force-sync') { + } else if (item.type === "force-sync") { eSendEvent(eCloseProgressDialog); await sleep(300); Progress.present(); - Sync.run('global', true, true, () => { + Sync.run("global", true, true, () => { eSendEvent(eCloseProgressDialog); }); } @@ -50,7 +54,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => { style={{ paddingHorizontal: 12, ...getStyle(style), - flexDirection: inline ? 'row' : 'column' + flexDirection: inline ? "row" : "column" }} > @@ -58,20 +62,20 @@ export const Cta = ({ actions, style = {}, color, inline }) => { {inline ? ( <> {buttons.length > 0 && - buttons.slice(0, 1).map(item => ( + buttons.slice(0, 1).map((item) => (