Compare commits

...
Author SHA1 Message Date
StandardNotes CI 66a25c7556 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-09-19 20:47:02 +00:00
Aman Harwara e3be17c7f8 fix: show warning on mobile webview before app quits (#1598) 2022-09-20 01:48:40 +05:30
Mo c4d7761496 feat: mobile web bridge (#1597) 2022-09-19 14:47:15 -05:00
StandardNotes CI f80cc5b822 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-09-19 15:02:19 +00:00
Aman Harwara ecaa2a629f fix: preferences dropdown contrast and color (#1591) 2022-09-19 20:03:23 +05:30
StandardNotes CI 46f3e873a5 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
2022-09-19 13:17:25 +00:00
Karol Sójko 038320b2fd fix(snjs): pass unencrypted file size to comply with file service interface (#1595) 2022-09-19 14:36:53 +02:00
StandardNotes CI ee4a1acb9c chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-09-18 12:37:48 +00:00
Aman Harwara efe0c38462 fix: move save status next to note actions on mobile (#1590) 2022-09-18 17:37:30 +05:30
StandardNotes CI 4336c9ed66 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-09-16 21:52:59 +00:00
Aman Harwara 85c90e07fc feat: add settings to fully switch between native/webview (#1587) 2022-09-17 02:52:38 +05:30
StandardNotes CI 146b3329e0 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-09-16 18:15:47 +00:00
Mo 0c3c98d7af refactor(mobile): default to web frame if isDev (#1586)
* refactor(mobile): default to web frame if isDev

* fix: ignore native challenges

* fix: use contentInset for padding
2022-09-16 12:44:06 -05:00
StandardNotes CI 5a8752b95a chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2022-09-16 13:35:11 +00:00
Aman Harwara 7807fa34f3 fix: long titles on non-mobile view (#1575) 2022-09-16 18:35:43 +05:30
StandardNotes CI ddf4c97477 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-09-15 18:43:48 +00:00
Aman Harwara 3a15142940 fix: biometrics input on mobile webview challenge modal (#1572) 2022-09-15 23:41:03 +05:30
StandardNotes CI 79518b6a5d chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2022-09-15 10:28:11 +00:00
Karol SójkoandAman Harwara 2d0ee10226 feat: sharing subscriptions UI (#1567)
* feat(web): add ui for subscription sharing

* fix(web): add missing triggers

* fix(snjs): setting authorization token on http service

* fix(web): add alert upon invite failure

* fix(web): display invitations list

* fix(web): canceling subscription invitations

* fix(web): fonts

* fix(web): linter issues

* fix: click event handler

* fix: styles

* feat: update styles

* feat: don't show bottom separator if all invites used

* fix(web): references to alert service

* fix(web): remove usebeforeunload

Co-authored-by: Aman Harwara <[email protected]>
2022-09-15 12:00:29 +02:00
StandardNotes CI 05068ef63a chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-09-15 09:30:06 +00:00
Karol Sójko 174b3845d1 fix(models): make @standardnotes/models publishable as it is required on the server side 2022-09-15 10:59:53 +02:00
StandardNotes CI a9ef41fdc1 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2022-09-14 15:17:51 +00:00
Aman Harwara 110c5ff47f fix: move mobile nav button next to note/file title (#1568) 2022-09-14 20:14:21 +05:30
StandardNotes CI f3738358c9 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-09-14 13:51:47 +00:00
Vardan Hakobyan b4bdbfe96a fix: display QR codes with correctly colored border so that scanner apps successfully recognize them (#1566) 2022-09-14 16:48:45 +04:00
StandardNotes CI 912c9ce707 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-09-14 09:36:52 +00:00
Vardan HakobyanandAman Harwara d7aca2c13a feat: mobile security prefs (#1496)
* feat: move mobile-specific security items to Web when rendered in WebView

* feat: better UI for biometrics section

* feat: move Multitasking Privacy section to WebView (mostly UI)

* feat: move Multitasking Privacy section to WebView (going to understand why in WebView multitasking privacy value is auto-changed after reopening the WebView)

* feat: store MultitaskingPrivacy value as "NonWrapped" so that it's the same both on mobile and WebView

* feat: open WebView correctly when "Storage Encryption" is disabled on mobile

* fix: remove unnecessary changes and comments

* chore: revert ios-related unneeded changes

* fix: let Android to correctly recognize the NativeMobileWeb environment when opening WebView on Android

* fix: correct styles for the selected state of Biometrics/Passcode options

* chore: code cleanup

* fix: store Multitasking/Screenshot Privacy in the `Default` storage value mode

* chore: remove comment

* fix: use application's method instead of directly updating Screenshot Privacy preference

* fix: remove unused variable

* fix: use methods from Application and MobileDeviceInterface in all places, remove duplicate code

* fix: hide Multitasking Privacy and Biometrics Lock in WebView

Co-authored-by: Aman Harwara
2022-09-14 13:07:10 +04:00
StandardNotes CI e73d187b65 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-09-13 21:42:41 +00:00
Aman Harwara 896bb22f64 chore: update dependencies (#1543) 2022-09-14 02:42:25 +05:30
StandardNotes CI e7839cb141 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-09-13 17:00:59 +00:00
Aman Harwara 930bc2d8f7 chore(desktop): update dependencies (#1542) 2022-09-13 21:55:33 +05:30
167 changed files with 2687 additions and 620 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.7.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-15)
**Note:** Version bump only for package @standardnotes/api
## [1.7.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-13)
**Note:** Version bump only for package @standardnotes/api
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.7.1",
"version": "1.7.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+1
View File
@@ -0,0 +1 @@
dist
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.7.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-16)
**Note:** Version bump only for package @standardnotes/components-meta
## [2.7.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-13)
**Note:** Version bump only for package @standardnotes/components-meta
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/components-meta",
"version": "2.7.12",
"version": "2.7.13",
"private": true,
"author": "Standard Notes.",
"main": "dist",
+60
View File
@@ -3,6 +3,66 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.23.132](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-19)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.131](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-19)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.130](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-19)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.129](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-18)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.128](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-16)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.127](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-16)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.126](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-16)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.125](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-15)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.124](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-15)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.123](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-15)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.122](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-14)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.121](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-14)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.120](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-14)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.119](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-13)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.118](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-13)
**Note:** Version bump only for package @standardnotes/desktop
## [3.23.117](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-13)
**Note:** Version bump only for package @standardnotes/desktop
+13 -13
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.23.117",
"version": "3.23.132",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
@@ -41,42 +41,42 @@
"axios": "^0.27.2",
"compare-versions": "^4.1.3",
"decrypt": "github:standardnotes/decrypt#master",
"dotenv": "^16.0.0",
"electron": "17.4.2",
"electron-log": "^4.4.6",
"electron-updater": "^5.0.1",
"dotenv": "^16.0.2",
"electron": "20.1.3",
"electron-log": "^4.4.8",
"electron-updater": "^5.2.1",
"fs-extra": "^10.1.0",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"mobx": "^6.5.0"
"mobx": "^6.6.2"
},
"devDependencies": {
"@babel/core": "*",
"@babel/preset-env": "*",
"@types/lodash": "^4.14.182",
"@types/lodash": "^4.14.185",
"@types/mime-types": "^2.1.1",
"@types/node": "15",
"@types/proxyquire": "^1.3.28",
"@types/yauzl": "^2.10.0",
"ava": "^4.2.0",
"ava": "^4.3.3",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.5",
"copy-webpack-plugin": "^10.2.4",
"electron-builder": "23.0.3",
"electron-builder": "23.3.3",
"electron-notarize": "^1.2.1",
"eslint": "*",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-promise": "^6.0.1",
"file-loader": "^6.2.0",
"mime-types": "^2.1.35",
"prettier": "*",
"proxyquire": "^2.1.3",
"rimraf": "^3.0.2",
"terser-webpack-plugin": "^5.3.1",
"ts-loader": "^9.3.0",
"ts-node": "^10.7.0",
"terser-webpack-plugin": "^5.3.6",
"ts-loader": "^9.3.1",
"ts-node": "^10.9.1",
"typescript": "*",
"webpack": "*",
"webpack-merge": "^5.8.0"
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.15.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-15)
**Note:** Version bump only for package @standardnotes/encryption
## [1.15.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-13)
**Note:** Version bump only for package @standardnotes/encryption
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.15.1",
"version": "1.15.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.23.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-15)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.23.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-13)
**Note:** Version bump only for package @standardnotes/filepicker
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.23.1",
"version": "1.23.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-15)
**Note:** Version bump only for package @standardnotes/files
## [1.10.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-13)
**Note:** Version bump only for package @standardnotes/files
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.10.1",
"version": "1.10.2",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+7 -2
View File
@@ -1,3 +1,8 @@
metro.config.js
packages/mobile/html/Web.bundle/src/components
packages/mobile/html/Web.bundle/src/web-src
html/**/*
node_modules
ios
e2e
android
fastlane
WebFrame
+64
View File
@@ -3,6 +3,70 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [3.35.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-19)
### Features
* mobile web bridge ([#1597](https://github.com/standardnotes/app/issues/1597)) ([c4d7761](https://github.com/standardnotes/app/commit/c4d776149677269bc766f24da6adc5ec816dbcfb))
## [3.34.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-19)
**Note:** Version bump only for package @standardnotes/mobile
## [3.34.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-19)
**Note:** Version bump only for package @standardnotes/mobile
## [3.34.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-18)
**Note:** Version bump only for package @standardnotes/mobile
# [3.34.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-16)
### Features
* add settings to fully switch between native/webview ([#1587](https://github.com/standardnotes/app/issues/1587)) ([85c90e0](https://github.com/standardnotes/app/commit/85c90e07fc8b1ce66eaa88df09f2728ac0fecf1c))
## [3.33.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-16)
**Note:** Version bump only for package @standardnotes/mobile
## [3.33.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-16)
**Note:** Version bump only for package @standardnotes/mobile
## [3.33.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-15)
### Bug Fixes
* biometrics input on mobile webview challenge modal ([#1572](https://github.com/standardnotes/app/issues/1572)) ([3a15142](https://github.com/standardnotes/app/commit/3a15142940ef9391868442e1997c3d9d5eec33be))
## [3.33.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-15)
**Note:** Version bump only for package @standardnotes/mobile
## [3.33.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-15)
**Note:** Version bump only for package @standardnotes/mobile
## [3.33.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-14)
**Note:** Version bump only for package @standardnotes/mobile
## [3.33.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-14)
**Note:** Version bump only for package @standardnotes/mobile
# [3.33.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-14)
### Features
* mobile security prefs ([#1496](https://github.com/standardnotes/app/issues/1496)) ([d7aca2c](https://github.com/standardnotes/app/commit/d7aca2c13a99b81c2ee9d36a09f670b0a4c61bd1))
## [3.32.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-13)
**Note:** Version bump only for package @standardnotes/mobile
## [3.32.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-13)
**Note:** Version bump only for package @standardnotes/mobile
+5 -3
View File
@@ -1,10 +1,12 @@
import { IsMobileWeb } from '@Lib/Utils'
import { MobileWebApp } from '@Root/MobileWebApp'
import { SNLog } from '@standardnotes/snjs'
import { AppRegistry } from 'react-native'
import 'react-native-gesture-handler'
import { enableScreens } from 'react-native-screens'
import 'react-native-url-polyfill/auto'
import { name as appName } from './app.json'
import { App } from './src/App'
import { NativeApp } from './src/NativeApp'
import { enableAndroidFontFix } from './src/Style/android_text_fix'
enableScreens()
@@ -28,11 +30,11 @@ console.warn = function filterWarnings(msg) {
"[react-native-gesture-handler] Seems like you're using an old API with gesture components",
]
if (!supressedWarnings.some(entry => msg.includes(entry))) {
if (!supressedWarnings.some((entry) => msg.includes(entry))) {
originalWarn.apply(console, arguments)
}
}
enableAndroidFontFix()
AppRegistry.registerComponent(appName, () => App)
AppRegistry.registerComponent(appName, () => (IsMobileWeb ? MobileWebApp : NativeApp))
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.32.4",
"version": "3.35.0",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+25 -29
View File
@@ -2,33 +2,30 @@ import { AppStateEventType, AppStateType, TabletModeChangeData } from '@Lib/Appl
import { AlwaysOpenWebAppOnLaunchKey } from '@Lib/constants'
import { useHasEditor, useIsLocked } from '@Lib/SnjsHelperHooks'
import { ScreenStatus } from '@Lib/StatusManager'
import { IsDev } from '@Lib/Utils'
import { CompositeNavigationProp, RouteProp, useNavigation } from '@react-navigation/native'
import { IsMobileWeb } from '@Lib/Utils'
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native'
import { createStackNavigator, StackNavigationProp } from '@react-navigation/stack'
import { HeaderTitleView } from '@Root/Components/HeaderTitleView'
import { IoniconsHeaderButton } from '@Root/Components/IoniconsHeaderButton'
import { Compose } from '@Root/Screens/Compose/Compose'
import { SCREEN_COMPOSE, SCREEN_NOTES, SCREEN_VIEW_PROTECTED_NOTE, SCREEN_WEB_APP } from '@Root/Screens/screens'
import { SCREEN_COMPOSE, SCREEN_NOTES, SCREEN_VIEW_PROTECTED_NOTE } from '@Root/Screens/screens'
import { MainSideMenu } from '@Root/Screens/SideMenu/MainSideMenu'
import { NoteSideMenu } from '@Root/Screens/SideMenu/NoteSideMenu'
import { ViewProtectedNote } from '@Root/Screens/ViewProtectedNote/ViewProtectedNote'
import { Root } from '@Screens/Root'
import { ApplicationEvent, StorageValueModes, UuidString } from '@standardnotes/snjs'
import { StorageValueModes, UuidString } from '@standardnotes/snjs'
import { ICON_MENU } from '@Style/Icons'
import { ThemeService } from '@Style/ThemeService'
import { getDefaultDrawerWidth } from '@Style/Utils'
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'
import { Dimensions, Keyboard, ScaledSize } from 'react-native'
import { Dimensions, Keyboard, ScaledSize, StatusBar } from 'react-native'
import DrawerLayout, { DrawerState } from 'react-native-gesture-handler/DrawerLayout'
import { HeaderButtons, Item } from 'react-navigation-header-buttons'
import { ThemeContext } from 'styled-components'
import { HeaderTitleParams } from './App'
import { ApplicationContext } from './ApplicationContext'
import { MobileWebAppContainer } from './MobileWebAppContainer'
import { ModalStackNavigationProp } from './ModalStack'
const IS_DEBUGGING_WEB_APP = false
const DEFAULT_TO_WEB_APP = IsDev && IS_DEBUGGING_WEB_APP
import { HeaderTitleParams } from './NativeApp'
export type AppStackNavigatorParamList = {
[SCREEN_NOTES]: HeaderTitleParams
@@ -124,27 +121,24 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
[application],
)
const navigation = useNavigation<ModalStackNavigationProp<'AppStack'>['navigation']>()
if (IsMobileWeb) {
return (
<AppStack.Navigator
screenOptions={() => ({
headerShown: false,
})}
initialRouteName={SCREEN_NOTES}
>
<AppStack.Screen name={SCREEN_NOTES} component={MobileWebAppContainer} />
</AppStack.Navigator>
)
}
useEffect(() => {
if (!application) {
return
}
if (!application) {
return null
}
const removeObserver = application.addEventObserver(async (event) => {
if (event === ApplicationEvent.Launched) {
const value = (await application.getValue(AlwaysOpenWebAppOnLaunchKey, StorageValueModes.Nonwrapped)) as
| boolean
| undefined
const shouldAlwaysOpenWebAppOnLaunch = value ?? false
if (shouldAlwaysOpenWebAppOnLaunch) {
navigation.push(SCREEN_WEB_APP)
}
}
})
return removeObserver
}, [application, navigation])
const shouldOpenWebApp = application.getValue(AlwaysOpenWebAppOnLaunchKey, StorageValueModes.Nonwrapped) as boolean
return (
<DrawerLayout
@@ -156,6 +150,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
onDrawerStateChanged={handleDrawerStateChange}
renderNavigationView={() => !isLocked && <MainSideMenu drawerRef={drawerRef.current} />}
>
<StatusBar translucent={!shouldOpenWebApp} />
<DrawerLayout
ref={noteDrawerRef}
drawerWidth={getDefaultDrawerWidth(dimensions)}
@@ -185,6 +180,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
name={SCREEN_NOTES}
options={({ route }) => ({
title: 'All notes',
headerShown: !shouldOpenWebApp,
headerTitle: ({ children }) => {
const screenStatus = isInTabletMode ? composeStatus || notesStatus : notesStatus
@@ -224,7 +220,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
</HeaderButtons>
),
})}
component={DEFAULT_TO_WEB_APP ? MobileWebAppContainer : Root}
component={shouldOpenWebApp ? MobileWebAppContainer : Root}
/>
<AppStack.Screen
name={SCREEN_COMPOSE}
@@ -0,0 +1,62 @@
import { AbstractService, InternalEventBus, ReactNativeToWebEvent } from '@standardnotes/snjs'
import { AppState, AppStateStatus, NativeEventSubscription } from 'react-native'
export class AppStateObserverService extends AbstractService<ReactNativeToWebEvent> {
private mostRecentState?: ReactNativeToWebEvent
private removeListener: NativeEventSubscription
private ignoringStateChanges = false
constructor() {
const bus = new InternalEventBus()
super(bus)
this.removeListener = AppState.addEventListener('change', async (nextAppState: AppStateStatus) => {
if (this.ignoringStateChanges) {
return
}
// if the most recent state is not 'background' ('inactive'), then we're going
// from inactive to active, which doesn't really happen unless you, say, swipe
// notification center in iOS down then back up. We don't want to lock on this state change.
const isResuming = nextAppState === 'active'
const isResumingFromBackground = isResuming && this.mostRecentState === ReactNativeToWebEvent.EnteringBackground
const isEnteringBackground = nextAppState === 'background'
const isLosingFocus = nextAppState === 'inactive'
if (isEnteringBackground) {
this.notifyStateChange(ReactNativeToWebEvent.EnteringBackground)
}
if (isResumingFromBackground || isResuming) {
if (isResumingFromBackground) {
this.notifyStateChange(ReactNativeToWebEvent.ResumingFromBackground)
}
// Notify of GainingFocus even if resuming from background
this.notifyStateChange(ReactNativeToWebEvent.GainingFocus)
return
}
if (isLosingFocus) {
this.notifyStateChange(ReactNativeToWebEvent.LosingFocus)
}
})
}
beginIgnoringStateChanges() {
this.ignoringStateChanges = true
}
stopIgnoringStateChanges() {
this.ignoringStateChanges = false
}
deinit() {
this.removeListener.remove()
}
private notifyStateChange(state: ReactNativeToWebEvent): void {
this.mostRecentState = state
void this.notifyEvent(state)
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ import React, { useContext } from 'react'
import { Platform } from 'react-native'
import { HeaderButtons, Item } from 'react-navigation-header-buttons'
import { ThemeContext } from 'styled-components'
import { HeaderTitleParams } from './App'
import { HeaderTitleParams } from './NativeApp'
type HistoryStackNavigatorParamList = {
[SCREEN_NOTE_HISTORY]: (HeaderTitleParams & { noteUuid: string }) | (undefined & { noteUuid: string })
+10 -5
View File
@@ -9,6 +9,7 @@ import {
Environment,
IconsController,
ItemGroupController,
MobileUnlockTiming,
platformFromString,
SNApplication,
SNComponentManager,
@@ -17,18 +18,18 @@ import { Platform } from 'react-native'
import { version } from '../../package.json'
import { MobileAlertService } from './AlertService'
import { ApplicationState, UnlockTiming } from './ApplicationState'
import { ApplicationState } from './ApplicationState'
import { BackupsService } from './BackupsService'
import { ComponentManager } from './ComponentManager'
import { FilesService } from './FilesService'
import { InstallationService } from './InstallationService'
import { MobileDeviceInterface } from './Interface'
import { MobileDevice } from './Interface'
import { push } from './NavigationService'
import { PreferencesManager } from './PreferencesManager'
import { SNReactNativeCrypto } from './ReactNativeCrypto'
import { ReviewService } from './ReviewService'
import { StatusManager } from './StatusManager'
import { IsDev } from './Utils'
import { IsDev, IsMobileWeb } from './Utils'
type MobileServices = {
applicationState: ApplicationState
@@ -51,7 +52,7 @@ export class MobileApplication extends SNApplication {
static previouslyLaunched = false
constructor(deviceInterface: MobileDeviceInterface, identifier: string) {
constructor(deviceInterface: MobileDevice, identifier: string) {
super({
environment: Environment.Mobile,
platform: platformFromString(Platform.OS),
@@ -122,7 +123,7 @@ export class MobileApplication extends SNApplication {
const previouslyLaunched = MobileApplication.getPreviouslyLaunched()
const biometricsTiming = this.getAppState().biometricsTiming
if (previouslyLaunched && biometricsTiming === UnlockTiming.OnQuit) {
if (previouslyLaunched && biometricsTiming === MobileUnlockTiming.OnQuit) {
const filteredPrompts = challenge.prompts.filter(
(prompt: ChallengePrompt) => prompt.validation !== ChallengeValidation.Biometric,
)
@@ -134,6 +135,10 @@ export class MobileApplication extends SNApplication {
}
promptForChallenge(challenge: Challenge) {
if (IsMobileWeb) {
return
}
push(SCREEN_AUTHENTICATE, { challenge, title: challenge.modalTitle })
}
+3 -3
View File
@@ -4,14 +4,14 @@ import { ApplicationState } from './ApplicationState'
import { BackupsService } from './BackupsService'
import { FilesService } from './FilesService'
import { InstallationService } from './InstallationService'
import { MobileDeviceInterface } from './Interface'
import { MobileDevice } from './Interface'
import { PreferencesManager } from './PreferencesManager'
import { ReviewService } from './ReviewService'
import { StatusManager } from './StatusManager'
export class ApplicationGroup extends SNApplicationGroup {
constructor() {
super(new MobileDeviceInterface())
super(new MobileDevice())
}
override async initialize(_callback?: any): Promise<void> {
@@ -21,7 +21,7 @@ export class ApplicationGroup extends SNApplicationGroup {
}
private createApplication = async (descriptor: ApplicationDescriptor, deviceInterface: DeviceInterface) => {
const application = new MobileApplication(deviceInterface as MobileDeviceInterface, descriptor.identifier)
const application = new MobileApplication(deviceInterface as MobileDevice, descriptor.identifier)
const internalEventBus = new InternalEventBus()
const applicationState = new ApplicationState(application)
const reviewService = new ReviewService(application, internalEventBus)
+46 -85
View File
@@ -1,3 +1,4 @@
import { MobileDevice } from '@Lib/Interface'
import {
ApplicationEvent,
ApplicationService,
@@ -8,6 +9,7 @@ import {
ContentType,
InternalEventBus,
isNullOrUndefined,
MobileUnlockTiming,
NoteViewController,
PayloadEmitSource,
PrefKey,
@@ -30,14 +32,13 @@ import {
KeyboardEventListener,
NativeEventSubscription,
NativeModules,
Platform,
} from 'react-native'
import FlagSecure from 'react-native-flag-secure-android'
import { hide, show } from 'react-native-privacy-snapshot'
import VersionInfo from 'react-native-version-info'
import pjson from '../../package.json'
import { MobileApplication } from './Application'
import { associateComponentWithNote } from './ComponentManager'
const { PlatformConstants } = NativeModules
export enum AppStateType {
@@ -66,11 +67,6 @@ export type TabletModeChangeData = {
old_isInTabletMode: boolean
}
export enum UnlockTiming {
Immediately = 'immediately',
OnQuit = 'on-quit',
}
export enum PasscodeKeyboardType {
Default = 'default',
Numeric = 'numeric',
@@ -103,8 +99,8 @@ export class ApplicationState extends ApplicationService {
authenticationInProgress = false
multiEditorEnabled = false
screenshotPrivacyEnabled?: boolean
passcodeTiming?: UnlockTiming
biometricsTiming?: UnlockTiming
passcodeTiming?: MobileUnlockTiming
biometricsTiming?: MobileUnlockTiming
removeHandleReactNativeAppStateChangeListener: NativeEventSubscription
removeItemChangesListener?: () => void
removePreferencesLoadedListener?: () => void
@@ -173,7 +169,7 @@ export class ApplicationState extends ApplicationService {
override async onAppLaunch() {
MobileApplication.setPreviouslyLaunched()
this.screenshotPrivacyEnabled = (await this.getScreenshotPrivacyEnabled()) ?? true
void this.setAndroidScreenshotPrivacy(this.screenshotPrivacyEnabled)
await (this.application.deviceInterface as MobileDevice).setAndroidScreenshotPrivacy(this.screenshotPrivacyEnabled)
}
/**
@@ -248,12 +244,6 @@ export class ApplicationState extends ApplicationService {
this.biometricsTiming = await this.getBiometricsTiming()
}
public async setAndroidScreenshotPrivacy(enable: boolean) {
if (Platform.OS === 'android') {
enable ? FlagSecure.activate() : FlagSecure.deactivate()
}
}
/**
* Creates a new editor if one doesn't exist. If one does, we'll replace the
* editor's note with an empty one.
@@ -486,60 +476,37 @@ export class ApplicationState extends ApplicationService {
}
}
getPasscodeTimingOptions() {
return [
{
title: 'Immediately',
key: UnlockTiming.Immediately,
selected: this.passcodeTiming === UnlockTiming.Immediately,
},
{
title: 'On Quit',
key: UnlockTiming.OnQuit,
selected: this.passcodeTiming === UnlockTiming.OnQuit,
},
]
}
getBiometricsTimingOptions() {
return [
{
title: 'Immediately',
key: UnlockTiming.Immediately,
selected: this.biometricsTiming === UnlockTiming.Immediately,
},
{
title: 'On Quit',
key: UnlockTiming.OnQuit,
selected: this.biometricsTiming === UnlockTiming.OnQuit,
},
]
}
private async checkAndLockApplication() {
const isLocked = await this.application.isLocked()
if (!isLocked) {
const hasBiometrics = await this.application.hasBiometrics()
const hasPasscode = this.application.hasPasscode()
if (hasPasscode && this.passcodeTiming === UnlockTiming.Immediately) {
await this.application.lock()
} else if (hasBiometrics && this.biometricsTiming === UnlockTiming.Immediately && !this.locked) {
const challenge = new Challenge(
[new ChallengePrompt(ChallengeValidation.Biometric)],
ChallengeReason.ApplicationUnlock,
false,
)
void this.application.promptForCustomChallenge(challenge)
this.locked = true
this.notifyLockStateObservers(LockStateType.Locked)
this.application.addChallengeObserver(challenge, {
onComplete: () => {
this.locked = false
this.notifyLockStateObservers(LockStateType.Unlocked)
},
})
}
if (isLocked) {
return
}
const hasBiometrics = this.application.hasBiometrics()
const hasPasscode = this.application.hasPasscode()
const passcodeLockImmediately = hasPasscode && this.passcodeTiming === MobileUnlockTiming.Immediately
const biometricsLockImmediately =
hasBiometrics && this.biometricsTiming === MobileUnlockTiming.Immediately && !this.locked
if (passcodeLockImmediately) {
await this.application.lock()
} else if (biometricsLockImmediately) {
const challenge = new Challenge(
[new ChallengePrompt(ChallengeValidation.Biometric)],
ChallengeReason.ApplicationUnlock,
false,
)
void this.application.promptForCustomChallenge(challenge)
this.locked = true
this.notifyLockStateObservers(LockStateType.Locked)
this.application.addChallengeObserver(challenge, {
onComplete: () => {
this.locked = false
this.notifyLockStateObservers(LockStateType.Unlocked)
},
})
}
}
@@ -604,36 +571,30 @@ export class ApplicationState extends ApplicationService {
}
private async getScreenshotPrivacyEnabled(): Promise<boolean | undefined> {
return this.application.getValue(StorageKey.MobileScreenshotPrivacyEnabled, StorageValueModes.Default) as Promise<
boolean | undefined
>
return this.application.getMobileScreenshotPrivacyEnabled()
}
private async getPasscodeTiming(): Promise<UnlockTiming | undefined> {
return this.application.getValue(StorageKey.MobilePasscodeTiming, StorageValueModes.Nonwrapped) as Promise<
UnlockTiming | undefined
>
private async getPasscodeTiming(): Promise<MobileUnlockTiming | undefined> {
return this.application.getMobilePasscodeTiming()
}
private async getBiometricsTiming(): Promise<UnlockTiming | undefined> {
return this.application.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped) as Promise<
UnlockTiming | undefined
>
private async getBiometricsTiming(): Promise<MobileUnlockTiming | undefined> {
return this.application.getMobileBiometricsTiming()
}
public async setScreenshotPrivacyEnabled(enabled: boolean) {
await this.application.setValue(StorageKey.MobileScreenshotPrivacyEnabled, enabled, StorageValueModes.Default)
await this.application.setMobileScreenshotPrivacyEnabled(enabled)
this.screenshotPrivacyEnabled = enabled
void this.setAndroidScreenshotPrivacy(enabled)
await (this.application.deviceInterface as MobileDevice).setAndroidScreenshotPrivacy(enabled)
}
public async setPasscodeTiming(timing: UnlockTiming) {
await this.application.setValue(StorageKey.MobilePasscodeTiming, timing, StorageValueModes.Nonwrapped)
public async setPasscodeTiming(timing: MobileUnlockTiming) {
this.application.setValue(StorageKey.MobilePasscodeTiming, timing, StorageValueModes.Nonwrapped)
this.passcodeTiming = timing
}
public async setBiometricsTiming(timing: UnlockTiming) {
await this.application.setValue(StorageKey.MobileBiometricsTiming, timing, StorageValueModes.Nonwrapped)
public async setBiometricsTiming(timing: MobileUnlockTiming) {
this.application.setValue(StorageKey.MobileBiometricsTiming, timing, StorageValueModes.Nonwrapped)
this.biometricsTiming = timing
}
@@ -645,7 +606,7 @@ export class ApplicationState extends ApplicationService {
}
public async setPasscodeKeyboardType(type: PasscodeKeyboardType) {
await this.application.setValue(MobileStorageKey.PasscodeKeyboardTypeKey, type, StorageValueModes.Nonwrapped)
this.application.setValue(MobileStorageKey.PasscodeKeyboardTypeKey, type, StorageValueModes.Nonwrapped)
}
public onDrawerOpen() {
@@ -1,6 +1,6 @@
import SNReactNative from '@standardnotes/react-native-utils'
import { ApplicationService, ButtonType, StorageValueModes } from '@standardnotes/snjs'
import { MobileDeviceInterface } from './Interface'
import { MobileDevice } from './Interface'
const FIRST_RUN_KEY = 'first_run'
@@ -23,7 +23,7 @@ export class InstallationService extends ApplicationService {
*/
async needsWipe() {
const hasAccountOrPasscode = this.application.hasAccount() || this.application?.hasPasscode()
const deviceInterface = this.application.deviceInterface as MobileDeviceInterface
const deviceInterface = this.application.deviceInterface as MobileDevice
const keychainKey = await deviceInterface.getNamespacedKeychainValue(this.application.identifier)
const hasKeychainValue = keychainKey != undefined
+116 -7
View File
@@ -2,20 +2,31 @@ import AsyncStorage from '@react-native-community/async-storage'
import SNReactNative from '@standardnotes/react-native-utils'
import {
ApplicationIdentifier,
DeviceInterface,
Environment,
LegacyMobileKeychainStructure,
LegacyRawKeychainValue,
MobileDeviceInterface,
NamespacedRootKeyInKeychain,
RawKeychainValue,
removeFromArray,
TransferPayload,
} from '@standardnotes/snjs'
import { Alert, Linking, Platform } from 'react-native'
import FingerprintScanner from 'react-native-fingerprint-scanner'
import FlagSecure from 'react-native-flag-secure-android'
import { hide, show } from 'react-native-privacy-snapshot'
import { AppStateObserverService } from './../AppStateObserverService'
import Keychain from './Keychain'
import { IsMobileWeb } from './Utils'
export type BiometricsType = 'Fingerprint' | 'Face ID' | 'Biometrics' | 'Touch ID'
export enum MobileDeviceEvent {
RequestsWebViewReload = 0,
}
type MobileDeviceEventHandler = (event: MobileDeviceEvent) => void
/**
* This identifier was the database name used in Standard Notes web/desktop.
*/
@@ -51,11 +62,16 @@ const showLoadFailForItemIds = (failedItemIds: string[]) => {
Alert.alert('Unable to load item(s)', text)
}
export class MobileDeviceInterface implements DeviceInterface {
export class MobileDevice implements MobileDeviceInterface {
environment: Environment.Mobile = Environment.Mobile
private eventObservers: MobileDeviceEventHandler[] = []
// eslint-disable-next-line @typescript-eslint/no-empty-function
deinit() {}
constructor(private stateObserverService?: AppStateObserverService) {}
deinit() {
this.stateObserverService?.deinit()
;(this.stateObserverService as unknown) = undefined
}
async setLegacyRawKeychainValue(value: LegacyRawKeychainValue): Promise<void> {
await Keychain.setKeys(value)
@@ -176,6 +192,14 @@ export class MobileDeviceInterface implements DeviceInterface {
}
}
hideMobileInterfaceFromScreenshots(): void {
hide()
}
stopHidingMobileInterfaceFromScreenshots(): void {
show()
}
async getAllRawStorageKeyValues() {
const keys = await AsyncStorage.getAllKeys()
return this.getRawStorageKeyValues(keys)
@@ -287,14 +311,79 @@ export class MobileDeviceInterface implements DeviceInterface {
}
}
getRawKeychainValue(): Promise<RawKeychainValue | null | undefined> {
return Keychain.getKeys()
async authenticateWithBiometrics() {
this.stateObserverService?.beginIgnoringStateChanges()
const result = await new Promise<boolean>((resolve) => {
if (Platform.OS === 'android') {
FingerprintScanner.authenticate({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ts type does not exist for deviceCredentialAllowed
deviceCredentialAllowed: true,
description: 'Biometrics are required to access your notes.',
})
.then(() => {
FingerprintScanner.release()
resolve(true)
})
.catch((error) => {
FingerprintScanner.release()
if (error.name === 'DeviceLocked') {
Alert.alert('Unsuccessful', 'Authentication failed. Wait 30 seconds to try again.')
} else {
Alert.alert('Unsuccessful', 'Authentication failed. Tap to try again.')
}
resolve(false)
})
} else {
// iOS
FingerprintScanner.authenticate({
fallbackEnabled: true,
description: 'This is required to access your notes.',
})
.then(() => {
FingerprintScanner.release()
resolve(true)
})
.catch((error_1) => {
FingerprintScanner.release()
if (error_1.name !== 'SystemCancel') {
if (error_1.name !== 'UserCancel') {
Alert.alert('Unsuccessful')
} else {
Alert.alert('Unsuccessful', 'Authentication failed. Tap to try again.')
}
}
resolve(false)
})
}
})
this.stateObserverService?.stopIgnoringStateChanges()
return result
}
async getRawKeychainValue(): Promise<RawKeychainValue | undefined> {
const result = await Keychain.getKeys()
if (result === null) {
return undefined
}
return result
}
async clearRawKeychainValue(): Promise<void> {
await Keychain.clearKeys()
}
async setAndroidScreenshotPrivacy(enable: boolean): Promise<void> {
if (Platform.OS === 'android') {
enable ? FlagSecure.activate() : FlagSecure.deactivate()
}
}
openUrl(url: string) {
const showAlert = () => {
Alert.alert('Unable to Open', `Unable to open URL ${url}.`)
@@ -321,7 +410,27 @@ export class MobileDeviceInterface implements DeviceInterface {
}
performSoftReset() {
SNReactNative.exitApp()
if (IsMobileWeb) {
this.notifyEvent(MobileDeviceEvent.RequestsWebViewReload)
} else {
SNReactNative.exitApp()
}
}
addMobileWebEventReceiver(handler: MobileDeviceEventHandler): () => void {
this.eventObservers.push(handler)
const thislessObservers = this.eventObservers
return () => {
removeFromArray(thislessObservers, handler)
}
}
private notifyEvent(event: MobileDeviceEvent): void {
for (const handler of this.eventObservers) {
handler(event)
}
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
+2 -1
View File
@@ -1,7 +1,8 @@
import { TEnvironment } from '@Root/App'
import { TEnvironment } from '@Root/NativeApp'
import VersionInfo from 'react-native-version-info'
export const IsDev = VersionInfo.bundleIdentifier?.includes('dev')
export const IsMobileWeb = IsDev
export function isNullOrUndefined(value: unknown) {
return value === null || value === undefined
+16
View File
@@ -0,0 +1,16 @@
import { navigationRef } from '@Lib/NavigationService'
import { NavigationContainer } from '@react-navigation/native'
import React from 'react'
import { MobileWebMainStackComponent } from './ModalStack'
const AppComponent: React.FC = () => {
return (
<NavigationContainer ref={navigationRef}>
<MobileWebMainStackComponent />
</NavigationContainer>
)
}
export const MobileWebApp = () => {
return <AppComponent />
}
+77 -24
View File
@@ -1,13 +1,50 @@
import { MobileDeviceInterface } from '@Lib/Interface'
import React, { useMemo, useRef } from 'react'
import { MobileDevice, MobileDeviceEvent } from '@Lib/Interface'
import { ReactNativeToWebEvent } from '@standardnotes/snjs'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Platform } from 'react-native'
import { WebView, WebViewMessageEvent } from 'react-native-webview'
import { AppStateObserverService } from './AppStateObserverService'
const LoggingEnabled = false
export const MobileWebAppContainer = () => {
const [identifier, setIdentifier] = useState(Math.random())
const destroyAndReload = useCallback(() => {
setIdentifier(Math.random())
}, [])
return <MobileWebAppContents key={`${identifier}`} destroyAndReload={destroyAndReload} />
}
const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => void }) => {
const webViewRef = useRef<WebView>(null)
const sourceUri = (Platform.OS === 'android' ? 'file:///android_asset/' : '') + 'Web.bundle/src/index.html'
const webViewRef = useRef<WebView>(null)
const stateService = useMemo(() => new AppStateObserverService(), [])
const device = useMemo(() => new MobileDevice(stateService), [stateService])
useEffect(() => {
const removeListener = stateService.addEventObserver((event: ReactNativeToWebEvent) => {
webViewRef.current?.postMessage(JSON.stringify({ reactNativeEvent: event, messageType: 'event' }))
})
return () => {
removeListener()
}
}, [webViewRef, stateService])
useEffect(() => {
const observer = device.addMobileWebEventReceiver((event) => {
if (event === MobileDeviceEvent.RequestsWebViewReload) {
destroyAndReload()
}
})
return () => {
observer()
}
}, [device, destroyAndReload])
const device = useMemo(() => new MobileDeviceInterface(), [])
const functions = Object.getOwnPropertyNames(Object.getPrototypeOf(device))
const baselineFunctions: Record<string, any> = {
@@ -28,7 +65,7 @@ export const MobileWebAppContainer = () => {
stringFunctions += `
${functionName}(...args) {
return this.sendMessage('${functionName}', args);
return this.askReactNativeToInvokeInterfaceMethod('${functionName}', args);
}
`
}
@@ -44,8 +81,8 @@ export const MobileWebAppContainer = () => {
setApplication() {}
sendMessage(functionName, args) {
return this.messageSender.sendMessage(functionName, args)
askReactNativeToInvokeInterfaceMethod(functionName, args) {
return this.messageSender.askReactNativeToInvokeInterfaceMethod(functionName, args)
}
${stringFunctions}
@@ -56,26 +93,18 @@ export const MobileWebAppContainer = () => {
class WebProcessMessageSender {
constructor() {
this.pendingMessages = []
window.addEventListener('message', this.handleMessageFromReactNative.bind(this))
document.addEventListener('message', this.handleMessageFromReactNative.bind(this))
}
handleMessageFromReactNative(event) {
const message = event.data
try {
const parsed = JSON.parse(message)
const { messageId, returnValue } = parsed
const pendingMessage = this.pendingMessages.find((m) => m.messageId === messageId)
pendingMessage.resolve(returnValue)
this.pendingMessages.splice(this.pendingMessages.indexOf(pendingMessage), 1)
} catch (error) {
console.log('Error parsing message from React Native', message, error)
}
handleReplyFromReactNative( messageId, returnValue) {
const pendingMessage = this.pendingMessages.find((m) => m.messageId === messageId)
pendingMessage.resolve(returnValue)
this.pendingMessages.splice(this.pendingMessages.indexOf(pendingMessage), 1)
}
sendMessage(functionName, args) {
askReactNativeToInvokeInterfaceMethod(functionName, args) {
const messageId = Math.random()
window.ReactNativeWebView.postMessage(JSON.stringify({ functionName: functionName, args: args, messageId }))
return new Promise((resolve) => {
this.pendingMessages.push({
messageId,
@@ -98,6 +127,25 @@ export const MobileWebAppContainer = () => {
const messageSender = new WebProcessMessageSender();
window.reactNativeDevice = new WebProcessDeviceInterface(messageSender);
const handleMessageFromReactNative = (event) => {
const message = event.data
try {
const parsed = JSON.parse(message)
const { messageId, returnValue, messageType } = parsed
if (messageType === 'reply') {
messageSender.handleReplyFromReactNative(messageId, returnValue)
}
} catch (error) {
console.log('Error parsing message from React Native', message, error)
}
}
window.addEventListener('message', handleMessageFromReactNative)
document.addEventListener('message', handleMessageFromReactNative)
true;
`
@@ -107,14 +155,18 @@ export const MobileWebAppContainer = () => {
const functionData = JSON.parse(message)
void onFunctionMessage(functionData.functionName, functionData.messageId, functionData.args)
} catch (error) {
console.log('onGeneralMessage', JSON.stringify(message))
if (LoggingEnabled) {
console.log('onGeneralMessage', JSON.stringify(message))
}
}
}
const onFunctionMessage = async (functionName: string, messageId: string, args: any) => {
const returnValue = await (device as any)[functionName](...args)
console.log(`Native device function ${functionName} called`)
webViewRef.current?.postMessage(JSON.stringify({ messageId, returnValue }))
if (LoggingEnabled) {
console.log(`Native device function ${functionName} called`)
}
webViewRef.current?.postMessage(JSON.stringify({ messageId, returnValue, messageType: 'reply' }))
}
/* eslint-disable @typescript-eslint/no-empty-function */
@@ -123,6 +175,7 @@ export const MobileWebAppContainer = () => {
ref={webViewRef}
source={{ uri: sourceUri }}
originWhitelist={['*']}
contentInset={{ top: 30, bottom: 10 }}
onLoad={() => {}}
onError={(err) => console.error('An error has occurred', err)}
onHttpError={() => console.error('An HTTP error occurred')}
+26 -2
View File
@@ -30,11 +30,11 @@ import React, { memo, useContext } from 'react'
import { Platform } from 'react-native'
import { HeaderButtons, Item } from 'react-navigation-header-buttons'
import { ThemeContext } from 'styled-components'
import { HeaderTitleParams, TEnvironment } from './App'
import { ApplicationContext } from './ApplicationContext'
import { AppStackComponent } from './AppStack'
import { HistoryStack } from './HistoryStack'
import { MobileWebAppContainer } from './MobileWebAppContainer'
import { HeaderTitleParams, TEnvironment } from './NativeApp'
export type ModalStackNavigatorParamList = {
AppStack: undefined
@@ -75,7 +75,31 @@ export type ModalStackNavigationProp<T extends keyof ModalStackNavigatorParamLis
const MainStack = createStackNavigator<ModalStackNavigatorParamList>()
export const MainStackComponent = ({ env }: { env: TEnvironment }) => {
export const MobileWebMainStackComponent = () => {
const MemoizedAppStackComponent = memo((props: ModalStackNavigationProp<'AppStack'>) => (
<AppStackComponent {...props} />
))
return (
<MainStack.Navigator
screenOptions={{
gestureEnabled: false,
presentation: 'modal',
}}
initialRouteName="AppStack"
>
<MainStack.Screen
name={'AppStack'}
options={{
headerShown: false,
}}
component={MemoizedAppStackComponent}
/>
</MainStack.Navigator>
)
}
export const NativeMainStackComponent = ({ env }: { env: TEnvironment }) => {
const application = useContext(ApplicationContext)
const theme = useContext(ThemeContext)
@@ -9,10 +9,9 @@ import { MobileThemeVariables } from '@Root/Style/Themes/styled-components'
import { ApplicationGroupEvent, DeinitMode, DeinitSource } from '@standardnotes/snjs'
import { ThemeService, ThemeServiceContext } from '@Style/ThemeService'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { StatusBar } from 'react-native'
import { ThemeProvider } from 'styled-components/native'
import { ApplicationContext } from './ApplicationContext'
import { MainStackComponent } from './ModalStack'
import { NativeMainStackComponent } from './ModalStack'
export type HeaderTitleParams = {
title?: string
@@ -107,13 +106,12 @@ const AppComponent: React.FC<{
}}
ref={navigationRef}
>
<StatusBar translucent />
{themeService.current && (
<>
<ThemeProvider theme={activeTheme}>
<ActionSheetProvider>
<ThemeServiceContext.Provider value={themeService.current}>
<MainStackComponent env={env} />
<NativeMainStackComponent env={env} />
</ThemeServiceContext.Provider>
</ActionSheetProvider>
<ToastWrapper />
@@ -124,7 +122,7 @@ const AppComponent: React.FC<{
)
}
export const App = (props: { env: TEnvironment }) => {
export const NativeApp = (props: { env: TEnvironment }) => {
const [application, setApplication] = useState<MobileApplication | undefined>()
const createNewAppGroup = useCallback(() => {
@@ -1,5 +1,5 @@
import { AppStateType, PasscodeKeyboardType } from '@Lib/ApplicationState'
import { MobileDeviceInterface } from '@Lib/Interface'
import { MobileDevice } from '@Lib/Interface'
import { HeaderHeightContext } from '@react-navigation/elements'
import { useFocusEffect } from '@react-navigation/native'
import { ApplicationContext } from '@Root/ApplicationContext'
@@ -164,7 +164,7 @@ export const Authenticate = ({
}, [])
const checkForBiometrics = useCallback(
async () => (application?.deviceInterface as MobileDeviceInterface).getDeviceBiometricsAvailability(),
async () => (application?.deviceInterface as MobileDevice).getDeviceBiometricsAvailability(),
[application],
)
@@ -197,7 +197,7 @@ export const Authenticate = ({
state: AuthenticationValueStateType.Pending,
})
if (application?.getAppState().screenshotPrivacyEnabled) {
if (await application?.getMobileScreenshotPrivacyEnabled()) {
hide()
}
@@ -1,4 +1,4 @@
import { PasscodeKeyboardType, UnlockTiming } from '@Lib/ApplicationState'
import { PasscodeKeyboardType } from '@Lib/ApplicationState'
import { ApplicationContext } from '@Root/ApplicationContext'
import { ButtonCell } from '@Root/Components/ButtonCell'
import { Option, SectionedOptionsTableCell } from '@Root/Components/SectionedOptionsTableCell'
@@ -6,6 +6,7 @@ import { SectionedTableCell } from '@Root/Components/SectionedTableCell'
import { TableSection } from '@Root/Components/TableSection'
import { ModalStackNavigationProp } from '@Root/ModalStack'
import { SCREEN_INPUT_MODAL_PASSCODE } from '@Root/Screens/screens'
import { MobileUnlockTiming } from '@standardnotes/snjs'
import { ThemeServiceContext } from '@Style/ThemeService'
import React, { useContext, useMemo, useRef, useState } from 'react'
import { Keyboard, KeyboardType, Platform, TextInput } from 'react-native'
@@ -50,7 +51,7 @@ export const PasscodeInputModal = (props: Props) => {
} else {
await application?.addPasscode(text)
await application?.getAppState().setPasscodeKeyboardType(keyboardType as PasscodeKeyboardType)
await application?.getAppState().setPasscodeTiming(UnlockTiming.OnQuit)
await application?.getAppState().setPasscodeTiming(MobileUnlockTiming.OnQuit)
setSettingPassocode(false)
props.navigation.goBack()
}
@@ -8,10 +8,10 @@ import { SectionHeader } from '@Root/Components/SectionHeader'
import { TableSection } from '@Root/Components/TableSection'
import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext'
import { ModalStackNavigationProp } from '@Root/ModalStack'
import { SCREEN_MANAGE_SESSIONS, SCREEN_SETTINGS, SCREEN_WEB_APP } from '@Root/Screens/screens'
import { SCREEN_MANAGE_SESSIONS, SCREEN_SETTINGS } from '@Root/Screens/screens'
import { ButtonType, PrefKey, StorageValueModes } from '@standardnotes/snjs'
import moment from 'moment'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import { Platform } from 'react-native'
import DocumentPicker from 'react-native-document-picker'
import RNFS from 'react-native-fs'
@@ -183,22 +183,6 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
)
}, [application.alertService])
const [shouldAlwaysOpenWebAppOnLaunch, setShouldAlwaysOpenWebAppOnLaunch] = useState(false)
useEffect(() => {
const getSetting = async () => {
const value = (await application.getValue(AlwaysOpenWebAppOnLaunchKey, StorageValueModes.Nonwrapped)) as
| boolean
| undefined
setShouldAlwaysOpenWebAppOnLaunch(value ?? false)
}
void getSetting()
}, [application])
const openWebApp = useCallback(() => {
navigation.push(SCREEN_WEB_APP)
}, [navigation])
return (
<TableSection>
<SectionHeader title={title} />
@@ -238,15 +222,19 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
onPress={onExportPress}
/>
<ButtonCell testID="openWebApp" leftAligned title="Open Web App" onPress={() => openWebApp()} />
<SectionedAccessoryTableCell
onPress={() => {
const newValue = !shouldAlwaysOpenWebAppOnLaunch
setShouldAlwaysOpenWebAppOnLaunch(newValue)
void application.setValue(AlwaysOpenWebAppOnLaunchKey, newValue, StorageValueModes.Nonwrapped)
<ButtonCell
onPress={async () => {
const confirmationText =
'This will close the app and fully switch to the web view next time you open it. You will be able to switch back from the settings.'
if (
await application.alertService.confirm(confirmationText, 'Switch To Web View?', 'Switch', ButtonType.Info)
) {
application.setValue(AlwaysOpenWebAppOnLaunchKey, true, StorageValueModes.Nonwrapped)
setTimeout(() => application.deviceInterface.performSoftReset(), 1000)
}
}}
text="Always Open Web App On Launch"
selected={() => shouldAlwaysOpenWebAppOnLaunch}
title="Switch to Web View"
/>
{!signedIn && (
@@ -1,5 +1,4 @@
import { UnlockTiming } from '@Lib/ApplicationState'
import { MobileDeviceInterface } from '@Lib/Interface'
import { MobileDevice } from '@Lib/Interface'
import { useFocusEffect, useNavigation } from '@react-navigation/native'
import { ApplicationContext } from '@Root/ApplicationContext'
import { ButtonCell } from '@Root/Components/ButtonCell'
@@ -8,7 +7,7 @@ import { SectionHeader } from '@Root/Components/SectionHeader'
import { TableSection } from '@Root/Components/TableSection'
import { ModalStackNavigationProp } from '@Root/ModalStack'
import { SCREEN_INPUT_MODAL_PASSCODE, SCREEN_SETTINGS } from '@Root/Screens/screens'
import { StorageEncryptionPolicy } from '@standardnotes/snjs'
import { MobileUnlockTiming, StorageEncryptionPolicy } from '@standardnotes/snjs'
import React, { useCallback, useContext, useEffect, useState } from 'react'
import { Platform } from 'react-native'
import { Title } from './SecuritySection.styled'
@@ -32,16 +31,14 @@ export const SecuritySection = (props: Props) => {
const [hasBiometrics, setHasBiometrics] = useState(false)
const [supportsBiometrics, setSupportsBiometrics] = useState(false)
const [biometricsTimingOptions, setBiometricsTimingOptions] = useState(() =>
application!.getAppState().getBiometricsTimingOptions(),
)
const [passcodeTimingOptions, setPasscodeTimingOptions] = useState(() =>
application!.getAppState().getPasscodeTimingOptions(),
application!.getBiometricsTimingOptions(),
)
const [passcodeTimingOptions, setPasscodeTimingOptions] = useState(() => application!.getPasscodeTimingOptions())
useEffect(() => {
let mounted = true
const getHasScreenshotPrivacy = async () => {
const hasScreenshotPrivacyEnabled = await application?.getAppState().screenshotPrivacyEnabled
const hasScreenshotPrivacyEnabled = (await application?.getMobileScreenshotPrivacyEnabled()) ?? true
if (mounted) {
setHasScreenshotPrivacy(hasScreenshotPrivacyEnabled)
}
@@ -56,7 +53,7 @@ export const SecuritySection = (props: Props) => {
void getHasBiometrics()
const hasBiometricsSupport = async () => {
const hasBiometricsAvailable = await (
application?.deviceInterface as MobileDeviceInterface
application?.deviceInterface as MobileDevice
).getDeviceBiometricsAvailability()
if (mounted) {
setSupportsBiometrics(hasBiometricsAvailable)
@@ -71,7 +68,7 @@ export const SecuritySection = (props: Props) => {
useFocusEffect(
useCallback(() => {
if (props.hasPasscode) {
setPasscodeTimingOptions(() => application!.getAppState().getPasscodeTimingOptions())
setPasscodeTimingOptions(() => application!.getPasscodeTimingOptions())
}
}, [application, props.hasPasscode]),
)
@@ -127,14 +124,14 @@ export const SecuritySection = (props: Props) => {
const biometricTitle = hasBiometrics ? 'Disable Biometrics Lock' : 'Enable Biometrics Lock'
const setBiometricsTiming = async (timing: UnlockTiming) => {
const setBiometricsTiming = async (timing: MobileUnlockTiming) => {
await application?.getAppState().setBiometricsTiming(timing)
setBiometricsTimingOptions(() => application!.getAppState().getBiometricsTimingOptions())
setBiometricsTimingOptions(() => application!.getBiometricsTimingOptions())
}
const setPasscodeTiming = async (timing: UnlockTiming) => {
const setPasscodeTiming = async (timing: MobileUnlockTiming) => {
await application?.getAppState().setPasscodeTiming(timing)
setPasscodeTimingOptions(() => application!.getAppState().getPasscodeTimingOptions())
setPasscodeTimingOptions(() => application!.getPasscodeTimingOptions())
}
const onScreenshotPrivacyPress = async () => {
@@ -157,7 +154,7 @@ export const SecuritySection = (props: Props) => {
} else {
setHasBiometrics(true)
await application?.enableBiometrics()
await setBiometricsTiming(UnlockTiming.OnQuit)
await setBiometricsTiming(MobileUnlockTiming.OnQuit)
props.updateProtectionsAvailable()
}
}
@@ -231,7 +228,7 @@ export const SecuritySection = (props: Props) => {
leftAligned
title={'Require Passcode'}
options={passcodeTimingOptions}
onPress={(option: Option) => setPasscodeTiming(option.key as UnlockTiming)}
onPress={(option: Option) => setPasscodeTiming(option.key as MobileUnlockTiming)}
/>
)}
@@ -240,7 +237,7 @@ export const SecuritySection = (props: Props) => {
leftAligned
title={'Require Biometrics'}
options={biometricsTimingOptions}
onPress={(option: Option) => setBiometricsTiming(option.key as UnlockTiming)}
onPress={(option: Option) => setBiometricsTiming(option.key as MobileUnlockTiming)}
/>
)}
</TableSection>

Some files were not shown because too many files have changed in this diff Show More