feat: mobile web bridge (#1597)

This commit is contained in:
Mo
2022-09-19 14:47:15 -05:00
committed by GitHub
parent f80cc5b822
commit c4d7761496
28 changed files with 462 additions and 104 deletions

View File

@@ -17,12 +17,15 @@ import {
DecryptedItemInterface,
WebAppEvent,
WebApplicationInterface,
MobileDeviceInterface,
MobileUnlockTiming,
} from '@standardnotes/snjs'
import { makeObservable, observable } from 'mobx'
import { PanelResizedData } from '@/Types/PanelResizedData'
import { isDesktopApplication } from '@/Utils'
import { DesktopManager } from './Device/DesktopManager'
import { ArchiveManager, AutolockService, IOService, WebAlertService, ThemeManager } from '@standardnotes/ui-services'
import { MobileWebReceiver } from './MobileWebReceiver'
type WebServices = {
viewControllerManager: ViewControllerManager
@@ -41,6 +44,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
public itemControllerGroup: ItemGroupController
public iconsController: IconsController
private onVisibilityChange: () => void
private mobileWebReceiver?: MobileWebReceiver
constructor(
deviceInterface: WebOrDesktopDevice,
@@ -70,6 +74,10 @@ export class WebApplication extends SNApplication implements WebApplicationInter
this.itemControllerGroup = new ItemGroupController(this)
this.iconsController = new IconsController()
if (this.isNativeMobileWeb()) {
this.mobileWebReceiver = new MobileWebReceiver(this)
}
this.onVisibilityChange = () => {
const visible = document.visibilityState === 'visible'
const event = visible ? WebAppEvent.WindowDidFocus : WebAppEvent.WindowDidBlur
@@ -101,6 +109,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
this.itemControllerGroup.deinit()
;(this.itemControllerGroup as unknown) = undefined
;(this.mobileWebReceiver as unknown) = undefined
this.webEventObservers.length = 0
@@ -161,6 +170,13 @@ export class WebApplication extends SNApplication implements WebApplicationInter
return undefined
}
get mobileDevice(): MobileDeviceInterface {
if (!this.isNativeMobileWeb()) {
throw Error('Attempting to access device as mobile device on non mobile platform')
}
return this.deviceInterface as MobileDeviceInterface
}
public getThemeService() {
return this.webServices.themeService
}
@@ -203,4 +219,44 @@ export class WebApplication extends SNApplication implements WebApplicationInter
const currentValue = this.isGlobalSpellcheckEnabled()
return this.setPreference(PrefKey.EditorSpellcheck, !currentValue)
}
async handleMobileEnteringBackgroundEvent(): Promise<void> {
await this.lockApplicationAfterMobileEventIfApplicable()
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
async handleMobileGainingFocusEvent(): Promise<void> {}
async handleMobileLosingFocusEvent(): Promise<void> {
if (await this.getMobileScreenshotPrivacyEnabled()) {
this.mobileDevice.stopHidingMobileInterfaceFromScreenshots()
}
await this.lockApplicationAfterMobileEventIfApplicable()
}
async handleMobileResumingFromBackgroundEvent(): Promise<void> {
if (await this.getMobileScreenshotPrivacyEnabled()) {
this.mobileDevice.hideMobileInterfaceFromScreenshots()
}
}
private async lockApplicationAfterMobileEventIfApplicable(): Promise<void> {
const isLocked = await this.isLocked()
if (isLocked) {
return
}
const hasBiometrics = this.hasBiometrics()
const hasPasscode = this.hasPasscode()
const passcodeTiming = await this.getMobilePasscodeTiming()
const biometricsTiming = await this.getMobileBiometricsTiming()
const passcodeLockImmediately = hasPasscode && passcodeTiming === MobileUnlockTiming.Immediately
const biometricsLockImmediately = hasBiometrics && biometricsTiming === MobileUnlockTiming.Immediately
if (passcodeLockImmediately || biometricsLockImmediately) {
await this.lock()
}
}
}

View File

@@ -0,0 +1,61 @@
import { ReactNativeToWebEvent, WebApplicationInterface } from '@standardnotes/snjs'
export class MobileWebReceiver {
constructor(private application: WebApplicationInterface) {
this.listenForNativeMobileEvents()
}
deinit() {
;(this.application as unknown) = undefined
window.removeEventListener('message', this.handleNativeMobileWindowMessage)
document.removeEventListener('message', this.handleNativeMobileWindowMessage as never)
}
listenForNativeMobileEvents() {
const iOSEventRecipient = window
const androidEventRecipient = document
iOSEventRecipient.addEventListener('message', this.handleNativeMobileWindowMessage)
androidEventRecipient.addEventListener('message', this.handleNativeMobileWindowMessage as never)
}
handleNativeMobileWindowMessage = (event: MessageEvent) => {
const nullOrigin = event.origin === '' || event.origin == null
if (!nullOrigin) {
return
}
const message = (event as MessageEvent).data
try {
const parsed = JSON.parse(message)
const { messageType, reactNativeEvent } = parsed
if (messageType === 'event' && reactNativeEvent) {
const nativeEvent = reactNativeEvent as ReactNativeToWebEvent
this.handleNativeEvent(nativeEvent)
}
} catch (error) {
console.log('Error parsing message from React Native', error)
}
}
handleNativeEvent(event: ReactNativeToWebEvent) {
switch (event) {
case ReactNativeToWebEvent.EnteringBackground:
void this.application.handleMobileEnteringBackgroundEvent()
break
case ReactNativeToWebEvent.GainingFocus:
void this.application.handleMobileGainingFocusEvent()
break
case ReactNativeToWebEvent.LosingFocus:
void this.application.handleMobileLosingFocusEvent()
break
case ReactNativeToWebEvent.ResumingFromBackground:
void this.application.handleMobileResumingFromBackgroundEvent()
break
default:
break
}
}
}