mirror of
https://github.com/standardnotes/app
synced 2026-09-13 09:46:02 -04:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0642b589ff | ||
|
|
f7a1c6ea65 | ||
|
|
e8c7c10358 | ||
|
|
b3eab998f9 | ||
|
|
c76ffc764b | ||
|
|
5602a4014e | ||
|
|
0a544e936c | ||
|
|
027dd41147 | ||
|
|
2036689dd6 | ||
|
|
25596e70a8 | ||
|
|
9961f83246 | ||
|
|
6caf7c959a | ||
|
|
8ebb0949f7 | ||
|
|
846e5b3ca8 | ||
|
|
4b6bd452f7 | ||
|
|
66d26ff44b | ||
|
|
247daddf5a | ||
|
|
a0bc1d2488 |
@@ -47,7 +47,7 @@ Questions? Find answers on our [Help page](https://standardnotes.com/help).
|
||||
|
||||
### Docker setup
|
||||
|
||||
Docker is the quickest way to try out Standard Notes. We recommend using our official [Docker hub image](https://hub.docker.com/repository/docker/standardnotes/web).
|
||||
If you'd like to self-host the web application, we recommend using our official [Docker hub image](https://hub.docker.com/repository/docker/standardnotes/web).
|
||||
|
||||
```
|
||||
docker run -d -p 3001:3001 --env-file=.env.sample standardnotes/web:stable
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"build:services": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/services run build",
|
||||
"build:api": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/api run build",
|
||||
"e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
|
||||
"e2e:test": "yarn dlx mocha-headless-chrome --timeout 1800000 -f http://localhost:9001/mocha/test.html",
|
||||
"reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock && yarn install",
|
||||
"release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"",
|
||||
"publish:prod": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.26.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.26.39](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* exit interview discount link not working ([#2381](https://github.com/standardnotes/app/issues/2381)) ([027dd41](https://github.com/standardnotes/app/commit/027dd4114720d46cccf215a71c04b67f91315ddc))
|
||||
|
||||
## [1.26.38](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.26.37](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.26.37",
|
||||
"version": "1.26.40",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ export class UserApiService implements UserApiServiceInterface {
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericRegistrationFail)
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ import { FetchRequestHandler } from './FetchRequestHandler'
|
||||
import { HttpErrorResponseBody, HttpRequest } from '@standardnotes/responses'
|
||||
|
||||
import { ErrorMessage } from '../Error'
|
||||
import { LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
describe('FetchRequestHandler', () => {
|
||||
const snjsVersion = 'snjsVersion'
|
||||
const appVersion = 'appVersion'
|
||||
const environment = Environment.Web
|
||||
const requestHandler = new FetchRequestHandler(snjsVersion, appVersion, environment)
|
||||
const logger: LoggerInterface = {} as jest.Mocked<LoggerInterface>
|
||||
const requestHandler = new FetchRequestHandler(snjsVersion, appVersion, environment, logger)
|
||||
|
||||
it('should create a request', () => {
|
||||
const httpRequest: HttpRequest = {
|
||||
|
||||
@@ -11,12 +11,14 @@ import { RequestHandlerInterface } from './RequestHandlerInterface'
|
||||
import { Environment } from '@standardnotes/models'
|
||||
import { isString } from 'lodash'
|
||||
import { ErrorMessage } from '../Error'
|
||||
import { LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
export class FetchRequestHandler implements RequestHandlerInterface {
|
||||
constructor(
|
||||
protected readonly snjsVersion: string,
|
||||
protected readonly appVersion: string,
|
||||
protected readonly environment: Environment,
|
||||
private logger: LoggerInterface,
|
||||
) {}
|
||||
|
||||
async handleRequest<T>(httpRequest: HttpRequest): Promise<HttpResponse<T>> {
|
||||
@@ -122,7 +124,7 @@ export class FetchRequestHandler implements RequestHandlerInterface {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
this.logger.error(JSON.stringify(error))
|
||||
}
|
||||
|
||||
if (httpStatus >= HttpStatusCode.Success && httpStatus < HttpStatusCode.InternalServerError) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { joinPaths, sleep } from '@standardnotes/utils'
|
||||
import { LoggerInterface, joinPaths, sleep } from '@standardnotes/utils'
|
||||
import { Environment } from '@standardnotes/models'
|
||||
import { LegacySession, Session, SessionToken } from '@standardnotes/domain-core'
|
||||
import {
|
||||
@@ -21,6 +21,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
private session?: Session | LegacySession
|
||||
private __latencySimulatorMs?: number
|
||||
private declare host: string
|
||||
loggingEnabled = false
|
||||
|
||||
private inProgressRefreshSessionPromise?: Promise<boolean>
|
||||
private updateMetaCallback!: (meta: HttpResponseMeta) => void
|
||||
@@ -32,8 +33,9 @@ export class HttpService implements HttpServiceInterface {
|
||||
private environment: Environment,
|
||||
private appVersion: string,
|
||||
private snjsVersion: string,
|
||||
private logger: LoggerInterface,
|
||||
) {
|
||||
this.requestHandler = new FetchRequestHandler(this.snjsVersion, this.appVersion, this.environment)
|
||||
this.requestHandler = new FetchRequestHandler(this.snjsVersion, this.appVersion, this.environment, this.logger)
|
||||
}
|
||||
|
||||
setCallbacks(
|
||||
@@ -150,6 +152,10 @@ export class HttpService implements HttpServiceInterface {
|
||||
|
||||
const response = await this.requestHandler.handleRequest<T>(httpRequest)
|
||||
|
||||
if (this.loggingEnabled && isErrorResponse(response)) {
|
||||
this.logger.error('Request failed', httpRequest, response)
|
||||
}
|
||||
|
||||
if (response.meta && !httpRequest.external) {
|
||||
this.updateMetaCallback?.(response.meta)
|
||||
}
|
||||
@@ -175,7 +181,7 @@ export class HttpService implements HttpServiceInterface {
|
||||
return response
|
||||
}
|
||||
|
||||
private async refreshSession(): Promise<boolean> {
|
||||
async refreshSession(): Promise<boolean> {
|
||||
if (!this.session) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface HttpServiceInterface {
|
||||
runHttp<T>(httpRequest: HttpRequest): Promise<HttpResponse<T>>
|
||||
|
||||
setSession(session: Session | LegacySession): void
|
||||
refreshSession(): Promise<boolean>
|
||||
setCallbacks(
|
||||
updateMetaCallback: (meta: HttpResponseMeta) => void,
|
||||
refreshSessionCallback: (session: Session) => void,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './HttpService'
|
||||
export * from './FetchRequestHandler'
|
||||
export * from './HttpServiceInterface'
|
||||
export * from './XMLHttpRequestState'
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.1.135](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.134](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.133](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.132](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.131](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.130](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/clipper",
|
||||
"description": "Web clipper browser extension for Standard Notes",
|
||||
"version": "1.1.130",
|
||||
"version": "1.1.135",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { runtime, action, browserAction, windows, storage, tabs } from 'webextension-polyfill'
|
||||
import { ClipPayload, RuntimeMessage, RuntimeMessageTypes } from '../types/message'
|
||||
import { Environment, FetchRequestHandler, Logger, SnjsVersion } from '@standardnotes/snjs'
|
||||
import packageInfo from '../../package.json'
|
||||
|
||||
const isFirefox = navigator.userAgent.indexOf('Firefox/') !== -1
|
||||
|
||||
@@ -22,6 +24,9 @@ const openPopupAndClipSelection = async (payload: ClipPayload) => {
|
||||
void openPopup()
|
||||
}
|
||||
|
||||
const logger = new Logger('clipper')
|
||||
const requestHandler = new FetchRequestHandler(SnjsVersion, packageInfo.version, Environment.Clipper, logger)
|
||||
|
||||
runtime.onMessage.addListener(async (message: RuntimeMessage) => {
|
||||
if (message.type === RuntimeMessageTypes.OpenPopupWithSelection) {
|
||||
if (!message.payload) {
|
||||
@@ -32,5 +37,7 @@ runtime.onMessage.addListener(async (message: RuntimeMessage) => {
|
||||
return await tabs.captureVisibleTab(undefined, {
|
||||
format: 'png',
|
||||
})
|
||||
} else if (message.type === RuntimeMessageTypes.RunHttpRequest) {
|
||||
requestHandler.handleRequest(message.payload).catch(console.error)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { HttpRequest } from '@standardnotes/snjs'
|
||||
|
||||
export const RuntimeMessageTypes = {
|
||||
GetArticle: 'get-article',
|
||||
GetSelection: 'get-selection',
|
||||
@@ -7,6 +9,7 @@ export const RuntimeMessageTypes = {
|
||||
StartNodeSelection: 'start-node-selection',
|
||||
ToggleScreenshotMode: 'toggle-screenshot-mode',
|
||||
CaptureVisibleTab: 'capture-visible-tab',
|
||||
RunHttpRequest: 'run-http-request',
|
||||
} as const
|
||||
|
||||
export type RuntimeMessageType = (typeof RuntimeMessageTypes)[keyof typeof RuntimeMessageTypes]
|
||||
@@ -29,6 +32,7 @@ export type RuntimeMessageReturnTypes = {
|
||||
[RuntimeMessageTypes.OpenPopupWithSelection]: void
|
||||
[RuntimeMessageTypes.StartNodeSelection]: void
|
||||
[RuntimeMessageTypes.ToggleScreenshotMode]: void
|
||||
[RuntimeMessageTypes.RunHttpRequest]: void
|
||||
}
|
||||
|
||||
export type RuntimeMessage =
|
||||
@@ -36,10 +40,19 @@ export type RuntimeMessage =
|
||||
type: MessagesWithClipPayload
|
||||
payload: ClipPayload
|
||||
}
|
||||
| {
|
||||
type: typeof RuntimeMessageTypes.RunHttpRequest
|
||||
payload: HttpRequest
|
||||
}
|
||||
| {
|
||||
type: typeof RuntimeMessageTypes.ToggleScreenshotMode
|
||||
enabled: boolean
|
||||
}
|
||||
| {
|
||||
type: Exclude<RuntimeMessageType, MessagesWithClipPayload | typeof RuntimeMessageTypes.ToggleScreenshotMode>
|
||||
type: Exclude<
|
||||
RuntimeMessageType,
|
||||
| MessagesWithClipPayload
|
||||
| typeof RuntimeMessageTypes.ToggleScreenshotMode
|
||||
| typeof RuntimeMessageTypes.RunHttpRequest
|
||||
>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.108.66](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.65](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.64](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.63](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.62](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.61](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.108.61",
|
||||
"version": "3.108.66",
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.62](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.61](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.60](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.21.60",
|
||||
"version": "1.21.62",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.28.72](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.71](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.70](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.28.70",
|
||||
"version": "1.28.72",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.16.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.16.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.16.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.16.16",
|
||||
"version": "1.16.18",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
recursive: true
|
||||
timeout: 120000
|
||||
bail: true
|
||||
file:
|
||||
- e2e/init.js
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.56.46](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where moving the floating keyboard on iPad would lead to blank space on screen ([f7a1c6e](https://github.com/standardnotes/app/commit/f7a1c6ea65201af80ca6859c0e83c5b49c94e11d))
|
||||
|
||||
## [3.56.45](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.44](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.43](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.42](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.41](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.56.41",
|
||||
"version": "3.56.46",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { ApplicationEvent, ReactNativeToWebEvent } from '@standardnotes/snjs'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Button, Keyboard, Platform, Text, View } from 'react-native'
|
||||
import { Button, Dimensions, Keyboard, Platform, Text, View } from 'react-native'
|
||||
import VersionInfo from 'react-native-version-info'
|
||||
import { WebView, WebViewMessageEvent } from 'react-native-webview'
|
||||
import { OnShouldStartLoadWithRequest } from 'react-native-webview/lib/WebViewTypes'
|
||||
@@ -84,7 +84,11 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
|
||||
JSON.stringify({
|
||||
reactNativeEvent: ReactNativeToWebEvent.KeyboardFrameWillChange,
|
||||
messageType: 'event',
|
||||
messageData: { height: e.endCoordinates.height, contentHeight: e.endCoordinates.screenY },
|
||||
messageData: {
|
||||
height: e.endCoordinates.height,
|
||||
contentHeight: e.endCoordinates.screenY,
|
||||
isFloatingKeyboard: e.endCoordinates.width !== Dimensions.get('window').width,
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.46.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.46.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.46.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.46.19",
|
||||
"version": "1.46.21",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -2,4 +2,5 @@ export enum Environment {
|
||||
Web = 1,
|
||||
Desktop = 2,
|
||||
Mobile = 3,
|
||||
Clipper = 4,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.418](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.417](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.416](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.415](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.414](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.413](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.413",
|
||||
"version": "1.4.418",
|
||||
"license": "CC BY-NC-SA 4.0",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -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.13.36](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.13.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/responses",
|
||||
"version": "1.13.35",
|
||||
"version": "1.13.36",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ApiEndpointParam } from './ApiEndpointParam'
|
||||
import { ConflictParams } from './ConflictParams'
|
||||
import { ServerItemResponse } from './ServerItemResponse'
|
||||
import { SharedVaultServerHash } from '../SharedVaults/SharedVaultServerHash'
|
||||
import { UserEventServerHash } from '../UserEvent/UserEventServerHash'
|
||||
import { NotificationServerHash } from '../Notification/NotificationServerHash'
|
||||
import { AsymmetricMessageServerHash } from '../AsymmetricMessage/AsymmetricMessageServerHash'
|
||||
|
||||
export type RawSyncData = {
|
||||
@@ -16,7 +16,7 @@ export type RawSyncData = {
|
||||
unsaved?: ConflictParams[]
|
||||
shared_vaults?: SharedVaultServerHash[]
|
||||
shared_vault_invites?: SharedVaultInviteServerHash[]
|
||||
notifications?: UserEventServerHash[]
|
||||
notifications?: NotificationServerHash[]
|
||||
messages?: AsymmetricMessageServerHash[]
|
||||
status?: number
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export type UserEventServerHash = {
|
||||
export type NotificationServerHash = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
type: string
|
||||
@@ -66,4 +66,4 @@ export * from './User/PostSubscriptionTokensResponse'
|
||||
export * from './User/SettingData'
|
||||
export * from './User/UpdateSettingResponse'
|
||||
|
||||
export * from './UserEvent/UserEventServerHash'
|
||||
export * from './Notification/NotificationServerHash'
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.63.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.63.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.63.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.63.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.63.19",
|
||||
"version": "1.63.22",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -2,8 +2,8 @@ import { FilesApiInterface } from '@standardnotes/files'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { ApiServiceEvent } from './ApiServiceEvent'
|
||||
import { ApiServiceEventData } from './ApiServiceEventData'
|
||||
import { SNFeatureRepo } from '@standardnotes/models'
|
||||
import { ClientDisplayableError, HttpResponse } from '@standardnotes/responses'
|
||||
import { SNFeatureRepo, ServerSyncPushContextualPayload } from '@standardnotes/models'
|
||||
import { ClientDisplayableError, HttpRequest, HttpResponse } from '@standardnotes/responses'
|
||||
import { AnyFeatureDescription } from '@standardnotes/features'
|
||||
|
||||
export interface LegacyApiServiceInterface
|
||||
@@ -16,4 +16,12 @@ export interface LegacyApiServiceInterface
|
||||
): Promise<{ features: AnyFeatureDescription[]; roles: string[] } | ClientDisplayableError>
|
||||
|
||||
downloadFeatureUrl(url: string): Promise<HttpResponse>
|
||||
|
||||
getSyncHttpRequest(
|
||||
payloads: ServerSyncPushContextualPayload[],
|
||||
lastSyncToken: string | undefined,
|
||||
paginationToken: string | undefined,
|
||||
limit: number,
|
||||
sharedVaultUuids?: string[],
|
||||
): HttpRequest
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
KeySystemRootKeyContentSpecialized,
|
||||
TrustedContactInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
describe('AsymmetricMessageService', () => {
|
||||
let sync: jest.Mocked<SyncServiceInterface>
|
||||
@@ -61,6 +62,7 @@ describe('AsymmetricMessageService', () => {
|
||||
encryption,
|
||||
mutator,
|
||||
sessions,
|
||||
sync,
|
||||
messageServer,
|
||||
createOrEditContact,
|
||||
findContact,
|
||||
@@ -115,6 +117,45 @@ describe('AsymmetricMessageService', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('handleTrustedMessageResult', () => {
|
||||
it('should not double handle the same message', async () => {
|
||||
/**
|
||||
* Because message retrieval is based on a syncToken, and the server aligns syncTokens to items sent back
|
||||
* rather than messages, we may receive the same message twice. We want to keep track of processed messages
|
||||
* and avoid double processing.
|
||||
*/
|
||||
|
||||
const message: AsymmetricMessageServerHash = {
|
||||
uuid: 'message',
|
||||
recipient_uuid: '1',
|
||||
sender_uuid: '2',
|
||||
encrypted_message: 'encrypted_message',
|
||||
created_at_timestamp: 2,
|
||||
updated_at_timestamp: 2,
|
||||
}
|
||||
|
||||
const decryptedMessagePayload: AsymmetricMessageTrustedContactShare = {
|
||||
type: AsymmetricMessagePayloadType.ContactShare,
|
||||
data: {
|
||||
recipientUuid: '1',
|
||||
trustedContact: {} as TrustedContactInterface,
|
||||
},
|
||||
}
|
||||
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(Result.ok(decryptedMessagePayload))
|
||||
|
||||
service.handleTrustedContactShareMessage = jest.fn()
|
||||
await service.handleTrustedMessageResult(message, decryptedMessagePayload)
|
||||
expect(service.handleTrustedContactShareMessage).toHaveBeenCalledTimes(1)
|
||||
|
||||
service.handleTrustedContactShareMessage = jest.fn()
|
||||
await service.handleTrustedMessageResult(message, decryptedMessagePayload)
|
||||
expect(service.handleTrustedContactShareMessage).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
it('should process incoming messages oldest first', async () => {
|
||||
const messages: AsymmetricMessageServerHash[] = [
|
||||
{
|
||||
@@ -139,7 +180,7 @@ describe('AsymmetricMessageService', () => {
|
||||
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(trustedPayloadMock)
|
||||
.mockReturnValue(Result.ok(trustedPayloadMock))
|
||||
|
||||
const handleTrustedContactShareMessageMock = jest.fn()
|
||||
service.handleTrustedContactShareMessage = handleTrustedContactShareMessageMock
|
||||
@@ -171,7 +212,7 @@ describe('AsymmetricMessageService', () => {
|
||||
service.handleTrustedContactShareMessage = jest.fn()
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
.mockReturnValue(Result.ok(decryptedMessagePayload))
|
||||
|
||||
await service.handleRemoteReceivedAsymmetricMessages([message])
|
||||
|
||||
@@ -200,7 +241,7 @@ describe('AsymmetricMessageService', () => {
|
||||
service.handleTrustedSenderKeypairChangedMessage = jest.fn()
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
.mockReturnValue(Result.ok(decryptedMessagePayload))
|
||||
|
||||
await service.handleRemoteReceivedAsymmetricMessages([message])
|
||||
|
||||
@@ -228,7 +269,7 @@ describe('AsymmetricMessageService', () => {
|
||||
service.handleTrustedSharedVaultRootKeyChangedMessage = jest.fn()
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
.mockReturnValue(Result.ok(decryptedMessagePayload))
|
||||
|
||||
await service.handleRemoteReceivedAsymmetricMessages([message])
|
||||
|
||||
@@ -258,7 +299,7 @@ describe('AsymmetricMessageService', () => {
|
||||
service.handleTrustedVaultMetadataChangedMessage = jest.fn()
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
.mockReturnValue(Result.ok(decryptedMessagePayload))
|
||||
|
||||
await service.handleRemoteReceivedAsymmetricMessages([message])
|
||||
|
||||
@@ -284,7 +325,7 @@ describe('AsymmetricMessageService', () => {
|
||||
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
.mockReturnValue(Result.ok(decryptedMessagePayload))
|
||||
|
||||
await expect(service.handleRemoteReceivedAsymmetricMessages([message])).rejects.toThrow(
|
||||
'Shared vault invites payloads are not handled as part of asymmetric messages',
|
||||
@@ -313,7 +354,7 @@ describe('AsymmetricMessageService', () => {
|
||||
service.handleTrustedContactShareMessage = jest.fn()
|
||||
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
|
||||
.fn()
|
||||
.mockReturnValue(decryptedMessagePayload)
|
||||
.mockReturnValue(Result.ok(decryptedMessagePayload))
|
||||
|
||||
await service.handleRemoteReceivedAsymmetricMessages([message])
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SyncServiceInterface } from './../Sync/SyncServiceInterface'
|
||||
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { SyncEvent, SyncEventReceivedAsymmetricMessagesData } from '../Event/SyncEvent'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
|
||||
@@ -20,7 +21,6 @@ import {
|
||||
VaultListingInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { HandleRootKeyChangedMessage } from './UseCase/HandleRootKeyChangedMessage'
|
||||
import { SessionEvent } from '../Session/SessionEvent'
|
||||
import { AsymmetricMessageServer } from '@standardnotes/api'
|
||||
import { GetOutboundMessages } from './UseCase/GetOutboundMessages'
|
||||
import { GetInboundMessages } from './UseCase/GetInboundMessages'
|
||||
@@ -31,15 +31,19 @@ import { FindContact } from '../Contacts/UseCase/FindContact'
|
||||
import { CreateOrEditContact } from '../Contacts/UseCase/CreateOrEditContact'
|
||||
import { ReplaceContactData } from '../Contacts/UseCase/ReplaceContactData'
|
||||
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
export class AsymmetricMessageService
|
||||
extends AbstractService
|
||||
implements AsymmetricMessageServiceInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private handledMessages = new Set<string>()
|
||||
|
||||
constructor(
|
||||
private encryption: EncryptionProviderInterface,
|
||||
private mutator: MutatorClientInterface,
|
||||
private sessions: SessionsClientInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
private messageServer: AsymmetricMessageServer,
|
||||
private _createOrEditContact: CreateOrEditContact,
|
||||
private _findContact: FindContact,
|
||||
@@ -73,30 +77,27 @@ export class AsymmetricMessageService
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
switch (event.type) {
|
||||
case SessionEvent.UserKeyPairChanged:
|
||||
void this.messageServer.deleteAllInboundMessages()
|
||||
break
|
||||
case SyncEvent.ReceivedAsymmetricMessages:
|
||||
void this.handleRemoteReceivedAsymmetricMessages(event.payload as SyncEventReceivedAsymmetricMessagesData)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public async getOutboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
|
||||
public async getOutboundMessages(): Promise<Result<AsymmetricMessageServerHash[]>> {
|
||||
return this._getOutboundMessagesUseCase.execute()
|
||||
}
|
||||
|
||||
public async getInboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
|
||||
public async getInboundMessages(): Promise<Result<AsymmetricMessageServerHash[]>> {
|
||||
return this._getInboundMessagesUseCase.execute()
|
||||
}
|
||||
|
||||
public async downloadAndProcessInboundMessages(): Promise<void> {
|
||||
const messages = await this.getInboundMessages()
|
||||
if (isClientDisplayableError(messages)) {
|
||||
if (messages.isFailed()) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.handleRemoteReceivedAsymmetricMessages(messages)
|
||||
await this.handleRemoteReceivedAsymmetricMessages(messages.getValue())
|
||||
}
|
||||
|
||||
sortServerMessages(messages: AsymmetricMessageServerHash[]): AsymmetricMessageServerHash[] {
|
||||
@@ -143,11 +144,11 @@ export class AsymmetricMessageService
|
||||
getServerMessageType(message: AsymmetricMessageServerHash): AsymmetricMessagePayloadType | undefined {
|
||||
const result = this.getUntrustedMessagePayload(message)
|
||||
|
||||
if (!result) {
|
||||
if (result.isFailed()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return result.type
|
||||
return result.getValue().type
|
||||
}
|
||||
|
||||
async handleRemoteReceivedAsymmetricMessages(messages: AsymmetricMessageServerHash[]): Promise<void> {
|
||||
@@ -159,18 +160,26 @@ export class AsymmetricMessageService
|
||||
|
||||
for (const message of sortedMessages) {
|
||||
const trustedPayload = this.getTrustedMessagePayload(message)
|
||||
if (!trustedPayload) {
|
||||
if (trustedPayload.isFailed()) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this.handleTrustedMessageResult(message, trustedPayload)
|
||||
await this.handleTrustedMessageResult(message, trustedPayload.getValue())
|
||||
}
|
||||
|
||||
void this.sync.sync()
|
||||
}
|
||||
|
||||
private async handleTrustedMessageResult(
|
||||
async handleTrustedMessageResult(
|
||||
message: AsymmetricMessageServerHash,
|
||||
payload: AsymmetricMessagePayload,
|
||||
): Promise<void> {
|
||||
if (this.handledMessages.has(message.uuid)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.handledMessages.add(message.uuid)
|
||||
|
||||
if (payload.type === AsymmetricMessagePayloadType.ContactShare) {
|
||||
await this.handleTrustedContactShareMessage(message, payload)
|
||||
} else if (payload.type === AsymmetricMessagePayloadType.SenderKeypairChanged) {
|
||||
@@ -186,23 +195,23 @@ export class AsymmetricMessageService
|
||||
await this.deleteMessageAfterProcessing(message)
|
||||
}
|
||||
|
||||
getUntrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined {
|
||||
getUntrustedMessagePayload(message: AsymmetricMessageServerHash): Result<AsymmetricMessagePayload> {
|
||||
const result = this._getUntrustedPayload.execute({
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
message,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return undefined
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
|
||||
return result.getValue()
|
||||
return result
|
||||
}
|
||||
|
||||
getTrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined {
|
||||
getTrustedMessagePayload(message: AsymmetricMessageServerHash): Result<AsymmetricMessagePayload> {
|
||||
const contact = this._findContact.execute({ userUuid: message.sender_uuid })
|
||||
if (contact.isFailed()) {
|
||||
return undefined
|
||||
return Result.fail(contact.getError())
|
||||
}
|
||||
|
||||
const result = this._getTrustedPayload.execute({
|
||||
@@ -213,10 +222,10 @@ export class AsymmetricMessageService
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return undefined
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
|
||||
return result.getValue()
|
||||
return result
|
||||
}
|
||||
|
||||
async deleteMessageAfterProcessing(message: AsymmetricMessageServerHash): Promise<void> {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { AsymmetricMessageServerHash, ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
|
||||
export interface AsymmetricMessageServiceInterface {
|
||||
getOutboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError>
|
||||
getInboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError>
|
||||
getOutboundMessages(): Promise<Result<AsymmetricMessageServerHash[]>>
|
||||
getInboundMessages(): Promise<Result<AsymmetricMessageServerHash[]>>
|
||||
downloadAndProcessInboundMessages(): Promise<void>
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { ClientDisplayableError, isErrorResponse, AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { isErrorResponse, AsymmetricMessageServerHash, getErrorFromErrorResponse } from '@standardnotes/responses'
|
||||
import { AsymmetricMessageServerInterface } from '@standardnotes/api'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export class GetInboundMessages {
|
||||
export class GetInboundMessages implements UseCaseInterface<AsymmetricMessageServerHash[]> {
|
||||
constructor(private messageServer: AsymmetricMessageServerInterface) {}
|
||||
|
||||
async execute(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
|
||||
async execute(): Promise<Result<AsymmetricMessageServerHash[]>> {
|
||||
const response = await this.messageServer.getMessages()
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromNetworkError(response)
|
||||
return Result.fail(getErrorFromErrorResponse(response).message)
|
||||
}
|
||||
|
||||
return response.data.messages
|
||||
return Result.ok(response.data.messages)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { ClientDisplayableError, isErrorResponse, AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { isErrorResponse, AsymmetricMessageServerHash, getErrorFromErrorResponse } from '@standardnotes/responses'
|
||||
import { AsymmetricMessageServerInterface } from '@standardnotes/api'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export class GetOutboundMessages {
|
||||
export class GetOutboundMessages implements UseCaseInterface<AsymmetricMessageServerHash[]> {
|
||||
constructor(private messageServer: AsymmetricMessageServerInterface) {}
|
||||
|
||||
async execute(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError> {
|
||||
async execute(): Promise<Result<AsymmetricMessageServerHash[]>> {
|
||||
const response = await this.messageServer.getOutboundUserMessages()
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromNetworkError(response)
|
||||
return Result.fail(getErrorFromErrorResponse(response).message)
|
||||
}
|
||||
|
||||
return response.data.messages
|
||||
return Result.ok(response.data.messages)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { ResendAllMessages } from './ResendAllMessages'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { AsymmetricMessagePayloadType } from '@standardnotes/models'
|
||||
|
||||
describe('ResendAllMessages', () => {
|
||||
let mockDecryptOwnMessage: any
|
||||
let mockMessageServer: any
|
||||
let mockResendMessage: any
|
||||
let mockFindContact: any
|
||||
|
||||
let useCase: ResendAllMessages
|
||||
let params: {
|
||||
keys: { encryption: PkcKeyPair; signing: PkcKeyPair }
|
||||
previousKeys?: { encryption: PkcKeyPair; signing: PkcKeyPair }
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
mockDecryptOwnMessage = {
|
||||
execute: jest.fn(),
|
||||
}
|
||||
|
||||
mockMessageServer = {
|
||||
getOutboundUserMessages: jest.fn(),
|
||||
deleteMessage: jest.fn(),
|
||||
}
|
||||
|
||||
mockResendMessage = {
|
||||
execute: jest.fn(),
|
||||
}
|
||||
|
||||
mockFindContact = {
|
||||
execute: jest.fn(),
|
||||
}
|
||||
|
||||
useCase = new ResendAllMessages(mockResendMessage, mockDecryptOwnMessage, mockMessageServer, mockFindContact)
|
||||
params = {
|
||||
keys: {
|
||||
encryption: { publicKey: 'new_public_key', privateKey: 'new_private_key' },
|
||||
signing: { publicKey: 'new_public_key', privateKey: 'new_private_key' },
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
it('should successfully resend all messages', async () => {
|
||||
const messages = {
|
||||
data: { messages: [{ recipient_uuid: 'uuid', uuid: 'uuid', encrypted_message: 'encrypted_message' }] },
|
||||
}
|
||||
const recipient = { publicKeySet: { encryption: 'public_key' } }
|
||||
const decryptedMessage = { type: AsymmetricMessagePayloadType.ContactShare }
|
||||
|
||||
mockMessageServer.getOutboundUserMessages.mockReturnValue(messages)
|
||||
mockFindContact.execute.mockReturnValue(Result.ok(recipient))
|
||||
mockDecryptOwnMessage.execute.mockReturnValue(Result.ok(decryptedMessage))
|
||||
|
||||
const result = await useCase.execute(params)
|
||||
|
||||
expect(result).toEqual(Result.ok())
|
||||
expect(mockMessageServer.getOutboundUserMessages).toHaveBeenCalled()
|
||||
expect(mockFindContact.execute).toHaveBeenCalled()
|
||||
expect(mockDecryptOwnMessage.execute).toHaveBeenCalled()
|
||||
expect(mockResendMessage.execute).toHaveBeenCalled()
|
||||
expect(mockMessageServer.deleteMessage).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle errors while getting outbound user messages', async () => {
|
||||
mockMessageServer.getOutboundUserMessages.mockReturnValue({ data: { error: 'Error' } })
|
||||
|
||||
const result = await useCase.execute(params)
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toBe('Failed to get outbound user messages')
|
||||
})
|
||||
|
||||
it('should handle errors while finding contact', async () => {
|
||||
const messages = {
|
||||
data: { messages: [{ recipient_uuid: 'uuid', uuid: 'uuid', encrypted_message: 'encrypted_message' }] },
|
||||
}
|
||||
|
||||
mockMessageServer.getOutboundUserMessages.mockReturnValue(messages)
|
||||
mockFindContact.execute.mockReturnValue(Result.fail('Contact not found'))
|
||||
|
||||
const result = await useCase.execute(params)
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toContain('Contact not found')
|
||||
})
|
||||
|
||||
it('should skip messages of excluded types', async () => {
|
||||
const messages = {
|
||||
data: {
|
||||
messages: [
|
||||
{ recipient_uuid: 'uuid', uuid: 'uuid', encrypted_message: 'encrypted_message' },
|
||||
{ recipient_uuid: 'uuid2', uuid: 'uuid2', encrypted_message: 'encrypted_message2' },
|
||||
],
|
||||
},
|
||||
}
|
||||
const recipient = { publicKeySet: { encryption: 'public_key' } }
|
||||
const decryptedMessage1 = { type: AsymmetricMessagePayloadType.SenderKeypairChanged }
|
||||
const decryptedMessage2 = { type: AsymmetricMessagePayloadType.ContactShare }
|
||||
|
||||
mockMessageServer.getOutboundUserMessages.mockReturnValue(messages)
|
||||
mockFindContact.execute.mockReturnValue(Result.ok(recipient))
|
||||
|
||||
mockDecryptOwnMessage.execute
|
||||
.mockReturnValueOnce(Result.ok(decryptedMessage1))
|
||||
.mockReturnValueOnce(Result.ok(decryptedMessage2))
|
||||
|
||||
const result = await useCase.execute(params)
|
||||
|
||||
expect(result).toEqual(Result.ok())
|
||||
expect(mockMessageServer.getOutboundUserMessages).toHaveBeenCalled()
|
||||
expect(mockFindContact.execute).toHaveBeenCalledTimes(2)
|
||||
expect(mockDecryptOwnMessage.execute).toHaveBeenCalledTimes(2)
|
||||
expect(mockResendMessage.execute).toHaveBeenCalledTimes(1)
|
||||
expect(mockMessageServer.deleteMessage).toHaveBeenCalledTimes(1)
|
||||
expect(mockResendMessage.execute).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ rawMessage: messages.data.messages[1] }),
|
||||
)
|
||||
expect(mockMessageServer.deleteMessage).toHaveBeenCalledWith({ messageUuid: messages.data.messages[1].uuid })
|
||||
})
|
||||
})
|
||||
@@ -1,17 +1,28 @@
|
||||
import { DecryptOwnMessage } from './../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
|
||||
import { AsymmetricMessageServerHash, isErrorResponse } from '@standardnotes/responses'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { AsymmetricMessageServerInterface } from '@standardnotes/api'
|
||||
import { ResendMessage } from './ResendMessage'
|
||||
import { FindContact } from '../../Contacts/UseCase/FindContact'
|
||||
import { AsymmetricMessagePayload, AsymmetricMessagePayloadType } from '@standardnotes/models'
|
||||
|
||||
export class ResendAllMessages implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private resendMessage: ResendMessage,
|
||||
private decryptOwnMessage: DecryptOwnMessage<AsymmetricMessagePayload>,
|
||||
private messageServer: AsymmetricMessageServerInterface,
|
||||
private findContact: FindContact,
|
||||
) {}
|
||||
|
||||
messagesToExcludeFromResending(): AsymmetricMessagePayloadType[] {
|
||||
/**
|
||||
* Sender key pair changed messages should never be re-encrypted with new keys as they must use the
|
||||
* previous keys used by the sender before their keypair changed.
|
||||
*/
|
||||
return [AsymmetricMessagePayloadType.SenderKeypairChanged]
|
||||
}
|
||||
|
||||
async execute(params: {
|
||||
keys: {
|
||||
encryption: PkcKeyPair
|
||||
@@ -37,10 +48,27 @@ export class ResendAllMessages implements UseCaseInterface<void> {
|
||||
continue
|
||||
}
|
||||
|
||||
const decryptionResult = this.decryptOwnMessage.execute({
|
||||
message: message.encrypted_message,
|
||||
privateKey: params.previousKeys?.encryption.privateKey ?? params.keys.encryption.privateKey,
|
||||
recipientPublicKey: recipient.getValue().publicKeySet.encryption,
|
||||
})
|
||||
|
||||
if (decryptionResult.isFailed()) {
|
||||
errors.push(`Failed to decrypt message ${message.uuid}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const decryptedMessage = decryptionResult.getValue()
|
||||
if (this.messagesToExcludeFromResending().includes(decryptedMessage.type)) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this.resendMessage.execute({
|
||||
keys: params.keys,
|
||||
previousKeys: params.previousKeys,
|
||||
message: message,
|
||||
decryptedMessage: decryptedMessage,
|
||||
rawMessage: message,
|
||||
recipient: recipient.getValue(),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
|
||||
import { AsymmetricMessagePayload, TrustedContactInterface } from '@standardnotes/models'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
@@ -8,7 +7,6 @@ import { SendMessage } from './SendMessage'
|
||||
|
||||
export class ResendMessage implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private decryptOwnMessage: DecryptOwnMessage<AsymmetricMessagePayload>,
|
||||
private sendMessage: SendMessage,
|
||||
private encryptMessage: EncryptMessage,
|
||||
) {}
|
||||
@@ -23,22 +21,11 @@ export class ResendMessage implements UseCaseInterface<void> {
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
recipient: TrustedContactInterface
|
||||
message: AsymmetricMessageServerHash
|
||||
rawMessage: AsymmetricMessageServerHash
|
||||
decryptedMessage: AsymmetricMessagePayload
|
||||
}): Promise<Result<AsymmetricMessageServerHash>> {
|
||||
const decryptionResult = this.decryptOwnMessage.execute({
|
||||
message: params.message.encrypted_message,
|
||||
privateKey: params.previousKeys?.encryption.privateKey ?? params.keys.encryption.privateKey,
|
||||
recipientPublicKey: params.recipient.publicKeySet.encryption,
|
||||
})
|
||||
|
||||
if (decryptionResult.isFailed()) {
|
||||
return Result.fail(decryptionResult.getError())
|
||||
}
|
||||
|
||||
const decryptedMessage = decryptionResult.getValue()
|
||||
|
||||
const encryptedMessage = this.encryptMessage.execute({
|
||||
message: decryptedMessage,
|
||||
message: params.decryptedMessage,
|
||||
keys: params.keys,
|
||||
recipientPublicKey: params.recipient.publicKeySet.encryption,
|
||||
})
|
||||
@@ -50,7 +37,7 @@ export class ResendMessage implements UseCaseInterface<void> {
|
||||
const sendMessageResult = await this.sendMessage.execute({
|
||||
recipientUuid: params.recipient.contactUuid,
|
||||
encryptedMessage: encryptedMessage.getValue(),
|
||||
replaceabilityIdentifier: params.message.replaceabilityIdentifier,
|
||||
replaceabilityIdentifier: params.rawMessage.replaceabilityIdentifier,
|
||||
})
|
||||
|
||||
return sendMessageResult
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage'
|
||||
import { DeleteContact } from './UseCase/DeleteContact'
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData'
|
||||
import { SessionEvent } from './../Session/SessionEvent'
|
||||
import { InternalEventInterface } from './../Internal/InternalEventInterface'
|
||||
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { SharedVaultInviteServerHash, SharedVaultUserServerHash } from '@standardnotes/responses'
|
||||
import { TrustedContactInterface, TrustedContactMutator, DecryptedItemInterface } from '@standardnotes/models'
|
||||
@@ -25,10 +20,7 @@ import { GetAllContacts } from './UseCase/GetAllContacts'
|
||||
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
export class ContactService
|
||||
extends AbstractService<ContactServiceEvent>
|
||||
implements ContactServiceInterface, InternalEventHandlerInterface
|
||||
{
|
||||
export class ContactService extends AbstractService<ContactServiceEvent> implements ContactServiceInterface {
|
||||
constructor(
|
||||
private sync: SyncServiceInterface,
|
||||
private mutator: MutatorClientInterface,
|
||||
@@ -43,48 +35,25 @@ export class ContactService
|
||||
private _createOrEditContact: CreateOrEditContact,
|
||||
private _editContact: EditContact,
|
||||
private _validateItemSigner: ValidateItemSigner,
|
||||
private _sendOwnContactChangedMessage: SendOwnContactChangeMessage,
|
||||
eventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(eventBus)
|
||||
|
||||
eventBus.addEventHandler(this, SessionEvent.UserKeyPairChanged)
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === SessionEvent.UserKeyPairChanged) {
|
||||
const data = event.payload as UserKeyPairChangedEventData
|
||||
await this.selfContactManager.updateWithNewPublicKeySet({
|
||||
encryption: data.current.encryption.publicKey,
|
||||
signing: data.current.signing.publicKey,
|
||||
})
|
||||
void this.sendOwnContactChangeEventToAllContacts(event.payload as UserKeyPairChangedEventData)
|
||||
}
|
||||
}
|
||||
|
||||
private async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise<void> {
|
||||
if (!data.previous) {
|
||||
return
|
||||
}
|
||||
|
||||
const contacts = this._getAllContacts.execute()
|
||||
if (contacts.isFailed()) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const contact of contacts.getValue()) {
|
||||
if (contact.isMe) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this._sendOwnContactChangedMessage.execute({
|
||||
senderOldKeyPair: data.previous.encryption,
|
||||
senderOldSigningKeyPair: data.previous.signing,
|
||||
senderNewKeyPair: data.current.encryption,
|
||||
senderNewSigningKeyPair: data.current.signing,
|
||||
contact,
|
||||
})
|
||||
}
|
||||
override deinit(): void {
|
||||
super.deinit()
|
||||
;(this.sync as unknown) = undefined
|
||||
;(this.mutator as unknown) = undefined
|
||||
;(this.session as unknown) = undefined
|
||||
;(this.crypto as unknown) = undefined
|
||||
;(this.user as unknown) = undefined
|
||||
;(this.selfContactManager as unknown) = undefined
|
||||
;(this.encryption as unknown) = undefined
|
||||
;(this._findContact as unknown) = undefined
|
||||
;(this._getAllContacts as unknown) = undefined
|
||||
;(this._createOrEditContact as unknown) = undefined
|
||||
;(this._editContact as unknown) = undefined
|
||||
;(this._validateItemSigner as unknown) = undefined
|
||||
}
|
||||
|
||||
getSelfContact(): TrustedContactInterface | undefined {
|
||||
@@ -183,6 +152,8 @@ export class ContactService
|
||||
): Promise<TrustedContactInterface> {
|
||||
const updatedContact = await this._editContact.execute(contact, params)
|
||||
|
||||
void this.sync.sync()
|
||||
|
||||
return updatedContact
|
||||
}
|
||||
|
||||
@@ -194,6 +165,9 @@ export class ContactService
|
||||
isMe?: boolean
|
||||
}): Promise<TrustedContactInterface | undefined> {
|
||||
const contact = await this._createOrEditContact.execute(params)
|
||||
|
||||
void this.sync.sync()
|
||||
|
||||
return contact
|
||||
}
|
||||
|
||||
@@ -233,20 +207,4 @@ export class ContactService
|
||||
getItemSignatureStatus(item: DecryptedItemInterface): ItemSignatureValidationResult {
|
||||
return this._validateItemSigner.execute(item)
|
||||
}
|
||||
|
||||
override deinit(): void {
|
||||
super.deinit()
|
||||
;(this.sync as unknown) = undefined
|
||||
;(this.mutator as unknown) = undefined
|
||||
;(this.session as unknown) = undefined
|
||||
;(this.crypto as unknown) = undefined
|
||||
;(this.user as unknown) = undefined
|
||||
;(this.selfContactManager as unknown) = undefined
|
||||
;(this.encryption as unknown) = undefined
|
||||
;(this._findContact as unknown) = undefined
|
||||
;(this._getAllContacts as unknown) = undefined
|
||||
;(this._createOrEditContact as unknown) = undefined
|
||||
;(this._editContact as unknown) = undefined
|
||||
;(this._validateItemSigner as unknown) = undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@ import {
|
||||
TrustedContactContent,
|
||||
TrustedContactContentSpecialized,
|
||||
TrustedContactInterface,
|
||||
PortablePublicKeySet,
|
||||
} from '@standardnotes/models'
|
||||
import { CreateOrEditContact } from './UseCase/CreateOrEditContact'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
|
||||
const SelfContactName = 'Me'
|
||||
@@ -35,7 +33,6 @@ export class SelfContactManager implements InternalEventHandlerInterface {
|
||||
items: ItemManagerInterface,
|
||||
private session: SessionsClientInterface,
|
||||
private singletons: SingletonManagerInterface,
|
||||
private createOrEditContact: CreateOrEditContact,
|
||||
) {
|
||||
this.eventDisposers.push(
|
||||
sync.addEventObserver((event) => {
|
||||
@@ -82,23 +79,6 @@ export class SelfContactManager implements InternalEventHandlerInterface {
|
||||
)
|
||||
}
|
||||
|
||||
public async updateWithNewPublicKeySet(publicKeySet: PortablePublicKeySet) {
|
||||
if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.selfContact) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.createOrEditContact.execute({
|
||||
name: SelfContactName,
|
||||
contactUuid: this.selfContact.contactUuid,
|
||||
publicKey: publicKeySet.encryption,
|
||||
signingPublicKey: publicKeySet.signing,
|
||||
})
|
||||
}
|
||||
|
||||
private async reloadSelfContactAndCreateIfNecessary() {
|
||||
if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
|
||||
return
|
||||
@@ -146,6 +126,5 @@ export class SelfContactManager implements InternalEventHandlerInterface {
|
||||
this.eventDisposers.forEach((disposer) => disposer())
|
||||
;(this.session as unknown) = undefined
|
||||
;(this.singletons as unknown) = undefined
|
||||
;(this.createOrEditContact as unknown) = undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
|
||||
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
|
||||
import {
|
||||
ContactPublicKeySet,
|
||||
@@ -15,7 +14,6 @@ import { ContentType } from '@standardnotes/domain-core'
|
||||
export class CreateOrEditContact {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
private findContact: FindContact,
|
||||
private editContact: EditContact,
|
||||
) {}
|
||||
@@ -54,8 +52,6 @@ export class CreateOrEditContact {
|
||||
true,
|
||||
)
|
||||
|
||||
await this.sync.sync()
|
||||
|
||||
return contact
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
|
||||
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
|
||||
import { TrustedContactInterface, TrustedContactMutator } from '@standardnotes/models'
|
||||
|
||||
export class EditContact {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
) {}
|
||||
constructor(private mutator: MutatorClientInterface) {}
|
||||
|
||||
async execute(
|
||||
contact: TrustedContactInterface,
|
||||
@@ -28,8 +24,6 @@ export class EditContact {
|
||||
},
|
||||
)
|
||||
|
||||
await this.sync.sync()
|
||||
|
||||
return updatedContact
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,20 @@ export class FindContact implements SyncUseCaseInterface<TrustedContactInterface
|
||||
|
||||
execute(query: FindContactQuery): Result<TrustedContactInterface> {
|
||||
if ('userUuid' in query && query.userUuid) {
|
||||
const contact = this.items.itemsMatchingPredicate<TrustedContactInterface>(
|
||||
const contacts = this.items.itemsMatchingPredicate<TrustedContactInterface>(
|
||||
ContentType.TYPES.TrustedContact,
|
||||
new Predicate<TrustedContactInterface>('contactUuid', '=', query.userUuid),
|
||||
)[0]
|
||||
)
|
||||
|
||||
if (contact) {
|
||||
return Result.ok(contact)
|
||||
} else {
|
||||
return Result.fail('Contact not found')
|
||||
if (contacts.length === 0) {
|
||||
return Result.fail(`Contact not found for user ${query.userUuid}`)
|
||||
}
|
||||
|
||||
if (contacts.length > 1) {
|
||||
return Result.fail(`Multiple contacts found for user ${query.userUuid}`)
|
||||
}
|
||||
|
||||
return Result.ok(contacts[0])
|
||||
}
|
||||
|
||||
if ('signingPublicKey' in query && query.signingPublicKey) {
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { HandleKeyPairChange } from './HandleKeyPairChange'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
describe('HandleKeyPairChange', () => {
|
||||
let useCase: HandleKeyPairChange
|
||||
let mockSelfContactManager: any
|
||||
let mockInvitesServer: any
|
||||
let mockMessageServer: any
|
||||
let mockReuploadAllInvites: any
|
||||
let mockResendAllMessages: any
|
||||
let mockGetAllContacts: any
|
||||
let mockCreateOrEditContact: any
|
||||
let mockSendOwnContactChangedMessage: any
|
||||
let logger: LoggerInterface
|
||||
|
||||
const dto = {
|
||||
newKeys: {
|
||||
encryption: <PkcKeyPair>{
|
||||
publicKey: 'new-encryption-public-key',
|
||||
privateKey: 'new-encryption-private-key',
|
||||
},
|
||||
signing: <PkcKeyPair>{
|
||||
publicKey: 'new-signing-public-key',
|
||||
privateKey: 'new-signing-private-key',
|
||||
},
|
||||
},
|
||||
previousKeys: {
|
||||
encryption: <PkcKeyPair>{
|
||||
publicKey: 'previous-encryption-public-key',
|
||||
privateKey: 'previous-encryption-private-key',
|
||||
},
|
||||
signing: <PkcKeyPair>{
|
||||
publicKey: 'previous-signing-public-key',
|
||||
privateKey: 'previous-signing-private-key',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockSelfContactManager = {
|
||||
updateWithNewPublicKeySet: jest.fn().mockReturnValue({}),
|
||||
}
|
||||
|
||||
mockInvitesServer = {
|
||||
deleteAllInboundInvites: jest.fn().mockReturnValue({}),
|
||||
}
|
||||
|
||||
mockMessageServer = {
|
||||
deleteAllInboundMessages: jest.fn().mockReturnValue({}),
|
||||
}
|
||||
|
||||
mockReuploadAllInvites = {
|
||||
execute: jest.fn().mockReturnValue(Result.ok()),
|
||||
}
|
||||
|
||||
mockResendAllMessages = {
|
||||
execute: jest.fn().mockReturnValue(Result.ok()),
|
||||
}
|
||||
|
||||
mockGetAllContacts = {
|
||||
execute: jest.fn().mockReturnValue(Result.ok()),
|
||||
}
|
||||
|
||||
mockSendOwnContactChangedMessage = {
|
||||
execute: jest.fn().mockReturnValue(Result.ok()),
|
||||
}
|
||||
|
||||
mockCreateOrEditContact = {
|
||||
execute: jest.fn().mockReturnValue(Result.ok()),
|
||||
}
|
||||
|
||||
logger = {} as jest.Mocked<LoggerInterface>
|
||||
logger.error = jest.fn()
|
||||
|
||||
useCase = new HandleKeyPairChange(
|
||||
mockSelfContactManager,
|
||||
mockInvitesServer,
|
||||
mockMessageServer,
|
||||
mockReuploadAllInvites,
|
||||
mockResendAllMessages,
|
||||
mockGetAllContacts,
|
||||
mockSendOwnContactChangedMessage,
|
||||
mockCreateOrEditContact,
|
||||
logger,
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle key pair change correctly', async () => {
|
||||
mockGetAllContacts.execute.mockReturnValue(Result.ok([]))
|
||||
|
||||
const result = await useCase.execute(dto)
|
||||
|
||||
expect(mockReuploadAllInvites.execute).toBeCalledWith({ keys: dto.newKeys, previousKeys: dto.previousKeys })
|
||||
expect(mockResendAllMessages.execute).toBeCalledWith({ keys: dto.newKeys, previousKeys: dto.previousKeys })
|
||||
expect(mockSendOwnContactChangedMessage.execute).not.toBeCalled()
|
||||
expect(mockMessageServer.deleteAllInboundMessages).toBeCalled()
|
||||
expect(mockInvitesServer.deleteAllInboundInvites).toBeCalled()
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
})
|
||||
|
||||
it('should handle sending contact change event to all contacts', async () => {
|
||||
const contact = { isMe: false }
|
||||
mockGetAllContacts.execute.mockReturnValue(Result.ok([contact]))
|
||||
|
||||
await useCase.execute(dto)
|
||||
|
||||
expect(mockSendOwnContactChangedMessage.execute).toBeCalledWith({
|
||||
senderOldKeyPair: dto.previousKeys.encryption,
|
||||
senderOldSigningKeyPair: dto.previousKeys.signing,
|
||||
senderNewKeyPair: dto.newKeys.encryption,
|
||||
senderNewSigningKeyPair: dto.newKeys.signing,
|
||||
contact,
|
||||
})
|
||||
})
|
||||
|
||||
it('should not send contact change event if previous keys are missing', async () => {
|
||||
const contact = { isMe: false }
|
||||
mockGetAllContacts.execute.mockReturnValue(Result.ok([contact]))
|
||||
|
||||
await useCase.execute({ newKeys: dto.newKeys })
|
||||
|
||||
expect(mockSendOwnContactChangedMessage.execute).not.toBeCalled()
|
||||
})
|
||||
|
||||
it('should not send contact change event if getAllContacts fails', async () => {
|
||||
mockGetAllContacts.execute.mockReturnValue(Result.fail('Some error'))
|
||||
|
||||
await useCase.execute(dto)
|
||||
|
||||
expect(mockSendOwnContactChangedMessage.execute).not.toBeCalled()
|
||||
})
|
||||
|
||||
it('should not send contact change event for self contact', async () => {
|
||||
const contact = { isMe: true }
|
||||
mockGetAllContacts.execute.mockReturnValue(Result.ok([contact]))
|
||||
|
||||
await useCase.execute(dto)
|
||||
|
||||
expect(mockSendOwnContactChangedMessage.execute).not.toBeCalled()
|
||||
})
|
||||
|
||||
it('should reupload invites and resend messages before sending contact change message', async () => {
|
||||
const contact = { isMe: false }
|
||||
mockGetAllContacts.execute.mockReturnValue(Result.ok([contact]))
|
||||
|
||||
await useCase.execute(dto)
|
||||
|
||||
const callOrder = [
|
||||
mockReuploadAllInvites.execute,
|
||||
mockResendAllMessages.execute,
|
||||
mockSendOwnContactChangedMessage.execute,
|
||||
].map((fn) => fn.mock.invocationCallOrder[0])
|
||||
|
||||
for (let i = 0; i < callOrder.length - 1; i++) {
|
||||
expect(callOrder[i]).toBeLessThan(callOrder[i + 1])
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,34 +1,121 @@
|
||||
import { InternalFeatureService } from './../../InternalFeatures/InternalFeatureService'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { ReuploadAllInvites } from '../../VaultInvite/UseCase/ReuploadAllInvites'
|
||||
import { ResendAllMessages } from '../../AsymmetricMessage/UseCase/ResendAllMessages'
|
||||
import { SelfContactManager } from '../SelfContactManager'
|
||||
import { GetAllContacts } from './GetAllContacts'
|
||||
import { SendOwnContactChangeMessage } from './SendOwnContactChangeMessage'
|
||||
import { AsymmetricMessageServer, SharedVaultInvitesServer } from '@standardnotes/api'
|
||||
import { PortablePublicKeySet } from '@standardnotes/models'
|
||||
import { InternalFeature } from '../../InternalFeatures/InternalFeature'
|
||||
import { CreateOrEditContact } from './CreateOrEditContact'
|
||||
import { isErrorResponse } from '@standardnotes/responses'
|
||||
import { LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
type Dto = {
|
||||
newKeys: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
previousKeys?: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
}
|
||||
|
||||
export class HandleKeyPairChange implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private reuploadAllInvites: ReuploadAllInvites,
|
||||
private resendAllMessages: ResendAllMessages,
|
||||
private selfContactManager: SelfContactManager,
|
||||
private invitesServer: SharedVaultInvitesServer,
|
||||
private messageServer: AsymmetricMessageServer,
|
||||
private _reuploadAllInvites: ReuploadAllInvites,
|
||||
private _resendAllMessages: ResendAllMessages,
|
||||
private _getAllContacts: GetAllContacts,
|
||||
private _sendOwnContactChangedMessage: SendOwnContactChangeMessage,
|
||||
private _createOrEditContact: CreateOrEditContact,
|
||||
private logger: LoggerInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: {
|
||||
newKeys: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
previousKeys?: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
}): Promise<Result<void>> {
|
||||
await this.reuploadAllInvites.execute({
|
||||
keys: dto.newKeys,
|
||||
previousKeys: dto.previousKeys,
|
||||
async execute(dto: Dto): Promise<Result<void>> {
|
||||
await this.updateSelfContact({
|
||||
encryption: dto.newKeys.encryption.publicKey,
|
||||
signing: dto.newKeys.signing.publicKey,
|
||||
})
|
||||
|
||||
await this.resendAllMessages.execute({
|
||||
keys: dto.newKeys,
|
||||
previousKeys: dto.previousKeys,
|
||||
})
|
||||
const results = await Promise.all([
|
||||
this._reuploadAllInvites.execute({
|
||||
keys: dto.newKeys,
|
||||
previousKeys: dto.previousKeys,
|
||||
}),
|
||||
|
||||
this._resendAllMessages.execute({
|
||||
keys: dto.newKeys,
|
||||
previousKeys: dto.previousKeys,
|
||||
}),
|
||||
])
|
||||
|
||||
for (const result of results) {
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(result.getError())
|
||||
}
|
||||
}
|
||||
|
||||
await this.sendOwnContactChangeEventToAllContacts(dto)
|
||||
|
||||
const deleteResponses = await Promise.all([
|
||||
this.messageServer.deleteAllInboundMessages(),
|
||||
this.invitesServer.deleteAllInboundInvites(),
|
||||
])
|
||||
|
||||
for (const response of deleteResponses) {
|
||||
if (isErrorResponse(response)) {
|
||||
this.logger.error(JSON.stringify(response))
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
private async updateSelfContact(publicKeySet: PortablePublicKeySet) {
|
||||
if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
|
||||
return
|
||||
}
|
||||
|
||||
const selfContact = this.selfContactManager.selfContact
|
||||
if (!selfContact) {
|
||||
return
|
||||
}
|
||||
|
||||
await this._createOrEditContact.execute({
|
||||
contactUuid: selfContact.contactUuid,
|
||||
publicKey: publicKeySet.encryption,
|
||||
signingPublicKey: publicKeySet.signing,
|
||||
})
|
||||
}
|
||||
|
||||
private async sendOwnContactChangeEventToAllContacts(data: Dto): Promise<void> {
|
||||
if (!data.previousKeys) {
|
||||
return
|
||||
}
|
||||
|
||||
const contacts = this._getAllContacts.execute()
|
||||
if (contacts.isFailed()) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const contact of contacts.getValue()) {
|
||||
if (contact.isMe) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this._sendOwnContactChangedMessage.execute({
|
||||
senderOldKeyPair: data.previousKeys.encryption,
|
||||
senderOldSigningKeyPair: data.previousKeys.signing,
|
||||
senderNewKeyPair: data.newKeys.encryption,
|
||||
senderNewSigningKeyPair: data.newKeys.signing,
|
||||
contact,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,12 +36,15 @@ import {
|
||||
RootKeyParamsInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { extendArray } from '@standardnotes/utils'
|
||||
import { extendArray, LoggerInterface } from '@standardnotes/utils'
|
||||
import { EncryptionService } from '../EncryptionService'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
|
||||
export class DecryptBackupFile {
|
||||
constructor(private encryption: EncryptionService) {}
|
||||
constructor(
|
||||
private encryption: EncryptionService,
|
||||
private logger: LoggerInterface,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
file: BackupFile,
|
||||
@@ -273,7 +276,7 @@ export class DecryptBackupFile {
|
||||
errorDecrypting: true,
|
||||
}),
|
||||
)
|
||||
console.error('Error decrypting payload', encryptedPayload, e)
|
||||
this.logger.error('Error decrypting payload', encryptedPayload, e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
AsymmetricMessageServerHash,
|
||||
SharedVaultInviteServerHash,
|
||||
SharedVaultServerHash,
|
||||
UserEventServerHash,
|
||||
NotificationServerHash,
|
||||
} from '@standardnotes/responses'
|
||||
|
||||
/* istanbul ignore file */
|
||||
@@ -31,11 +31,11 @@ export enum SyncEvent {
|
||||
SyncRequestsIntegrityCheck = 'sync:requests-integrity-check',
|
||||
ReceivedRemoteSharedVaults = 'received-shared-vaults',
|
||||
ReceivedSharedVaultInvites = 'received-shared-vault-invites',
|
||||
ReceivedUserEvents = 'received-user-events',
|
||||
ReceivedNotifications = 'received-user-events',
|
||||
ReceivedAsymmetricMessages = 'received-asymmetric-messages',
|
||||
}
|
||||
|
||||
export type SyncEventReceivedRemoteSharedVaultsData = SharedVaultServerHash[]
|
||||
export type SyncEventReceivedSharedVaultInvitesData = SharedVaultInviteServerHash[]
|
||||
export type SyncEventReceivedAsymmetricMessagesData = AsymmetricMessageServerHash[]
|
||||
export type SyncEventReceivedUserEventsData = UserEventServerHash[]
|
||||
export type SyncEventReceivedNotificationsData = NotificationServerHash[]
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
|
||||
import { FileService } from './FileService'
|
||||
import { BackupServiceInterface } from '@standardnotes/files'
|
||||
import { HttpServiceInterface } from '@standardnotes/api'
|
||||
import { LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
describe('fileService', () => {
|
||||
let apiService: LegacyApiServiceInterface
|
||||
@@ -26,6 +27,8 @@ describe('fileService', () => {
|
||||
let backupService: BackupServiceInterface
|
||||
let http: HttpServiceInterface
|
||||
|
||||
let logger: LoggerInterface
|
||||
|
||||
beforeEach(() => {
|
||||
apiService = {} as jest.Mocked<LegacyApiServiceInterface>
|
||||
apiService.addEventObserver = jest.fn()
|
||||
@@ -82,6 +85,9 @@ describe('fileService', () => {
|
||||
backupService.readEncryptedFileFromBackup = jest.fn()
|
||||
backupService.getFileBackupInfo = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<LoggerInterface>
|
||||
logger.info = jest.fn()
|
||||
|
||||
http = {} as jest.Mocked<HttpServiceInterface>
|
||||
|
||||
fileService = new FileService(
|
||||
@@ -94,6 +100,7 @@ describe('fileService', () => {
|
||||
alertService,
|
||||
crypto,
|
||||
internalEventBus,
|
||||
logger,
|
||||
backupService,
|
||||
)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
SharedVaultListingInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { spaceSeparatedStrings, UuidGenerator } from '@standardnotes/utils'
|
||||
import { LoggerInterface, spaceSeparatedStrings, UuidGenerator } from '@standardnotes/utils'
|
||||
import { SNItemsKey } from '@standardnotes/encryption'
|
||||
import {
|
||||
DownloadAndDecryptFileOperation,
|
||||
@@ -47,7 +47,6 @@ import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
|
||||
import { DecryptItemsKeyWithUserFallback } from '../Encryption/Functions'
|
||||
import { log, LoggingDomain } from '../Logging'
|
||||
import { SharedVaultServer, SharedVaultServerInterface, HttpServiceInterface } from '@standardnotes/api'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
|
||||
@@ -68,6 +67,7 @@ export class FileService extends AbstractService implements FilesClientInterface
|
||||
private alertService: AlertService,
|
||||
private crypto: PureCryptoInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
private logger: LoggerInterface,
|
||||
private backupsService?: BackupServiceInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
@@ -317,19 +317,19 @@ export class FileService extends AbstractService implements FilesClientInterface
|
||||
const fileBackup = await this.backupsService?.getFileBackupInfo(file)
|
||||
|
||||
if (this.backupsService && fileBackup) {
|
||||
log(LoggingDomain.FilesService, 'Downloading file from backup', fileBackup)
|
||||
this.logger.info('Downloading file from backup', fileBackup)
|
||||
|
||||
await readAndDecryptBackupFileUsingBackupService(file, this.backupsService, this.crypto, async (chunk) => {
|
||||
log(LoggingDomain.FilesService, 'Got local file chunk', chunk.progress)
|
||||
this.logger.info('Got local file chunk', chunk.progress)
|
||||
|
||||
return onDecryptedBytes(chunk.data, chunk.progress)
|
||||
})
|
||||
|
||||
log(LoggingDomain.FilesService, 'Finished downloading file from backup')
|
||||
this.logger.info('Finished downloading file from backup')
|
||||
|
||||
return undefined
|
||||
} else {
|
||||
log(LoggingDomain.FilesService, 'Downloading file from network')
|
||||
this.logger.info('Downloading file from network')
|
||||
|
||||
const addToCache = file.encryptedSize < this.encryptedCache.maxSize
|
||||
|
||||
|
||||
@@ -8,14 +8,16 @@ import { IntegrityApiInterface } from './IntegrityApiInterface'
|
||||
import { IntegrityService } from './IntegrityService'
|
||||
import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface'
|
||||
import { IntegrityPayload } from '@standardnotes/responses'
|
||||
import { LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
describe('IntegrityService', () => {
|
||||
let integrityApi: IntegrityApiInterface
|
||||
let itemApi: ItemsServerInterface
|
||||
let payloadManager: PayloadManagerInterface
|
||||
let logger: LoggerInterface
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
|
||||
const createService = () => new IntegrityService(integrityApi, itemApi, payloadManager, internalEventBus)
|
||||
const createService = () => new IntegrityService(integrityApi, itemApi, payloadManager, logger, internalEventBus)
|
||||
|
||||
beforeEach(() => {
|
||||
integrityApi = {} as jest.Mocked<IntegrityApiInterface>
|
||||
@@ -29,6 +31,10 @@ describe('IntegrityService', () => {
|
||||
|
||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
internalEventBus.publishSync = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<LoggerInterface>
|
||||
logger.info = jest.fn()
|
||||
logger.error = jest.fn()
|
||||
})
|
||||
|
||||
it('should check integrity of payloads and publish mismatches', async () => {
|
||||
@@ -63,7 +69,7 @@ describe('IntegrityService', () => {
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
],
|
||||
source: "AfterDownloadFirst",
|
||||
source: 'AfterDownloadFirst',
|
||||
},
|
||||
type: 'IntegrityCheckCompleted',
|
||||
},
|
||||
@@ -90,7 +96,7 @@ describe('IntegrityService', () => {
|
||||
{
|
||||
payload: {
|
||||
rawPayloads: [],
|
||||
source: "AfterDownloadFirst",
|
||||
source: 'AfterDownloadFirst',
|
||||
},
|
||||
type: 'IntegrityCheckCompleted',
|
||||
},
|
||||
@@ -140,7 +146,7 @@ describe('IntegrityService', () => {
|
||||
{
|
||||
payload: {
|
||||
rawPayloads: [],
|
||||
source: "AfterDownloadFirst",
|
||||
source: 'AfterDownloadFirst',
|
||||
},
|
||||
type: 'IntegrityCheckCompleted',
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SyncEvent } from '../Event/SyncEvent'
|
||||
import { IntegrityEventPayload } from './IntegrityEventPayload'
|
||||
import { SyncSource } from '../Sync/SyncSource'
|
||||
import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface'
|
||||
import { LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
export class IntegrityService
|
||||
extends AbstractService<IntegrityEvent, IntegrityEventPayload>
|
||||
@@ -19,6 +20,7 @@ export class IntegrityService
|
||||
private integrityApi: IntegrityApiInterface,
|
||||
private itemApi: ItemsServerInterface,
|
||||
private payloadManager: PayloadManagerInterface,
|
||||
private logger: LoggerInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
@@ -31,7 +33,7 @@ export class IntegrityService
|
||||
|
||||
const integrityCheckResponse = await this.integrityApi.checkIntegrity(this.payloadManager.integrityPayloads)
|
||||
if (isErrorResponse(integrityCheckResponse)) {
|
||||
this.log(`Could not obtain integrity check: ${integrityCheckResponse.data.error?.message}`)
|
||||
this.logger.error(`Could not obtain integrity check: ${integrityCheckResponse.data.error?.message}`)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -50,7 +52,7 @@ export class IntegrityService
|
||||
isErrorResponse(serverItemResponse) ||
|
||||
!('item' in serverItemResponse.data)
|
||||
) {
|
||||
this.log(
|
||||
this.logger.error(
|
||||
`Could not obtain item for integrity adjustments: ${
|
||||
isErrorResponse(serverItemResponse) ? serverItemResponse.data.error?.message : ''
|
||||
}`,
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { logWithColor } from '@standardnotes/utils'
|
||||
|
||||
declare const process: {
|
||||
env: {
|
||||
NODE_ENV: string | null | undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const isDev = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test'
|
||||
|
||||
export enum LoggingDomain {
|
||||
FilesService,
|
||||
FilesBackups,
|
||||
}
|
||||
|
||||
const LoggingStatus: Record<LoggingDomain, boolean> = {
|
||||
[LoggingDomain.FilesService]: false,
|
||||
[LoggingDomain.FilesBackups]: false,
|
||||
}
|
||||
|
||||
const LoggingColor: Record<LoggingDomain, string> = {
|
||||
[LoggingDomain.FilesService]: 'blue',
|
||||
[LoggingDomain.FilesBackups]: 'yellow',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function log(domain: LoggingDomain, ...args: any[]): void {
|
||||
if (!isDev || !LoggingStatus[domain]) {
|
||||
return
|
||||
}
|
||||
|
||||
logWithColor(LoggingDomain[domain], LoggingColor[domain], ...args)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
import { log, removeFromArray } from '@standardnotes/utils'
|
||||
import { removeFromArray } from '@standardnotes/utils'
|
||||
import { EventObserver } from '../Event/EventObserver'
|
||||
import { ApplicationServiceInterface } from './ApplicationServiceInterface'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
@@ -99,11 +99,4 @@ export abstract class AbstractService<EventName = string, EventData = unknown>
|
||||
isApplicationService(): true {
|
||||
return true
|
||||
}
|
||||
|
||||
log(..._args: unknown[]): void {
|
||||
if (this.loggingEnabled) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
log(this.getServiceName(), ...arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,4 @@ export interface ApplicationServiceInterface<E, D> extends ServiceDiagnostics {
|
||||
addEventObserver(observer: EventObserver<E, D>): () => void
|
||||
blockDeinit(): Promise<void>
|
||||
deinit(): void
|
||||
log(message: string, ...args: unknown[]): void
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface SessionsClientInterface {
|
||||
isSignedIntoFirstPartyServer(): boolean
|
||||
|
||||
getSessionsList(): Promise<HttpResponse<SessionListEntry[]>>
|
||||
refreshSessionIfExpiringSoon(): Promise<boolean>
|
||||
revokeSession(sessionId: string): Promise<HttpResponse<SessionListResponse>>
|
||||
revokeAllOtherSessions(): Promise<void>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface
|
||||
import { SyncEvent } from '../Event/SyncEvent'
|
||||
import { SessionEvent } from '../Session/SessionEvent'
|
||||
import { InternalEventInterface } from '../Internal/InternalEventInterface'
|
||||
import { UserEventServiceEvent, UserEventServiceEventPayload } from '../UserEvent/UserEventServiceEvent'
|
||||
import { NotificationServiceEvent, NotificationServiceEventPayload } from '../UserEvent/NotificationServiceEvent'
|
||||
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault'
|
||||
import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
|
||||
import { VaultServiceEvent, VaultServiceEventPayload } from '../Vault/VaultServiceEvent'
|
||||
@@ -106,8 +106,8 @@ export class SharedVaultService
|
||||
})
|
||||
break
|
||||
}
|
||||
case UserEventServiceEvent.UserEventReceived:
|
||||
await this.handleUserEvent(event.payload as UserEventServiceEventPayload)
|
||||
case NotificationServiceEvent.NotificationReceived:
|
||||
await this.handleUserEvent(event.payload as NotificationServiceEventPayload)
|
||||
break
|
||||
case VaultServiceEvent.VaultRootKeyRotated: {
|
||||
const payload = event.payload as VaultServiceEventPayload[VaultServiceEvent.VaultRootKeyRotated]
|
||||
@@ -120,7 +120,7 @@ export class SharedVaultService
|
||||
}
|
||||
}
|
||||
|
||||
private async handleUserEvent(event: UserEventServiceEventPayload): Promise<void> {
|
||||
private async handleUserEvent(event: NotificationServiceEventPayload): Promise<void> {
|
||||
switch (event.eventPayload.props.type.value) {
|
||||
case NotificationType.TYPES.RemovedFromSharedVault: {
|
||||
const vault = this._getVault.execute<SharedVaultListingInterface>({
|
||||
|
||||
@@ -20,7 +20,7 @@ export class ConvertToSharedVault {
|
||||
|
||||
const serverResult = await this.sharedVaultServer.createSharedVault()
|
||||
if (isErrorResponse(serverResult)) {
|
||||
return ClientDisplayableError.FromString(`Failed to create shared vault ${JSON.stringify(serverResult)}`)
|
||||
return ClientDisplayableError.FromString(`Failed to convert to shared vault ${JSON.stringify(serverResult)}`)
|
||||
}
|
||||
|
||||
const serverVaultHash = serverResult.data.sharedVault
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
import { FullyFormedPayloadInterface } from '@standardnotes/models'
|
||||
import { DecryptedItemInterface, DeletedItemInterface, FullyFormedPayloadInterface } from '@standardnotes/models'
|
||||
import { SyncOptions } from './SyncOptions'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { SyncEvent } from '../Event/SyncEvent'
|
||||
import { SyncOpStatus } from './SyncOpStatus'
|
||||
import { HttpRequest } from '@standardnotes/responses'
|
||||
|
||||
export interface SyncServiceInterface extends AbstractService<SyncEvent> {
|
||||
sync(options?: Partial<SyncOptions>): Promise<unknown>
|
||||
getRawSyncRequestForExternalUse(
|
||||
items: (DecryptedItemInterface | DeletedItemInterface)[],
|
||||
): Promise<HttpRequest | undefined>
|
||||
|
||||
isDatabaseLoaded(): boolean
|
||||
onNewDatabaseCreated(): Promise<void>
|
||||
loadDatabasePayloads(): Promise<void>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { NotificationServerHash } from '@standardnotes/responses'
|
||||
import { SyncEvent, SyncEventReceivedNotificationsData } from '../Event/SyncEvent'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
|
||||
import { InternalEventInterface } from '../Internal/InternalEventInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { NotificationServiceEventPayload, NotificationServiceEvent } from './NotificationServiceEvent'
|
||||
import { NotificationPayload } from '@standardnotes/domain-core'
|
||||
|
||||
export class NotificationService
|
||||
extends AbstractService<NotificationServiceEvent, NotificationServiceEventPayload>
|
||||
implements InternalEventHandlerInterface
|
||||
{
|
||||
private handledNotifications = new Set<string>()
|
||||
|
||||
constructor(internalEventBus: InternalEventBusInterface) {
|
||||
super(internalEventBus)
|
||||
|
||||
internalEventBus.addEventHandler(this, SyncEvent.ReceivedNotifications)
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === SyncEvent.ReceivedNotifications) {
|
||||
return this.handleReceivedNotifications(event.payload as SyncEventReceivedNotificationsData)
|
||||
}
|
||||
}
|
||||
|
||||
private async handleReceivedNotifications(notifications: NotificationServerHash[]): Promise<void> {
|
||||
if (notifications.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const notification of notifications) {
|
||||
if (this.handledNotifications.has(notification.uuid)) {
|
||||
continue
|
||||
}
|
||||
|
||||
this.handledNotifications.add(notification.uuid)
|
||||
|
||||
const eventPayloadOrError = NotificationPayload.createFromString(notification.payload)
|
||||
if (eventPayloadOrError.isFailed()) {
|
||||
continue
|
||||
}
|
||||
|
||||
const payload: NotificationPayload = eventPayloadOrError.getValue()
|
||||
|
||||
const serviceEvent: NotificationServiceEventPayload = { eventPayload: payload }
|
||||
|
||||
await this.notifyEventSync(NotificationServiceEvent.NotificationReceived, serviceEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { NotificationPayload } from '@standardnotes/domain-core'
|
||||
|
||||
export enum NotificationServiceEvent {
|
||||
NotificationReceived = 'NotificationReceived',
|
||||
}
|
||||
|
||||
export type NotificationServiceEventPayload = {
|
||||
eventPayload: NotificationPayload
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { UserEventServerHash } from '@standardnotes/responses'
|
||||
import { SyncEvent, SyncEventReceivedUserEventsData } from '../Event/SyncEvent'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
|
||||
import { InternalEventInterface } from '../Internal/InternalEventInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { UserEventServiceEventPayload, UserEventServiceEvent } from './UserEventServiceEvent'
|
||||
import { NotificationPayload } from '@standardnotes/domain-core'
|
||||
|
||||
export class UserEventService
|
||||
extends AbstractService<UserEventServiceEvent, UserEventServiceEventPayload>
|
||||
implements InternalEventHandlerInterface
|
||||
{
|
||||
constructor(internalEventBus: InternalEventBusInterface) {
|
||||
super(internalEventBus)
|
||||
|
||||
internalEventBus.addEventHandler(this, SyncEvent.ReceivedUserEvents)
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === SyncEvent.ReceivedUserEvents) {
|
||||
return this.handleReceivedUserEvents(event.payload as SyncEventReceivedUserEventsData)
|
||||
}
|
||||
}
|
||||
|
||||
private async handleReceivedUserEvents(userEvents: UserEventServerHash[]): Promise<void> {
|
||||
if (userEvents.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const serverEvent of userEvents) {
|
||||
const eventPayloadOrError = NotificationPayload.createFromString(serverEvent.payload)
|
||||
if (eventPayloadOrError.isFailed()) {
|
||||
continue
|
||||
}
|
||||
const eventPayload = eventPayloadOrError.getValue()
|
||||
|
||||
const serviceEvent: UserEventServiceEventPayload = { eventPayload }
|
||||
|
||||
await this.notifyEventSync(UserEventServiceEvent.UserEventReceived, serviceEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { NotificationPayload } from '@standardnotes/domain-core'
|
||||
|
||||
export enum UserEventServiceEvent {
|
||||
UserEventReceived = 'UserEventReceived',
|
||||
}
|
||||
|
||||
export type UserEventServiceEventPayload = {
|
||||
eventPayload: NotificationPayload
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite'
|
||||
import { SyncEvent, SyncEventReceivedSharedVaultInvitesData } from './../Event/SyncEvent'
|
||||
import { SessionEvent } from './../Session/SessionEvent'
|
||||
import { InternalEventInterface } from './../Internal/InternalEventInterface'
|
||||
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
|
||||
import { ItemManagerInterface } from './../Item/ItemManagerInterface'
|
||||
@@ -92,9 +91,6 @@ export class VaultInviteService
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
switch (event.type) {
|
||||
case SessionEvent.UserKeyPairChanged:
|
||||
void this.invitesServer.deleteAllInboundInvites()
|
||||
break
|
||||
case SyncEvent.ReceivedSharedVaultInvites:
|
||||
await this.processInboundInvites(event.payload as SyncEventReceivedSharedVaultInvitesData)
|
||||
break
|
||||
@@ -238,6 +234,8 @@ export class VaultInviteService
|
||||
}
|
||||
|
||||
for (const invite of invites) {
|
||||
delete this.pendingInvites[invite.uuid]
|
||||
|
||||
const sender = this._findContact.execute({ userUuid: invite.sender_uuid })
|
||||
if (!sender.isFailed()) {
|
||||
const trustedMessage = this._getTrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
|
||||
|
||||
@@ -177,8 +177,8 @@ export * from './User/SignedOutEventPayload'
|
||||
export * from './User/UserClientInterface'
|
||||
export * from './User/UserClientInterface'
|
||||
export * from './User/UserService'
|
||||
export * from './UserEvent/UserEventService'
|
||||
export * from './UserEvent/UserEventServiceEvent'
|
||||
export * from './UserEvent/NotificationService'
|
||||
export * from './UserEvent/NotificationServiceEvent'
|
||||
export * from './VaultInvite/InviteRecord'
|
||||
export * from './VaultInvite/UseCase/AcceptVaultInvite'
|
||||
export * from './VaultInvite/UseCase/InviteToVault'
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.202.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* exit interview discount link not working ([#2381](https://github.com/standardnotes/app/issues/2381)) ([027dd41](https://github.com/standardnotes/app/commit/027dd4114720d46cccf215a71c04b67f91315ddc))
|
||||
|
||||
## [2.202.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -73,7 +73,7 @@ import {
|
||||
EncryptionProviderInterface,
|
||||
VaultUserServiceInterface,
|
||||
VaultInviteServiceInterface,
|
||||
UserEventServiceEvent,
|
||||
NotificationServiceEvent,
|
||||
VaultServiceEvent,
|
||||
VaultLockServiceInterface,
|
||||
} from '@standardnotes/services'
|
||||
@@ -116,6 +116,7 @@ import {
|
||||
sleep,
|
||||
UuidGenerator,
|
||||
useBoolean,
|
||||
LoggerInterface,
|
||||
} from '@standardnotes/utils'
|
||||
import { UuidString, ApplicationEventPayload } from '../Types'
|
||||
import { applicationEventForSyncEvent } from '@Lib/Application/Event'
|
||||
@@ -504,7 +505,8 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
|
||||
private beginAutoSyncTimer() {
|
||||
this.autoSyncInterval = setInterval(() => {
|
||||
this.sync.log('Syncing from autosync')
|
||||
const logger = this.dependencies.get<LoggerInterface>(TYPES.Logger)
|
||||
logger.info('Syncing from autosync')
|
||||
void this.sync.sync({ sourceDescription: 'Auto Sync' })
|
||||
}, DEFAULT_AUTO_SYNC_INTERVAL)
|
||||
}
|
||||
@@ -807,7 +809,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
await promise
|
||||
} else {
|
||||
/** Await up to maxWait. If not resolved by then, return. */
|
||||
await Promise.race([promise, sleep(maxWait)])
|
||||
await Promise.race([promise, sleep(maxWait, false, 'Preparing for deinit...')])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1120,7 +1122,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
}
|
||||
|
||||
private createBackgroundDependencies() {
|
||||
this.dependencies.get(TYPES.UserEventService)
|
||||
this.dependencies.get(TYPES.NotificationService)
|
||||
this.dependencies.get(TYPES.KeyRecoveryService)
|
||||
}
|
||||
|
||||
@@ -1133,12 +1135,11 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SubscriptionManager), SessionEvent.Restored)
|
||||
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SyncEvent.ReceivedSharedVaultInvites)
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SessionEvent.UserKeyPairChanged)
|
||||
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SessionEvent.UserKeyPairChanged)
|
||||
this.events.addEventHandler(
|
||||
this.dependencies.get(TYPES.SharedVaultService),
|
||||
UserEventServiceEvent.UserEventReceived,
|
||||
NotificationServiceEvent.NotificationReceived,
|
||||
)
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), VaultServiceEvent.VaultRootKeyRotated)
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SyncEvent.ReceivedRemoteSharedVaults)
|
||||
@@ -1147,7 +1148,6 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.dependencies.get(TYPES.AsymmetricMessageService),
|
||||
SyncEvent.ReceivedAsymmetricMessages,
|
||||
)
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.AsymmetricMessageService), SessionEvent.UserKeyPairChanged)
|
||||
|
||||
if (this.dependencies.get(TYPES.FilesBackupService)) {
|
||||
this.events.addEventHandler(
|
||||
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
SelfContactManager,
|
||||
StatusService,
|
||||
SubscriptionManager,
|
||||
UserEventService,
|
||||
NotificationService,
|
||||
UserService,
|
||||
ValidateItemSigner,
|
||||
isDesktopDevice,
|
||||
@@ -140,6 +140,7 @@ import {
|
||||
SubscriptionApiService,
|
||||
SubscriptionServer,
|
||||
UserApiService,
|
||||
UserRequestServer,
|
||||
UserServer,
|
||||
WebSocketApiService,
|
||||
WebSocketServer,
|
||||
@@ -147,7 +148,7 @@ import {
|
||||
import { FullyResolvedApplicationOptions } from '../Options/ApplicationOptions'
|
||||
import { TYPES } from './Types'
|
||||
import { isDeinitable } from './isDeinitable'
|
||||
import { isNotUndefined } from '@standardnotes/utils'
|
||||
import { Logger, isNotUndefined } from '@standardnotes/utils'
|
||||
import { EncryptionOperators } from '@standardnotes/encryption'
|
||||
|
||||
export class Dependencies {
|
||||
@@ -225,7 +226,7 @@ export class Dependencies {
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.DecryptBackupFile, () => {
|
||||
return new DecryptBackupFile(this.get(TYPES.EncryptionService))
|
||||
return new DecryptBackupFile(this.get(TYPES.EncryptionService), this.get(TYPES.Logger))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.DiscardItemsLocally, () => {
|
||||
@@ -254,7 +255,7 @@ export class Dependencies {
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.EditContact, () => {
|
||||
return new EditContact(this.get(TYPES.MutatorService), this.get(TYPES.SyncService))
|
||||
return new EditContact(this.get(TYPES.MutatorService))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.GetAllContacts, () => {
|
||||
@@ -268,7 +269,6 @@ export class Dependencies {
|
||||
this.factory.set(TYPES.CreateOrEditContact, () => {
|
||||
return new CreateOrEditContact(
|
||||
this.get(TYPES.MutatorService),
|
||||
this.get(TYPES.SyncService),
|
||||
this.get(TYPES.FindContact),
|
||||
this.get(TYPES.EditContact),
|
||||
)
|
||||
@@ -364,6 +364,7 @@ export class Dependencies {
|
||||
this.factory.set(TYPES.ResendAllMessages, () => {
|
||||
return new ResendAllMessages(
|
||||
this.get(TYPES.ResendMessage),
|
||||
this.get(TYPES.DecryptOwnMessage),
|
||||
this.get(TYPES.AsymmetricMessageServer),
|
||||
this.get(TYPES.FindContact),
|
||||
)
|
||||
@@ -380,7 +381,17 @@ export class Dependencies {
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.HandleKeyPairChange, () => {
|
||||
return new HandleKeyPairChange(this.get(TYPES.ReuploadAllInvites), this.get(TYPES.ResendAllMessages))
|
||||
return new HandleKeyPairChange(
|
||||
this.get(TYPES.SelfContactManager),
|
||||
this.get(TYPES.SharedVaultInvitesServer),
|
||||
this.get(TYPES.AsymmetricMessageServer),
|
||||
this.get(TYPES.ReuploadAllInvites),
|
||||
this.get(TYPES.ResendAllMessages),
|
||||
this.get(TYPES.GetAllContacts),
|
||||
this.get(TYPES.SendOwnContactChangeMessage),
|
||||
this.get(TYPES.CreateOrEditContact),
|
||||
this.get(TYPES.Logger),
|
||||
)
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.NotifyVaultUsersOfKeyRotation, () => {
|
||||
@@ -515,11 +526,7 @@ export class Dependencies {
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.ResendMessage, () => {
|
||||
return new ResendMessage(
|
||||
this.get(TYPES.DecryptOwnMessage),
|
||||
this.get(TYPES.SendMessage),
|
||||
this.get(TYPES.EncryptMessage),
|
||||
)
|
||||
return new ResendMessage(this.get(TYPES.SendMessage), this.get(TYPES.EncryptMessage))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.SendMessage, () => {
|
||||
@@ -613,6 +620,10 @@ export class Dependencies {
|
||||
}
|
||||
|
||||
private registerServiceMakers() {
|
||||
this.factory.set(TYPES.Logger, () => {
|
||||
return new Logger(this.options.identifier)
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.UserServer, () => {
|
||||
return new UserServer(this.get(TYPES.HttpService))
|
||||
})
|
||||
@@ -703,6 +714,7 @@ export class Dependencies {
|
||||
this.get(TYPES.EncryptionService),
|
||||
this.get(TYPES.MutatorService),
|
||||
this.get(TYPES.SessionManager),
|
||||
this.get(TYPES.SyncService),
|
||||
this.get(TYPES.AsymmetricMessageServer),
|
||||
this.get(TYPES.CreateOrEditContact),
|
||||
this.get(TYPES.FindContact),
|
||||
@@ -774,7 +786,6 @@ export class Dependencies {
|
||||
this.get(TYPES.ItemManager),
|
||||
this.get(TYPES.SessionManager),
|
||||
this.get(TYPES.SingletonManager),
|
||||
this.get(TYPES.CreateOrEditContact),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -793,7 +804,6 @@ export class Dependencies {
|
||||
this.get(TYPES.CreateOrEditContact),
|
||||
this.get(TYPES.EditContact),
|
||||
this.get(TYPES.ValidateItemSigner),
|
||||
this.get(TYPES.SendOwnContactChangeMessage),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
@@ -921,6 +931,7 @@ export class Dependencies {
|
||||
this.get(TYPES.LegacyApiService),
|
||||
this.get(TYPES.LegacyApiService),
|
||||
this.get(TYPES.PayloadManager),
|
||||
this.get(TYPES.Logger),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
@@ -936,6 +947,7 @@ export class Dependencies {
|
||||
this.get(TYPES.AlertService),
|
||||
this.get(TYPES.Crypto),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
this.get(TYPES.Logger),
|
||||
this.get(TYPES.FilesBackupService),
|
||||
)
|
||||
})
|
||||
@@ -1014,6 +1026,7 @@ export class Dependencies {
|
||||
this.options.environment,
|
||||
this.options.platform,
|
||||
this.get(TYPES.DeviceInterface),
|
||||
this.get(TYPES.Logger),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
@@ -1032,6 +1045,7 @@ export class Dependencies {
|
||||
this.get(TYPES.AlertService),
|
||||
this.get(TYPES.SessionManager),
|
||||
this.get(TYPES.Crypto),
|
||||
this.get(TYPES.Logger),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
@@ -1120,6 +1134,7 @@ export class Dependencies {
|
||||
loadBatchSize: this.options.loadBatchSize,
|
||||
sleepBetweenBatches: this.options.sleepBetweenBatches,
|
||||
},
|
||||
this.get(TYPES.Logger),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
@@ -1190,7 +1205,7 @@ export class Dependencies {
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.UserRequestServer, () => {
|
||||
return new UserServer(this.get(TYPES.HttpService))
|
||||
return new UserRequestServer(this.get(TYPES.HttpService))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.InternalEventBus, () => {
|
||||
@@ -1198,7 +1213,7 @@ export class Dependencies {
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.PayloadManager, () => {
|
||||
return new PayloadManager(this.get(TYPES.InternalEventBus))
|
||||
return new PayloadManager(this.get(TYPES.Logger), this.get(TYPES.InternalEventBus))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.ItemManager, () => {
|
||||
@@ -1222,8 +1237,8 @@ export class Dependencies {
|
||||
)
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.UserEventService, () => {
|
||||
return new UserEventService(this.get(TYPES.InternalEventBus))
|
||||
this.factory.set(TYPES.NotificationService, () => {
|
||||
return new NotificationService(this.get(TYPES.InternalEventBus))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.InMemoryStore, () => {
|
||||
@@ -1278,7 +1293,7 @@ export class Dependencies {
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.HttpService, () => {
|
||||
return new HttpService(this.options.environment, this.options.appVersion, SnjsVersion)
|
||||
return new HttpService(this.options.environment, this.options.appVersion, SnjsVersion, this.get(TYPES.Logger))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.LegacyApiService, () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ export const TYPES = {
|
||||
ItemManager: Symbol.for('ItemManager'),
|
||||
MutatorService: Symbol.for('MutatorService'),
|
||||
DiskStorageService: Symbol.for('DiskStorageService'),
|
||||
UserEventService: Symbol.for('UserEventService'),
|
||||
NotificationService: Symbol.for('NotificationService'),
|
||||
InMemoryStore: Symbol.for('InMemoryStore'),
|
||||
KeySystemKeyManager: Symbol.for('KeySystemKeyManager'),
|
||||
EncryptionService: Symbol.for('EncryptionService'),
|
||||
@@ -63,6 +63,7 @@ export const TYPES = {
|
||||
VaultInviteService: Symbol.for('VaultInviteService'),
|
||||
VaultUserCache: Symbol.for('VaultUserCache'),
|
||||
VaultLockService: Symbol.for('VaultLockService'),
|
||||
Logger: Symbol.for('Logger'),
|
||||
|
||||
// Servers
|
||||
RevisionServer: Symbol.for('RevisionServer'),
|
||||
|
||||
@@ -33,6 +33,7 @@ export function environmentToString(environment: Environment) {
|
||||
[Environment.Web]: 'web',
|
||||
[Environment.Desktop]: 'desktop',
|
||||
[Environment.Mobile]: 'native-mobile-web',
|
||||
[Environment.Clipper]: 'clipper',
|
||||
}
|
||||
return map[environment]
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { log as utilsLog } from '@standardnotes/utils'
|
||||
|
||||
export const isDev = true
|
||||
|
||||
export enum LoggingDomain {
|
||||
DatabaseLoad,
|
||||
Sync,
|
||||
AccountMigration,
|
||||
}
|
||||
|
||||
const LoggingStatus: Record<LoggingDomain, boolean> = {
|
||||
[LoggingDomain.DatabaseLoad]: false,
|
||||
[LoggingDomain.Sync]: false,
|
||||
[LoggingDomain.AccountMigration]: true,
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function log(domain: LoggingDomain, ...args: any[]): void {
|
||||
if (!isDev || !LoggingStatus[domain]) {
|
||||
return
|
||||
}
|
||||
|
||||
utilsLog(LoggingDomain[domain], ...args)
|
||||
}
|
||||
@@ -374,15 +374,8 @@ export class LegacyApiService
|
||||
if (preprocessingError) {
|
||||
return preprocessingError
|
||||
}
|
||||
const path = Paths.v1.sync
|
||||
const params = this.params({
|
||||
[ApiEndpointParam.SyncPayloads]: payloads,
|
||||
[ApiEndpointParam.LastSyncToken]: lastSyncToken,
|
||||
[ApiEndpointParam.PaginationToken]: paginationToken,
|
||||
[ApiEndpointParam.SyncDlLimit]: limit,
|
||||
[ApiEndpointParam.SharedVaultUuids]: sharedVaultUuids,
|
||||
})
|
||||
const response = await this.httpService.post<RawSyncResponse>(path, params, this.getSessionAccessToken())
|
||||
const request = this.getSyncHttpRequest(payloads, lastSyncToken, paginationToken, limit, sharedVaultUuids)
|
||||
const response = await this.httpService.runHttp<RawSyncResponse>(request)
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
this.preprocessAuthenticatedErrorResponse(response)
|
||||
@@ -394,6 +387,29 @@ export class LegacyApiService
|
||||
return response
|
||||
}
|
||||
|
||||
getSyncHttpRequest(
|
||||
payloads: ServerSyncPushContextualPayload[],
|
||||
lastSyncToken: string | undefined,
|
||||
paginationToken: string | undefined,
|
||||
limit: number,
|
||||
sharedVaultUuids?: string[] | undefined,
|
||||
): HttpRequest {
|
||||
const path = Paths.v1.sync
|
||||
const params = this.params({
|
||||
[ApiEndpointParam.SyncPayloads]: payloads,
|
||||
[ApiEndpointParam.LastSyncToken]: lastSyncToken,
|
||||
[ApiEndpointParam.PaginationToken]: paginationToken,
|
||||
[ApiEndpointParam.SyncDlLimit]: limit,
|
||||
[ApiEndpointParam.SharedVaultUuids]: sharedVaultUuids,
|
||||
})
|
||||
return {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Post,
|
||||
authentication: this.getSessionAccessToken(),
|
||||
}
|
||||
}
|
||||
|
||||
async refreshSession(): Promise<HttpResponse<SessionRenewalResponse>> {
|
||||
const preprocessingError = this.preprocessingError()
|
||||
if (preprocessingError) {
|
||||
|
||||
@@ -51,10 +51,6 @@ const SubscriptionPaths = {
|
||||
subscriptionTokens: '/v1/subscription-tokens',
|
||||
}
|
||||
|
||||
const SubscriptionPathsV2 = {
|
||||
subscriptions: '/v2/subscriptions',
|
||||
}
|
||||
|
||||
const UserPathsV2 = {
|
||||
keyParams: '/v2/login-params',
|
||||
signIn: '/v2/login',
|
||||
@@ -75,7 +71,6 @@ export const Paths = {
|
||||
...UserPaths,
|
||||
},
|
||||
v2: {
|
||||
...SubscriptionPathsV2,
|
||||
...UserPathsV2,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ItemManager } from '@Lib/Services/Items/ItemManager'
|
||||
import { FeaturesService } from '@Lib/Services/Features/FeaturesService'
|
||||
import { SNComponentManager } from './ComponentManager'
|
||||
import { SyncService } from '../Sync/SyncService'
|
||||
import { LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
describe('featuresService', () => {
|
||||
let items: ItemManagerInterface
|
||||
@@ -23,6 +24,7 @@ describe('featuresService', () => {
|
||||
let prefs: PreferenceServiceInterface
|
||||
let eventBus: InternalEventBusInterface
|
||||
let device: DeviceInterface
|
||||
let logger: LoggerInterface
|
||||
|
||||
const createManager = (environment: Environment, platform: Platform) => {
|
||||
const manager = new SNComponentManager(
|
||||
@@ -35,6 +37,7 @@ describe('featuresService', () => {
|
||||
environment,
|
||||
platform,
|
||||
device,
|
||||
logger,
|
||||
eventBus,
|
||||
)
|
||||
|
||||
@@ -46,6 +49,8 @@ describe('featuresService', () => {
|
||||
addEventListener: jest.fn(),
|
||||
attachEvent: jest.fn(),
|
||||
} as unknown as Window & typeof globalThis
|
||||
logger = {} as jest.Mocked<LoggerInterface>
|
||||
logger.info = jest.fn()
|
||||
|
||||
sync = {} as jest.Mocked<SyncService>
|
||||
sync.sync = jest.fn()
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
GetNativeThemes,
|
||||
NativeFeatureIdentifier,
|
||||
} from '@standardnotes/features'
|
||||
import { Copy, removeFromArray, sleep, isNotUndefined } from '@standardnotes/utils'
|
||||
import { Copy, removeFromArray, sleep, isNotUndefined, LoggerInterface } from '@standardnotes/utils'
|
||||
import { ComponentViewer } from '@Lib/Services/ComponentManager/ComponentViewer'
|
||||
import {
|
||||
AbstractService,
|
||||
@@ -98,6 +98,7 @@ export class SNComponentManager
|
||||
private environment: Environment,
|
||||
private platform: Platform,
|
||||
private device: DeviceInterface,
|
||||
private logger: LoggerInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
@@ -177,6 +178,7 @@ export class SNComponentManager
|
||||
alerts: this.alerts,
|
||||
preferences: this.preferences,
|
||||
features: this.features,
|
||||
logger: this.logger,
|
||||
},
|
||||
{
|
||||
url: this.urlForFeature(component) ?? '',
|
||||
@@ -312,7 +314,7 @@ export class SNComponentManager
|
||||
onWindowMessage = (event: MessageEvent): void => {
|
||||
const data = event.data as ComponentMessage
|
||||
if (data.sessionKey) {
|
||||
this.log('Component manager received message', data)
|
||||
this.logger.info('Component manager received message', data)
|
||||
this.componentViewerForSessionKey(data.sessionKey)?.handleMessage(data)
|
||||
}
|
||||
}
|
||||
@@ -369,7 +371,7 @@ export class SNComponentManager
|
||||
}
|
||||
|
||||
public async toggleTheme(uiFeature: UIFeature<ThemeFeatureDescription>): Promise<void> {
|
||||
this.log('Toggling theme', uiFeature.uniqueIdentifier)
|
||||
this.logger.info('Toggling theme', uiFeature.uniqueIdentifier)
|
||||
|
||||
if (this.isThemeActive(uiFeature)) {
|
||||
await this.removeActiveTheme(uiFeature)
|
||||
@@ -449,7 +451,7 @@ export class SNComponentManager
|
||||
}
|
||||
|
||||
public async toggleComponent(component: ComponentInterface): Promise<void> {
|
||||
this.log('Toggling component', component.uuid)
|
||||
this.logger.info('Toggling component', component.uuid)
|
||||
|
||||
if (this.isComponentActive(component)) {
|
||||
await this.removeActiveComponent(component)
|
||||
|
||||
@@ -65,13 +65,13 @@ import {
|
||||
extendArray,
|
||||
Copy,
|
||||
removeFromArray,
|
||||
log,
|
||||
nonSecureRandomIdentifier,
|
||||
UuidGenerator,
|
||||
Uuids,
|
||||
sureSearchArray,
|
||||
isNotUndefined,
|
||||
uniqueArray,
|
||||
LoggerInterface,
|
||||
} from '@standardnotes/utils'
|
||||
import { ContentType, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
@@ -80,7 +80,6 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
private streamContextItemOriginalMessage?: ComponentMessage
|
||||
private streamItemsOriginalMessage?: ComponentMessage
|
||||
private removeItemObserver: () => void
|
||||
private loggingEnabled = false
|
||||
public identifier = nonSecureRandomIdentifier()
|
||||
private actionObservers: ActionObserver[] = []
|
||||
|
||||
@@ -102,6 +101,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
alerts: AlertService
|
||||
preferences: PreferenceServiceInterface
|
||||
features: FeaturesService
|
||||
logger: LoggerInterface
|
||||
},
|
||||
private options: {
|
||||
item: ComponentViewerItem
|
||||
@@ -143,7 +143,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
}
|
||||
})
|
||||
|
||||
this.log('Constructor', this)
|
||||
this.services.logger.info('Constructor', this)
|
||||
}
|
||||
|
||||
public getComponentOrFeatureItem(): UIFeature<IframeComponentFeatureDescription> {
|
||||
@@ -163,7 +163,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.log('Destroying', this)
|
||||
this.services.logger.info('Destroying', this)
|
||||
this.deinit()
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
this.componentUniqueIdentifier.value,
|
||||
requiredContextPermissions,
|
||||
() => {
|
||||
this.log(
|
||||
this.services.logger.info(
|
||||
'Send context item in reply',
|
||||
'component:',
|
||||
this.componentOrFeature,
|
||||
@@ -364,18 +364,12 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
)
|
||||
}
|
||||
|
||||
private log(message: string, ...args: unknown[]): void {
|
||||
if (this.loggingEnabled) {
|
||||
log('ComponentViewer', message, args)
|
||||
}
|
||||
}
|
||||
|
||||
private sendItemsInReply(
|
||||
items: (DecryptedItemInterface | DeletedItemInterface)[],
|
||||
message: ComponentMessage,
|
||||
source?: PayloadEmitSource,
|
||||
): void {
|
||||
this.log('Send items in reply', this.componentOrFeature, items, message)
|
||||
this.services.logger.info('Send items in reply', this.componentOrFeature, items, message)
|
||||
|
||||
const responseData: MessageReplyData = {}
|
||||
|
||||
@@ -453,10 +447,14 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
*/
|
||||
private sendMessage(message: ComponentMessage | MessageReply, essential = true): void {
|
||||
if (!this.window && message.action === ComponentAction.Reply) {
|
||||
this.log('Component has been deallocated in between message send and reply', this.componentOrFeature, message)
|
||||
this.services.logger.info(
|
||||
'Component has been deallocated in between message send and reply',
|
||||
this.componentOrFeature,
|
||||
message,
|
||||
)
|
||||
return
|
||||
}
|
||||
this.log('Send message to component', this.componentOrFeature, 'message: ', message)
|
||||
this.services.logger.info('Send message to component', this.componentOrFeature, 'message: ', message)
|
||||
|
||||
if (!this.window) {
|
||||
if (essential) {
|
||||
@@ -518,7 +516,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
throw Error('Attempting to override component viewer window. Create a new component viewer instead.')
|
||||
}
|
||||
|
||||
this.log('setWindow', 'component: ', this.componentOrFeature, 'window: ', window)
|
||||
this.services.logger.info('setWindow', 'component: ', this.componentOrFeature, 'window: ', window)
|
||||
|
||||
this.window = window
|
||||
this.sessionKey = UuidGenerator.GenerateUuid()
|
||||
@@ -537,7 +535,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
},
|
||||
})
|
||||
|
||||
this.log('setWindow got new sessionKey', this.sessionKey)
|
||||
this.services.logger.info('setWindow got new sessionKey', this.sessionKey)
|
||||
|
||||
this.postActiveThemes()
|
||||
}
|
||||
@@ -557,9 +555,9 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
}
|
||||
|
||||
handleMessage(message: ComponentMessage): void {
|
||||
this.log('Handle message', message, this)
|
||||
this.services.logger.info('Handle message', message, this)
|
||||
if (!this.componentOrFeature) {
|
||||
this.log('Component not defined for message, returning', message)
|
||||
this.services.logger.info('Component not defined for message, returning', message)
|
||||
void this.services.alerts.alert(
|
||||
'A component is trying to communicate with Standard Notes, ' +
|
||||
'but there is an error establishing a bridge. Please restart the app and try again.',
|
||||
|
||||
@@ -27,6 +27,7 @@ import { LegacyApiService, SessionManager } from '../Api'
|
||||
import { ItemManager } from '../Items'
|
||||
import { DiskStorageService } from '../Storage/DiskStorageService'
|
||||
import { SettingsClientInterface } from '../Settings/SettingsClientInterface'
|
||||
import { LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
describe('FeaturesService', () => {
|
||||
let storageService: StorageServiceInterface
|
||||
@@ -45,26 +46,12 @@ describe('FeaturesService', () => {
|
||||
let items: ItemInterface[]
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
let featureService: FeaturesService
|
||||
|
||||
const createService = () => {
|
||||
return new FeaturesService(
|
||||
storageService,
|
||||
itemManager,
|
||||
mutator,
|
||||
subscriptions,
|
||||
apiService,
|
||||
webSocketsService,
|
||||
settingsService,
|
||||
userService,
|
||||
syncService,
|
||||
alertService,
|
||||
sessionManager,
|
||||
crypto,
|
||||
internalEventBus,
|
||||
)
|
||||
}
|
||||
let logger: LoggerInterface
|
||||
|
||||
beforeEach(() => {
|
||||
logger = {} as jest.Mocked<LoggerInterface>
|
||||
logger.info = jest.fn()
|
||||
|
||||
roles = [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser]
|
||||
|
||||
items = [] as jest.Mocked<ItemInterface[]>
|
||||
@@ -133,6 +120,7 @@ describe('FeaturesService', () => {
|
||||
alertService,
|
||||
sessionManager,
|
||||
crypto,
|
||||
logger,
|
||||
internalEventBus,
|
||||
)
|
||||
})
|
||||
@@ -199,6 +187,25 @@ describe('FeaturesService', () => {
|
||||
|
||||
describe('loadUserRoles()', () => {
|
||||
it('retrieves user roles and features from storage', async () => {
|
||||
const createService = () => {
|
||||
return new FeaturesService(
|
||||
storageService,
|
||||
itemManager,
|
||||
mutator,
|
||||
subscriptions,
|
||||
apiService,
|
||||
webSocketsService,
|
||||
settingsService,
|
||||
userService,
|
||||
syncService,
|
||||
alertService,
|
||||
sessionManager,
|
||||
crypto,
|
||||
logger,
|
||||
internalEventBus,
|
||||
)
|
||||
}
|
||||
|
||||
createService().initializeFromDisk()
|
||||
expect(storageService.getValue).toHaveBeenCalledWith(StorageKey.UserRoles, undefined, [])
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MigrateFeatureRepoToUserSettingUseCase } from './UseCase/MigrateFeatureRepoToUserSetting'
|
||||
import { arraysEqual, removeFromArray, lastElement } from '@standardnotes/utils'
|
||||
import { arraysEqual, removeFromArray, lastElement, LoggerInterface } from '@standardnotes/utils'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { RoleName, ContentType, Uuid } from '@standardnotes/domain-core'
|
||||
import { PROD_OFFLINE_FEATURES_URL } from '../../Hosts'
|
||||
@@ -81,6 +81,7 @@ export class FeaturesService
|
||||
private alerts: AlertService,
|
||||
private sessions: SessionsClientInterface,
|
||||
private crypto: PureCryptoInterface,
|
||||
private logger: LoggerInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
@@ -146,7 +147,7 @@ export class FeaturesService
|
||||
switch (event.type) {
|
||||
case ApiServiceEvent.MetaReceived: {
|
||||
if (!this.sync) {
|
||||
this.log('Handling events interrupted. Sync service is not yet initialized.', event)
|
||||
this.logger.warn('Handling events interrupted. Sync service is not yet initialized.', event)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ContentType } from '@standardnotes/domain-core'
|
||||
import { AlertService, InternalEventBusInterface, ItemRelationshipDirection } from '@standardnotes/services'
|
||||
import { ItemManager } from './ItemManager'
|
||||
import { PayloadManager } from '../Payloads/PayloadManager'
|
||||
import { UuidGenerator, assert } from '@standardnotes/utils'
|
||||
import { LoggerInterface, UuidGenerator, assert } from '@standardnotes/utils'
|
||||
import * as Models from '@standardnotes/models'
|
||||
import {
|
||||
DecryptedPayload,
|
||||
@@ -48,14 +48,18 @@ describe('itemManager', () => {
|
||||
let payloadManager: PayloadManager
|
||||
let itemManager: ItemManager
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
let logger: LoggerInterface
|
||||
|
||||
beforeEach(() => {
|
||||
setupRandomUuid()
|
||||
|
||||
logger = {} as jest.Mocked<LoggerInterface>
|
||||
logger.debug = jest.fn()
|
||||
|
||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
internalEventBus.publish = jest.fn()
|
||||
|
||||
payloadManager = new PayloadManager(internalEventBus)
|
||||
payloadManager = new PayloadManager(logger, internalEventBus)
|
||||
itemManager = new ItemManager(payloadManager, internalEventBus)
|
||||
|
||||
mutator = new MutatorService(itemManager, payloadManager, {} as jest.Mocked<AlertService>, internalEventBus)
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { AlertService, InternalEventBusInterface } from '@standardnotes/services'
|
||||
import { MutatorService, PayloadManager, ItemManager } from '../'
|
||||
import { UuidGenerator, sleep } from '@standardnotes/utils'
|
||||
import { UuidGenerator, sleep, LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
const setupRandomUuid = () => {
|
||||
UuidGenerator.SetGenerator(() => String(Math.random()))
|
||||
@@ -23,13 +23,17 @@ describe('mutator service', () => {
|
||||
let itemManager: ItemManager
|
||||
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
let logger: LoggerInterface
|
||||
|
||||
beforeEach(() => {
|
||||
setupRandomUuid()
|
||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
internalEventBus.publish = jest.fn()
|
||||
|
||||
payloadManager = new PayloadManager(internalEventBus)
|
||||
logger = {} as jest.Mocked<LoggerInterface>
|
||||
logger.debug = jest.fn()
|
||||
|
||||
payloadManager = new PayloadManager(logger, internalEventBus)
|
||||
itemManager = new ItemManager(payloadManager, internalEventBus)
|
||||
|
||||
const alerts = {} as jest.Mocked<AlertService>
|
||||
|
||||
@@ -8,16 +8,21 @@ import {
|
||||
import { PayloadManager } from './PayloadManager'
|
||||
import { InternalEventBusInterface } from '@standardnotes/services'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { LoggerInterface } from '@standardnotes/utils'
|
||||
|
||||
describe('payload manager', () => {
|
||||
let payloadManager: PayloadManager
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
let logger: LoggerInterface
|
||||
|
||||
beforeEach(() => {
|
||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
internalEventBus.publish = jest.fn()
|
||||
|
||||
payloadManager = new PayloadManager(internalEventBus)
|
||||
logger = {} as jest.Mocked<LoggerInterface>
|
||||
logger.debug = jest.fn()
|
||||
|
||||
payloadManager = new PayloadManager(logger, internalEventBus)
|
||||
})
|
||||
|
||||
it('emitting a payload should emit as-is and not merge on top of existing payload', async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { PayloadsChangeObserver, QueueElement, PayloadsChangeObserverCallback, EmitQueue } from './Types'
|
||||
import { removeFromArray, Uuids } from '@standardnotes/utils'
|
||||
import { LoggerInterface, removeFromArray, Uuids } from '@standardnotes/utils'
|
||||
import {
|
||||
DeltaFileImport,
|
||||
isDeletedPayload,
|
||||
@@ -42,7 +42,10 @@ export class PayloadManager extends AbstractService implements PayloadManagerInt
|
||||
public collection: PayloadCollection<FullyFormedPayloadInterface>
|
||||
private emitQueue: EmitQueue<FullyFormedPayloadInterface> = []
|
||||
|
||||
constructor(protected override internalEventBus: InternalEventBusInterface) {
|
||||
constructor(
|
||||
private logger: LoggerInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
this.collection = new PayloadCollection()
|
||||
}
|
||||
@@ -183,7 +186,7 @@ export class PayloadManager extends AbstractService implements PayloadManagerInt
|
||||
continue
|
||||
}
|
||||
|
||||
this.log(
|
||||
this.logger.debug(
|
||||
'applying payload',
|
||||
apply.uuid,
|
||||
'globalDirtyIndexAtLastSync',
|
||||
|
||||
@@ -71,6 +71,7 @@ import {
|
||||
|
||||
export const MINIMUM_PASSWORD_LENGTH = 8
|
||||
export const MissingAccountParams = 'missing-params'
|
||||
const ThirtyMinutes = 30 * 60 * 1000
|
||||
|
||||
const cleanedEmailString = (email: string) => {
|
||||
return email.trim().toLowerCase()
|
||||
@@ -837,4 +838,27 @@ export class SessionManager
|
||||
|
||||
return Result.ok(sessionOrError.getValue())
|
||||
}
|
||||
|
||||
async refreshSessionIfExpiringSoon(): Promise<boolean> {
|
||||
const session = this.getSession()
|
||||
|
||||
if (!session) {
|
||||
return false
|
||||
}
|
||||
if (session instanceof LegacySession) {
|
||||
return false
|
||||
}
|
||||
|
||||
const accessTokenExpiration = new Date(session.accessToken.expiresAt)
|
||||
const refreshTokenExpiration = new Date(session.refreshToken.expiresAt)
|
||||
|
||||
const willAccessTokenExpireSoon = accessTokenExpiration.getTime() - Date.now() < ThirtyMinutes
|
||||
const willRefreshTokenExpireSoon = refreshTokenExpiration.getTime() - Date.now() < ThirtyMinutes
|
||||
|
||||
if (willAccessTokenExpireSoon || willRefreshTokenExpireSoon) {
|
||||
return this.httpService.refreshSession()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
HttpResponse,
|
||||
isErrorResponse,
|
||||
RawSyncResponse,
|
||||
UserEventServerHash,
|
||||
NotificationServerHash,
|
||||
AsymmetricMessageServerHash,
|
||||
getErrorFromErrorResponse,
|
||||
} from '@standardnotes/responses'
|
||||
@@ -29,7 +29,7 @@ export class ServerSyncResponse {
|
||||
readonly asymmetricMessages: AsymmetricMessageServerHash[]
|
||||
readonly vaults: SharedVaultServerHash[]
|
||||
readonly vaultInvites: SharedVaultInviteServerHash[]
|
||||
readonly userEvents: UserEventServerHash[]
|
||||
readonly userEvents: NotificationServerHash[]
|
||||
|
||||
private readonly rawConflictObjects: ConflictParams[]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ConflictParams, ConflictType } from '@standardnotes/responses'
|
||||
import { log, LoggingDomain } from './../../Logging'
|
||||
import { ConflictParams, ConflictType, HttpRequest } from '@standardnotes/responses'
|
||||
import { AccountSyncOperation } from '@Lib/Services/Sync/Account/Operation'
|
||||
import {
|
||||
LoggerInterface,
|
||||
Uuids,
|
||||
extendArray,
|
||||
isNotUndefined,
|
||||
@@ -80,7 +80,7 @@ import {
|
||||
isChunkFullEntry,
|
||||
SyncEventReceivedSharedVaultInvitesData,
|
||||
SyncEventReceivedRemoteSharedVaultsData,
|
||||
SyncEventReceivedUserEventsData,
|
||||
SyncEventReceivedNotificationsData,
|
||||
SyncEventReceivedAsymmetricMessagesData,
|
||||
SyncOpStatus,
|
||||
} from '@standardnotes/services'
|
||||
@@ -160,6 +160,7 @@ export class SyncService
|
||||
private device: DeviceInterface,
|
||||
private identifier: string,
|
||||
private readonly options: ApplicationSyncOptions,
|
||||
private logger: LoggerInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
@@ -258,7 +259,7 @@ export class SyncService
|
||||
}
|
||||
|
||||
public async loadDatabasePayloads(): Promise<void> {
|
||||
log(LoggingDomain.DatabaseLoad, 'Loading database payloads')
|
||||
this.logger.debug('Loading database payloads')
|
||||
|
||||
if (this.databaseLoaded) {
|
||||
throw 'Attempting to initialize already initialized local database.'
|
||||
@@ -353,7 +354,7 @@ export class SyncService
|
||||
currentPosition?: number,
|
||||
payloadCount?: number,
|
||||
) {
|
||||
log(LoggingDomain.DatabaseLoad, 'Processing batch at index', currentPosition, 'length', batch.length)
|
||||
this.logger.debug('Processing batch at index', currentPosition, 'length', batch.length)
|
||||
const encrypted: EncryptedPayloadInterface[] = []
|
||||
const nonencrypted: (DecryptedPayloadInterface | DeletedPayloadInterface)[] = []
|
||||
|
||||
@@ -419,7 +420,7 @@ export class SyncService
|
||||
}
|
||||
|
||||
public async markAllItemsAsNeedingSyncAndPersist(): Promise<void> {
|
||||
log(LoggingDomain.Sync, 'Marking all items as needing sync')
|
||||
this.logger.debug('Marking all items as needing sync')
|
||||
|
||||
const items = this.itemManager.items
|
||||
const payloads = items.map((item) => {
|
||||
@@ -485,7 +486,7 @@ export class SyncService
|
||||
|
||||
const promise = this.spawnQueue[0]
|
||||
removeFromIndex(this.spawnQueue, 0)
|
||||
log(LoggingDomain.Sync, 'Syncing again from spawn queue')
|
||||
this.logger.debug('Syncing again from spawn queue')
|
||||
|
||||
return this.sync({
|
||||
queueStrategy: SyncQueueStrategy.ForceSpawnNew,
|
||||
@@ -547,7 +548,7 @@ export class SyncService
|
||||
|
||||
public async sync(options: Partial<SyncOptions> = {}): Promise<unknown> {
|
||||
if (this.clientLocked) {
|
||||
log(LoggingDomain.Sync, 'Sync locked by client')
|
||||
this.logger.debug('Sync locked by client')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -613,8 +614,7 @@ export class SyncService
|
||||
if (shouldExecuteSync) {
|
||||
this.syncLock = true
|
||||
} else {
|
||||
log(
|
||||
LoggingDomain.Sync,
|
||||
this.logger.debug(
|
||||
!canExecuteSync
|
||||
? 'Another function call has begun preparing for sync.'
|
||||
: syncInProgress
|
||||
@@ -727,8 +727,7 @@ export class SyncService
|
||||
payloads: (DeletedPayloadInterface | DecryptedPayloadInterface)[],
|
||||
options: SyncOptions,
|
||||
) {
|
||||
log(
|
||||
LoggingDomain.Sync,
|
||||
this.logger.debug(
|
||||
'Syncing offline user',
|
||||
'source:',
|
||||
SyncSource[options.source],
|
||||
@@ -812,8 +811,7 @@ export class SyncService
|
||||
},
|
||||
)
|
||||
|
||||
log(
|
||||
LoggingDomain.Sync,
|
||||
this.logger.debug(
|
||||
'Syncing online user',
|
||||
'source',
|
||||
SyncSource[options.source],
|
||||
@@ -924,8 +922,28 @@ export class SyncService
|
||||
return undefined
|
||||
}
|
||||
|
||||
async getRawSyncRequestForExternalUse(
|
||||
items: (DecryptedItemInterface | DeletedItemInterface)[],
|
||||
): Promise<HttpRequest | undefined> {
|
||||
if (this.dealloced) {
|
||||
return
|
||||
}
|
||||
|
||||
const online = this.sessionManager.online()
|
||||
|
||||
if (!online) {
|
||||
return
|
||||
}
|
||||
|
||||
const payloads = await this.payloadsByPreparingForServer(items.map((i) => i.payloadRepresentation()))
|
||||
const syncToken = await this.getLastSyncToken()
|
||||
const paginationToken = await this.getPaginationToken()
|
||||
|
||||
return this.apiService.getSyncHttpRequest(payloads, syncToken, paginationToken, 150)
|
||||
}
|
||||
|
||||
private async handleOfflineResponse(response: OfflineSyncResponse) {
|
||||
log(LoggingDomain.Sync, 'Offline Sync Response', response)
|
||||
this.logger.debug('Offline Sync Response', response)
|
||||
|
||||
const masterCollection = this.payloadManager.getMasterCollection()
|
||||
|
||||
@@ -943,7 +961,7 @@ export class SyncService
|
||||
}
|
||||
|
||||
private handleErrorServerResponse(response: ServerSyncResponse) {
|
||||
log(LoggingDomain.Sync, 'Sync Error', response)
|
||||
this.logger.debug('Sync Error', response)
|
||||
|
||||
if (response.status === INVALID_SESSION_RESPONSE_STATUS) {
|
||||
void this.notifyEvent(SyncEvent.InvalidSession)
|
||||
@@ -968,7 +986,10 @@ export class SyncService
|
||||
const historyMap = this.historyService.getHistoryMapCopy()
|
||||
|
||||
if (response.userEvents && response.userEvents.length > 0) {
|
||||
await this.notifyEventSync(SyncEvent.ReceivedUserEvents, response.userEvents as SyncEventReceivedUserEventsData)
|
||||
await this.notifyEventSync(
|
||||
SyncEvent.ReceivedNotifications,
|
||||
response.userEvents as SyncEventReceivedNotificationsData,
|
||||
)
|
||||
}
|
||||
|
||||
if (response.asymmetricMessages && response.asymmetricMessages.length > 0) {
|
||||
@@ -1003,8 +1024,7 @@ export class SyncService
|
||||
historyMap,
|
||||
)
|
||||
|
||||
log(
|
||||
LoggingDomain.Sync,
|
||||
this.logger.debug(
|
||||
'Online Sync Response',
|
||||
'Operator ID',
|
||||
operation.id,
|
||||
@@ -1263,7 +1283,7 @@ export class SyncService
|
||||
}
|
||||
|
||||
private async syncAgainByHandlingRequestsWaitingInResolveQueue(options: SyncOptions) {
|
||||
log(LoggingDomain.Sync, 'Syncing again from resolve queue')
|
||||
this.logger.debug('Syncing again from resolve queue')
|
||||
const promise = this.sync({
|
||||
source: SyncSource.ResolveQueue,
|
||||
checkIntegrity: options.checkIntegrity,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const VaultTests = {
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
exclusive: false,
|
||||
files: [
|
||||
'vaults/vaults.test.js',
|
||||
|
||||
@@ -112,7 +112,7 @@ describe('application instances', () => {
|
||||
await app.lock()
|
||||
})
|
||||
|
||||
describe.skip('signOut()', () => {
|
||||
describe('signOut()', () => {
|
||||
let testNote1
|
||||
let confirmAlert
|
||||
let deinit
|
||||
@@ -129,7 +129,7 @@ describe('application instances', () => {
|
||||
beforeEach(async () => {
|
||||
testSNApp = await Factory.createAndInitializeApplication('test-application')
|
||||
testNote1 = await Factory.createMappedNote(testSNApp, 'Note 1', 'This is a test note!', false)
|
||||
confirmAlert = sinon.spy(testSNApp.alertService, 'confirm')
|
||||
confirmAlert = sinon.spy(testSNApp.alerts, 'confirm')
|
||||
deinit = sinon.spy(testSNApp, 'deinit')
|
||||
})
|
||||
|
||||
@@ -164,7 +164,7 @@ describe('application instances', () => {
|
||||
|
||||
it('cancels sign out if confirmation dialog is rejected', async () => {
|
||||
confirmAlert.restore()
|
||||
confirmAlert = sinon.stub(testSNApp.alertService, 'confirm').callsFake((_message) => false)
|
||||
confirmAlert = sinon.stub(testSNApp.alerts, 'confirm').callsFake((_message) => false)
|
||||
|
||||
await testSNApp.mutator.setItemDirty(testNote1)
|
||||
await testSNApp.user.signOut()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable no-undef */
|
||||
import { BaseItemCounts } from './lib/BaseItemCounts.js'
|
||||
import * as Factory from './lib/factory.js'
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
const expect = chai.expect
|
||||
|
||||
@@ -23,7 +22,7 @@ describe('basic auth', function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
context = await Factory.createAppContextWithFakeCrypto()
|
||||
|
||||
await context.launch()
|
||||
|
||||
@@ -70,7 +69,14 @@ describe('basic auth', function () {
|
||||
|
||||
await context.signout()
|
||||
|
||||
const response = await context.application.signIn(context.email, context.password, undefined, undefined, undefined, true)
|
||||
const response = await context.application.signIn(
|
||||
context.email,
|
||||
context.password,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
)
|
||||
expect(response).to.be.ok
|
||||
expect(response.data.error).to.not.be.ok
|
||||
expect(await context.application.encryption.getRootKey()).to.be.ok
|
||||
@@ -81,7 +87,14 @@ describe('basic auth', function () {
|
||||
await Factory.createSyncedNote(context.application)
|
||||
await context.signout()
|
||||
|
||||
const response = await context.application.signIn(context.email, context.password, undefined, undefined, undefined, true)
|
||||
const response = await context.application.signIn(
|
||||
context.email,
|
||||
context.password,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
)
|
||||
expect(response).to.be.ok
|
||||
expect(response.data.error).to.not.be.ok
|
||||
expect(await context.application.encryption.getRootKey()).to.be.ok
|
||||
@@ -113,7 +126,14 @@ describe('basic auth', function () {
|
||||
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
const response = await context.application.signIn(context.email, context.password, undefined, undefined, undefined, true)
|
||||
const response = await context.application.signIn(
|
||||
context.email,
|
||||
context.password,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
)
|
||||
expect(response).to.be.ok
|
||||
expect(response.data.error).to.not.be.ok
|
||||
expect(await context.application.encryption.getRootKey()).to.be.ok
|
||||
@@ -160,7 +180,14 @@ describe('basic auth', function () {
|
||||
await context.register()
|
||||
await context.signout()
|
||||
|
||||
let response = await context.application.signIn(context.email, 'wrong password', undefined, undefined, undefined, true)
|
||||
let response = await context.application.signIn(
|
||||
context.email,
|
||||
'wrong password',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
)
|
||||
expect(response).to.have.property('status', 401)
|
||||
expect(response.data.error).to.be.ok
|
||||
|
||||
@@ -207,7 +234,6 @@ describe('basic auth', function () {
|
||||
await specContext.launch()
|
||||
await specContext.register()
|
||||
await specContext.signout()
|
||||
await specContext.deinit()
|
||||
|
||||
specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), uppercase, password)
|
||||
|
||||
@@ -217,6 +243,7 @@ describe('basic auth', function () {
|
||||
expect(response).to.be.ok
|
||||
expect(response.data.error).to.not.be.ok
|
||||
expect(await specContext.application.encryption.getRootKey()).to.be.ok
|
||||
await specContext.deinit()
|
||||
}).timeout(20000)
|
||||
|
||||
it('can sign into account regardless of whitespace', async function () {
|
||||
@@ -232,7 +259,6 @@ describe('basic auth', function () {
|
||||
await specContext.launch()
|
||||
await specContext.register()
|
||||
await specContext.signout()
|
||||
await specContext.deinit()
|
||||
|
||||
specContext = await Factory.createAppContextWithFakeCrypto(Math.random(), withspace, password)
|
||||
await specContext.launch()
|
||||
@@ -241,12 +267,20 @@ describe('basic auth', function () {
|
||||
expect(response).to.be.ok
|
||||
expect(response.data.error).to.not.be.ok
|
||||
expect(await specContext.application.encryption.getRootKey()).to.be.ok
|
||||
await specContext.deinit()
|
||||
}).timeout(20000)
|
||||
|
||||
it('fails login with wrong password', async function () {
|
||||
await context.register()
|
||||
context.application = await Factory.signOutApplicationAndReturnNew(context.application)
|
||||
const response = await context.application.signIn(context.email, 'wrongpassword', undefined, undefined, undefined, true)
|
||||
const response = await context.application.signIn(
|
||||
context.email,
|
||||
'wrongpassword',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
)
|
||||
expect(response).to.be.ok
|
||||
expect(response.data.error).to.be.ok
|
||||
expect(await context.application.encryption.getRootKey()).to.not.be.ok
|
||||
@@ -300,102 +334,77 @@ describe('basic auth', function () {
|
||||
expect(outOfSync).to.equal(false)
|
||||
})
|
||||
|
||||
async function changePassword() {
|
||||
it('successfully changes password', async function () {
|
||||
await context.register()
|
||||
|
||||
const noteCount = 10
|
||||
|
||||
const noteCount = 5
|
||||
await Factory.createManyMappedNotes(context.application, noteCount)
|
||||
|
||||
this.expectedItemCount += noteCount
|
||||
|
||||
await context.application.sync.sync(syncOptions)
|
||||
|
||||
await context.sync()
|
||||
expect(context.application.items.items.length).to.equal(this.expectedItemCount)
|
||||
|
||||
const newPassword = 'newpassword'
|
||||
const response = await context.application.changePassword(context.password, newPassword)
|
||||
|
||||
/** New items key */
|
||||
this.expectedItemCount++
|
||||
|
||||
expect(context.application.items.items.length).to.equal(this.expectedItemCount)
|
||||
|
||||
expect(response.error).to.not.be.ok
|
||||
|
||||
this.expectedItemCount += ['new items key'].length
|
||||
expect(context.application.items.items.length).to.equal(this.expectedItemCount)
|
||||
expect(context.application.payloads.invalidPayloads.length).to.equal(0)
|
||||
|
||||
await context.application.sync.markAllItemsAsNeedingSyncAndPersist()
|
||||
await context.application.sync.sync(syncOptions)
|
||||
await context.sync(syncOptions)
|
||||
|
||||
expect(context.application.items.items.length).to.equal(this.expectedItemCount)
|
||||
}).timeout(40000)
|
||||
|
||||
const note = context.application.items.getDisplayableNotes()[0]
|
||||
it('should sign into account after changing password', async function () {
|
||||
await context.register()
|
||||
|
||||
/**
|
||||
* Create conflict for a note. First modify the item without saving so that
|
||||
* our local contents digress from the server's
|
||||
*/
|
||||
await context.application.mutator.changeItem(note, (mutator) => {
|
||||
mutator.title = `${Math.random()}`
|
||||
})
|
||||
const newPassword = 'newpassword'
|
||||
const response = await context.application.changePassword(context.password, newPassword)
|
||||
expect(response.error).to.not.be.ok
|
||||
|
||||
await Factory.changePayloadTimeStampAndSync(
|
||||
context.application,
|
||||
note.payload,
|
||||
Factory.dateToMicroseconds(Factory.yesterday()),
|
||||
{
|
||||
title: `${Math.random()}`,
|
||||
},
|
||||
syncOptions,
|
||||
)
|
||||
this.expectedItemCount++
|
||||
this.expectedItemCount += ['new items key'].length
|
||||
|
||||
await context.signout()
|
||||
|
||||
/** Should login with new password */
|
||||
const signinResponse = await context.application.signIn(context.email, newPassword, undefined, undefined, undefined, true)
|
||||
const signinResponse = await context.application.signIn(
|
||||
context.email,
|
||||
newPassword,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
)
|
||||
|
||||
expect(signinResponse).to.be.ok
|
||||
expect(signinResponse.data.error).to.not.be.ok
|
||||
|
||||
expect(await context.application.encryption.getRootKey()).to.be.ok
|
||||
|
||||
expect(context.application.items.items.length).to.equal(this.expectedItemCount)
|
||||
expect(context.application.payloads.invalidPayloads.length).to.equal(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('successfully changes password', changePassword).timeout(40000)
|
||||
|
||||
it.skip('successfully changes password when passcode is set', async function () {
|
||||
it('successfully changes password when passcode is set', async function () {
|
||||
const passcode = 'passcode'
|
||||
const promptValueReply = (prompts) => {
|
||||
const values = []
|
||||
for (const prompt of prompts) {
|
||||
if (prompt.validation === ChallengeValidation.LocalPasscode) {
|
||||
values.push(CreateChallengeValue(prompt, passcode))
|
||||
} else {
|
||||
values.push(CreateChallengeValue(prompt, context.password))
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
context.application.setLaunchCallback({
|
||||
receiveChallenge: (challenge) => {
|
||||
context.application.addChallengeObserver(challenge, {
|
||||
onInvalidValue: (value) => {
|
||||
const values = promptValueReply([value.prompt])
|
||||
context.application.submitValuesForChallenge(challenge, values)
|
||||
numPasscodeAttempts++
|
||||
},
|
||||
})
|
||||
const initialValues = promptValueReply(challenge.prompts)
|
||||
context.application.submitValuesForChallenge(challenge, initialValues)
|
||||
},
|
||||
})
|
||||
await context.application.setPasscode(passcode)
|
||||
await changePassword.bind(this)()
|
||||
}).timeout(20000)
|
||||
await context.addPasscode(passcode)
|
||||
await context.register()
|
||||
|
||||
const noteCount = 3
|
||||
await Factory.createManyMappedNotes(context.application, noteCount)
|
||||
this.expectedItemCount += noteCount
|
||||
|
||||
await context.sync()
|
||||
|
||||
const newPassword = 'newpassword'
|
||||
const response = await context.application.changePassword(context.password, newPassword)
|
||||
expect(response.error).to.not.be.ok
|
||||
|
||||
this.expectedItemCount += ['new items key'].length
|
||||
|
||||
expect(context.application.items.items.length).to.equal(this.expectedItemCount)
|
||||
})
|
||||
|
||||
it('changes password many times', async function () {
|
||||
await context.register()
|
||||
@@ -541,15 +550,19 @@ describe('basic auth', function () {
|
||||
expect(signOutSpy.callCount).to.equal(1)
|
||||
}).timeout(Factory.TenSecondTimeout)
|
||||
|
||||
it('should not allow to delete someone else\'s account', async function () {
|
||||
it("should not allow to delete someone else's account", async function () {
|
||||
const secondContext = await Factory.createAppContextWithRealCrypto()
|
||||
await secondContext.launch()
|
||||
const registerResponse = await secondContext.register()
|
||||
|
||||
const response = await context.application.dependencies.get(TYPES.UserApiService).deleteAccount(registerResponse.user.uuid)
|
||||
const response = await context.application.dependencies
|
||||
.get(TYPES.UserApiService)
|
||||
.deleteAccount(registerResponse.user.uuid)
|
||||
|
||||
expect(response.status).to.equal(401)
|
||||
expect(response.data.error.message).to.equal('Operation not allowed.')
|
||||
|
||||
await secondContext.deinit()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -81,7 +81,6 @@ describe('backups', function () {
|
||||
})
|
||||
|
||||
it('passcode + account backup file should have correct number of items', async function () {
|
||||
this.timeout(10000)
|
||||
const passcode = 'passcode'
|
||||
await this.application.register(this.email, this.password)
|
||||
Factory.handlePasswordChallenges(this.application, this.password)
|
||||
@@ -101,7 +100,7 @@ describe('backups', function () {
|
||||
// Encrypted backup with authorization
|
||||
const authorizedEncryptedData = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
|
||||
expect(authorizedEncryptedData.items.length).to.equal(BaseItemCounts.DefaultItemsWithAccount + 2)
|
||||
})
|
||||
}).timeout(10000)
|
||||
|
||||
it('backup file item should have correct fields', async function () {
|
||||
await Factory.createSyncedNote(this.application)
|
||||
|
||||
@@ -133,11 +133,12 @@ describe('features', () => {
|
||||
await promise
|
||||
})
|
||||
|
||||
it.skip('migrated ext repo should have property indicating it was migrated', async () => {
|
||||
it('migrated ext repo should have property indicating it was migrated', async () => {
|
||||
sinon.stub(application.legacyApi, 'isThirdPartyHostUsed').callsFake(() => {
|
||||
return false
|
||||
})
|
||||
expect(await application.settings.getDoesSensitiveSettingExist(SettingName.ExtensionKey)).to.equal(false)
|
||||
const setting = SettingName.create(SettingName.NAMES.ExtensionKey).getValue()
|
||||
expect(await application.settings.getDoesSensitiveSettingExist(setting)).to.equal(false)
|
||||
const extensionKey = UuidGenerator.GenerateUuid().split('-').join('')
|
||||
const promise = new Promise((resolve) => {
|
||||
application.streamItems(ContentType.TYPES.ExtensionRepo, ({ changed }) => {
|
||||
|
||||
@@ -404,7 +404,7 @@ describe('history manager', () => {
|
||||
|
||||
expect(noteHistory.length).to.equal(expectedRevisions)
|
||||
expect(dupeHistory.length).to.equal(expectedRevisions + 1)
|
||||
})
|
||||
}).timeout(Factory.ThirtySecondTimeout)
|
||||
|
||||
it('can decrypt revisions for duplicate_of items', async function () {
|
||||
const note = await Factory.createSyncedNote(this.application)
|
||||
|
||||
@@ -449,6 +449,7 @@ describe('keys', function () {
|
||||
},
|
||||
})
|
||||
expect(payload.items_key_id).to.equal(newDefaultItemsKey.uuid)
|
||||
await Factory.safeDeinit(application)
|
||||
})
|
||||
|
||||
it('compares root keys', async function () {
|
||||
@@ -642,7 +643,7 @@ describe('keys', function () {
|
||||
await contextB.deinit()
|
||||
})
|
||||
|
||||
describe('changing password on 003 client while signed into 004 client', function () {
|
||||
describe('changing password on 003 account while signed into 004 client', function () {
|
||||
/**
|
||||
* When an 004 client signs into 003 account, it creates a root key based items key.
|
||||
* Then, if the 003 client changes its account password, and the 004 client
|
||||
@@ -859,5 +860,6 @@ describe('keys', function () {
|
||||
const notePayload = rawPayloads.find((p) => p.content_type === ContentType.TYPES.Note)
|
||||
|
||||
expect(notePayload.items_key_id).to.equal(itemsKey.uuid)
|
||||
await otherClient.deinit()
|
||||
})
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user