Compare commits

...
Author SHA1 Message Date
StandardNotes CI 7205cf9bd6 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-04 15:11:54 +00:00
Mo 494436bdb6 refactor: key rotation (#2383) 2023-08-04 09:25:28 -05:00
StandardNotes CI a7f266bb68 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-04 13:30:52 +00:00
Karol Sójko c42a559955 chore: remove e2e tests that are invalid due to session invalidation upon password change (#2384)
* chore: remove e2e tests that are invalid due to session invalidation upon password change

* chore: move invalid session e2e tests to a skipped suite for revisiting
2023-08-04 15:02:31 +02:00
StandardNotes CI afc32121ee chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-04 11:26:54 +00:00
Aman Harwara 1abacff890 chore: fix blur issues on alert dialogs on chrome [skip e2e] 2023-08-04 16:37:28 +05:30
Aman Harwara fc500b08d2 chore: fix issue where popovers & modals are sometimes blurry on chrome 2023-08-04 16:22:14 +05:30
StandardNotes CI 0642b589ff chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-03 19:48:59 +00:00
Aman Harwara f7a1c6ea65 fix: Fixed issue where moving the floating keyboard on iPad would lead to blank space on screen 2023-08-04 00:30:12 +05:30
StandardNotes CI e8c7c10358 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-03 14:42:10 +00:00
Aman Harwara b3eab998f9 chore: fix colors 2023-08-03 19:17:47 +05:30
Aman Harwara c76ffc764b clipper: handle clipped note sync in background (#2367) 2023-08-03 18:43:04 +05:30
Aman Harwara 5602a4014e chore: improve super formatting toolbar contrast 2023-08-03 18:24:50 +05:30
StandardNotes CI 0a544e936c chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-03 12:13:07 +00:00
Karol Sójko 027dd41147 fix: exit interview discount link not working (#2381) 2023-08-03 13:28:27 +02:00
StandardNotes CI 2036689dd6 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-02 19:14:59 +00:00
Mo 25596e70a8 refactor: test 2023-08-02 13:27:13 -05:00
Mo 9961f83246 chore: vault tests on (#2380) 2023-08-02 11:02:04 -05:00
StandardNotes CI 6caf7c959a chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-02 13:01:51 +00:00
Mo 8ebb0949f7 chore: skip test 2023-08-02 07:28:50 -05:00
Mo 846e5b3ca8 chore: password tests 2023-08-02 06:54:18 -05:00
Mo 4b6bd452f7 chore: test skip 2023-08-01 20:46:14 -05:00
Mo 66d26ff44b chore: test timeout 2023-08-01 18:38:40 -05:00
Karol Sójko 247daddf5a chore: vault tests refactors and lint (#2374) 2023-08-01 17:23:56 -05:00
Mo a0bc1d2488 chore: readme [no ci] 2023-08-01 16:53:53 -05:00
StandardNotes CI 146f4540a8 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected]
2023-08-01 20:34:01 +00:00
Aman Harwara 6f7a72d645 chore: fix super block picker menu item icons 2023-08-02 01:28:28 +05:30
Aman Harwara a059c2e7e9 chore: fix super icons 2023-08-02 01:20:14 +05:30
Mo 73609ca7e3 fix: Fixes issue where selecting a third-party editor/note-type as default for a tag would not correctly apply editor 2023-08-01 13:53:15 -05:00
Mo fe77508061 chore: update package files [no ci] 2023-08-01 11:38:49 -05:00
StandardNotes CI a24ceecb63 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-01 13:27:26 +00:00
Aman Harwara b58898687e chore: show toast when duplicating note and update last modified date for duplicated note (#2379) 2023-08-01 18:23:20 +05:30
232 changed files with 4032 additions and 2387 deletions
+1 -1
View File
@@ -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
+1
View File
@@ -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",
+18
View File
@@ -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.
## [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.26.36](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-28)
**Note:** Version bump only for package @standardnotes/api
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.26.36",
"version": "1.26.40",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -14,7 +14,7 @@
"publishConfig": {
"access": "public"
},
"license": "AGPL-3.0-or-later",
"license": "CC BY-NC-SA 4.0",
"scripts": {
"clean": "rm -fr dist",
"prestart": "yarn clean",
@@ -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) {
+9 -3
View File
@@ -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
View File
@@ -1,3 +1,4 @@
export * from './HttpService'
export * from './FetchRequestHandler'
export * from './HttpServiceInterface'
export * from './XMLHttpRequestState'
+40
View File
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.138](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.137](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.136](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/clipper
## [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.1.129](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.128](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
**Note:** Version bump only for package @standardnotes/clipper
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/clipper",
"description": "Web clipper browser extension for Standard Notes",
"version": "1.1.128",
"version": "1.1.138",
"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)
}
})
+14 -1
View File
@@ -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
>
}
+40
View File
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.108.69](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.68](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.67](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/desktop
## [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
## [3.108.60](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.59](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
**Note:** Version bump only for package @standardnotes/desktop
+2 -2
View File
@@ -1,8 +1,8 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.108.59",
"license": "AGPL-3.0-or-later",
"version": "3.108.69",
"license": "CC BY-NC-SA 4.0",
"author": "Standard Notes.",
"private": true,
"repository": {
+12
View File
@@ -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.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.21.59](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-28)
**Note:** Version bump only for package @standardnotes/encryption
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.21.59",
"version": "1.21.62",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -8,7 +8,7 @@
"main": "./src/index.ts",
"private": true,
"author": "Standard Notes",
"license": "AGPL-3.0-or-later",
"license": "CC BY-NC-SA 4.0",
"scripts": {
"lint": "eslint src --ext .ts",
"format": "prettier --write src",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.59.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
**Note:** Version bump only for package @standardnotes/features
## [1.59.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-28)
**Note:** Version bump only for package @standardnotes/features
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/features",
"version": "1.59.13",
"version": "1.59.14",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -14,7 +14,7 @@
"publishConfig": {
"access": "public"
},
"license": "AGPL-3.0-or-later",
"license": "CC BY-NC-SA 4.0",
"scripts": {
"clean": "rm -fr dist",
"prestart": "yarn clean",
+12
View File
@@ -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.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.28.69](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-28)
**Note:** Version bump only for package @standardnotes/filepicker
+1 -1
View File
@@ -8,7 +8,7 @@
"publishConfig": {
"access": "public"
},
"license": "AGPL-3.0-or-later",
"license": "CC BY-NC-SA 4.0",
"scripts": {
"clean": "rm -fr dist",
"prestart": "yarn clean",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.28.69",
"version": "1.28.72",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -9,7 +9,7 @@
"author": "Standard Notes",
"types": "./src/index.ts",
"private": true,
"license": "AGPL-3.0-or-later",
"license": "CC BY-NC-SA 4.0",
"scripts": {
"build": "echo 'Empty build script required for yarn topological install'",
"lint": "eslint src --ext .ts",
+12
View File
@@ -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.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.16.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-28)
**Note:** Version bump only for package @standardnotes/files
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.16.15",
"version": "1.16.18",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -12,7 +12,7 @@
"dist"
],
"private": true,
"license": "AGPL-3.0-or-later",
"license": "CC BY-NC-SA 4.0",
"scripts": {
"lint": "eslint src --ext .ts",
"test": "jest",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
**Note:** Version bump only for package @standardnotes/icons
## [1.12.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-13)
**Note:** Version bump only for package @standardnotes/icons
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/icons",
"version": "1.12.2",
"version": "1.12.3",
"private": true,
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 16">
<path fill="currentColor"
d="M7.62 7.18L2.79 3.03c-.7-.6-1.79-.1-1.79.82v8.29c0 .93 1.09 1.42 1.79.82l4.83-4.14c.5-.43.5-1.21 0-1.64Z" />
</svg>

After

Width:  |  Height:  |  Size: 207 B

+2
View File
@@ -29,6 +29,7 @@ import BoldIcon from './ic-bold.svg'
import BoxFilledIcon from './ic-box-filled.svg'
import BoxIcon from './ic-box.svg'
import CameraIcon from './ic-camera.svg'
import CaretRightIcon from './ic-caret-right.svg'
import CheckAllIcon from './ic-check-all.svg'
import CheckBoldIcon from './ic-check-bold.svg'
import CheckCircleFilledIcon from './ic-check-circle-filled.svg'
@@ -242,6 +243,7 @@ export {
BoxFilledIcon,
BoxIcon,
CameraIcon,
CaretRightIcon,
CheckAllIcon,
CheckBoldIcon,
CheckCircleFilledIcon,
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="bi bi-caret-right-fill"><path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/></svg>

Before

Width:  |  Height:  |  Size: 241 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="bi bi-chat-square-quote"><path d="M14 1a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-2.5a2 2 0 0 0-1.6.8L8 14.333 6.1 11.8a2 2 0 0 0-1.6-.8H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2.5a1 1 0 0 1 .8.4l1.9 2.533a1 1 0 0 0 1.6 0l1.9-2.533a1 1 0 0 1 .8-.4H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/><path d="M7.066 4.76A1.665 1.665 0 0 0 4 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112zm4 0A1.665 1.665 0 0 0 8 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M9 22c-.6 0-1-.4-1-1v-3H4c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2h-6.1l-3.7 3.7c-.2.2-.4.3-.7.3H9m1-6v3.1l3.1-3.1H20V4H4v12h6m6.3-10l-1.4 3H17v4h-4V8.8L14.3 6h2m-6 0L8.9 9H11v4H7V8.8L8.3 6h2Z"/></svg>

Before

Width:  |  Height:  |  Size: 705 B

After

Width:  |  Height:  |  Size: 317 B

-2
View File
@@ -1,4 +1,3 @@
import LexicalCaretRightFill from './caret-right-fill.svg'
import LexicalCheck from './square-check.svg'
import LexicalCode from './code.svg'
import LexicalHorizontalRule from './horizontal-rule.svg'
@@ -30,7 +29,6 @@ import LexicalPencilFill from './pencil-fill.svg'
import LexicalDraggableBlockMenu from './draggable-block-menu.svg'
export {
LexicalCaretRightFill,
LexicalCheck,
LexicalCode,
LexicalHorizontalRule,
+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="bi bi-table"><path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"/></svg>

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 324 B

+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="bi bi-twitter"><path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M22.46 6c-.77.35-1.6.58-2.46.69c.88-.53 1.56-1.37 1.88-2.38c-.83.5-1.75.85-2.72 1.05C18.37 4.5 17.26 4 16 4c-2.35 0-4.27 1.92-4.27 4.29c0 .34.04.67.11.98C8.28 9.09 5.11 7.38 3 4.79c-.37.63-.58 1.37-.58 2.15c0 1.49.75 2.81 1.91 3.56c-.71 0-1.37-.2-1.95-.5v.03c0 2.08 1.48 3.82 3.44 4.21a4.22 4.22 0 0 1-1.93.07a4.28 4.28 0 0 0 4 2.98a8.521 8.521 0 0 1-5.33 1.84c-.34 0-.68-.02-1.02-.06C3.44 20.29 5.7 21 8.12 21C16 21 20.33 14.46 20.33 8.79c0-.19 0-.37-.01-.56c.84-.6 1.56-1.36 2.14-2.23Z"/></svg>

Before

Width:  |  Height:  |  Size: 613 B

After

Width:  |  Height:  |  Size: 585 B

+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="bi bi-youtube"><path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408L6.4 5.209z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="m10 15l5.19-3L10 9v6m11.56-7.83c.13.47.22 1.1.28 1.9c.07.8.1 1.49.1 2.09L22 12c0 2.19-.16 3.8-.44 4.83c-.25.9-.83 1.48-1.73 1.73c-.47.13-1.33.22-2.65.28c-1.3.07-2.49.1-3.59.1L12 19c-4.19 0-6.8-.16-7.83-.44c-.9-.25-1.48-.83-1.73-1.73c-.13-.47-.22-1.1-.28-1.9c-.07-.8-.1-1.49-.1-2.09L2 12c0-2.19.16-3.8.44-4.83c.25-.9.83-1.48 1.73-1.73c.47-.13 1.33-.22 2.65-.28c1.3-.07 2.49-.1 3.59-.1L12 5c4.19 0 6.8.16 7.83.44c.9.25 1.48.83 1.73 1.73Z"/></svg>

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 533 B

-5
View File
@@ -1,5 +0,0 @@
recursive: true
timeout: 120000
bail: true
file:
- e2e/init.js
+42
View File
@@ -3,6 +3,48 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.56.49](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.48](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.47](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/mobile
## [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
## [3.56.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.39](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
**Note:** Version bump only for package @standardnotes/mobile
+2 -2
View File
@@ -1,9 +1,9 @@
{
"name": "@standardnotes/mobile",
"version": "3.56.39",
"version": "3.56.49",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
"license": "CC BY-NC-SA 4.0",
"scripts": {
"android-dev": "react-native run-android --variant devDebug --appIdSuffix dev",
"android-prod-debug": "react-native run-android --variant prodDebug",
@@ -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,
},
}),
)
})
+12
View File
@@ -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.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.46.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-28)
**Note:** Version bump only for package @standardnotes/models
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.46.18",
"version": "1.46.21",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -11,7 +11,7 @@
"author": "Standard Notes",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"license": "AGPL-3.0-or-later",
"license": "CC BY-NC-SA 4.0",
"scripts": {
"clean": "rm -fr dist",
"prestart": "yarn clean",
@@ -2,4 +2,5 @@ export enum Environment {
Web = 1,
Desktop = 2,
Mobile = 3,
Clipper = 4,
}
+40
View File
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.421](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.420](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.419](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/releases
## [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.4.412](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.411](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-01)
**Note:** Version bump only for package @standardnotes/releases
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/releases",
"version": "1.4.411",
"license": "AGPL-3.0-or-later",
"version": "1.4.421",
"license": "CC BY-NC-SA 4.0",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
"publishConfig": {
+8
View File
@@ -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.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.13.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-28)
**Note:** Version bump only for package @standardnotes/responses
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/responses",
"version": "1.13.34",
"version": "1.13.36",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -13,7 +13,7 @@
"publishConfig": {
"access": "public"
},
"license": "AGPL-3.0-or-later",
"license": "CC BY-NC-SA 4.0",
"scripts": {
"clean": "rm -fr dist",
"prestart": "yarn clean",
@@ -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,4 +1,4 @@
export type UserEventServerHash = {
export type NotificationServerHash = {
uuid: string
user_uuid: string
type: string
+1 -1
View File
@@ -66,4 +66,4 @@ export * from './User/PostSubscriptionTokensResponse'
export * from './User/SettingData'
export * from './User/UpdateSettingResponse'
export * from './UserEvent/UserEventServerHash'
export * from './Notification/NotificationServerHash'
+22
View File
@@ -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.
## [1.63.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/services
## [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
* Fixes issue where selecting a third-party editor/note-type as default for a tag would not correctly apply editor ([73609ca](https://github.com/standardnotes/app/commit/73609ca7e31c6cc628f46319f0b9502f7b8afcd3))
## [1.63.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-31)
**Note:** Version bump only for package @standardnotes/services
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.63.18",
"version": "1.63.23",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -8,7 +8,7 @@
"main": "./src/index.ts",
"author": "Standard Notes",
"private": true,
"license": "AGPL-3.0-or-later",
"license": "CC BY-NC-SA 4.0",
"scripts": {
"tsc": "tsc --project tsconfig.json",
"lint": "eslint src --ext .ts && yarn tsc",
@@ -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
}
@@ -34,7 +34,7 @@ import { StorageValueModes } from '../Storage/StorageTypes'
import { DeinitMode } from './DeinitMode'
import { DeinitSource } from './DeinitSource'
import { UserClientInterface } from '../User/UserClientInterface'
import { UserServiceInterface } from '../User/UserServiceInterface'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { HomeServerServiceInterface } from '../HomeServer/HomeServerServiceInterface'
import { User } from '@standardnotes/responses'
@@ -113,7 +113,7 @@ export interface ApplicationInterface {
get preferences(): PreferenceServiceInterface
get sessions(): SessionsClientInterface
get subscriptions(): SubscriptionManagerInterface
get user(): UserClientInterface
get user(): UserServiceInterface
get vaults(): VaultServiceInterface
get vaultLocks(): VaultLockServiceInterface
get vaultUsers(): VaultUserServiceInterface
@@ -1,3 +1,4 @@
import { GetKeyPairs } from './../Encryption/UseCase/GetKeyPairs'
import { GetVault } from './../Vault/UseCase/GetVault'
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
@@ -25,6 +26,7 @@ import {
KeySystemRootKeyContentSpecialized,
TrustedContactInterface,
} from '@standardnotes/models'
import { Result } from '@standardnotes/domain-core'
describe('AsymmetricMessageService', () => {
let sync: jest.Mocked<SyncServiceInterface>
@@ -47,6 +49,7 @@ describe('AsymmetricMessageService', () => {
const getOutboundMessagesUseCase = {} as jest.Mocked<GetOutboundMessages>
const getInboundMessagesUseCase = {} as jest.Mocked<GetInboundMessages>
const getUntrustedPayload = {} as jest.Mocked<GetUntrustedPayload>
const getKeyPairs = {} as jest.Mocked<GetKeyPairs>
sync = {} as jest.Mocked<SyncServiceInterface>
sync.sync = jest.fn()
@@ -61,6 +64,7 @@ describe('AsymmetricMessageService', () => {
encryption,
mutator,
sessions,
sync,
messageServer,
createOrEditContact,
findContact,
@@ -71,6 +75,7 @@ describe('AsymmetricMessageService', () => {
getOutboundMessagesUseCase,
getInboundMessagesUseCase,
getUntrustedPayload,
getKeyPairs,
eventBus,
)
})
@@ -115,6 +120,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 +183,7 @@ describe('AsymmetricMessageService', () => {
service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest
.fn()
.mockReturnValue(trustedPayloadMock)
.mockReturnValue(Result.ok(trustedPayloadMock))
const handleTrustedContactShareMessageMock = jest.fn()
service.handleTrustedContactShareMessage = handleTrustedContactShareMessageMock
@@ -171,7 +215,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 +244,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 +272,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 +302,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 +328,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 +357,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,8 @@
import { GetKeyPairs } from './../Encryption/UseCase/GetKeyPairs'
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 +22,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 +32,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,
@@ -50,6 +55,7 @@ export class AsymmetricMessageService
private _getOutboundMessagesUseCase: GetOutboundMessages,
private _getInboundMessagesUseCase: GetInboundMessages,
private _getUntrustedPayload: GetUntrustedPayload,
private _getKeyPairs: GetKeyPairs,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
@@ -73,30 +79,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 +146,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 +162,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,37 +197,47 @@ export class AsymmetricMessageService
await this.deleteMessageAfterProcessing(message)
}
getUntrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined {
getUntrustedMessagePayload(message: AsymmetricMessageServerHash): Result<AsymmetricMessagePayload> {
const keys = this._getKeyPairs.execute()
if (keys.isFailed()) {
return Result.fail(keys.getError())
}
const result = this._getUntrustedPayload.execute({
privateKey: this.encryption.getKeyPair().privateKey,
privateKey: keys.getValue().encryption.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 keys = this._getKeyPairs.execute()
if (keys.isFailed()) {
return Result.fail(keys.getError())
}
const result = this._getTrustedPayload.execute({
privateKey: this.encryption.getKeyPair().privateKey,
privateKey: keys.getValue().encryption.privateKey,
sender: contact.getValue(),
ownUserUuid: this.sessions.userUuid,
message,
})
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)
}
}
@@ -11,14 +11,14 @@ import {
import { ContentType } from '@standardnotes/domain-core'
import { GetVault } from '../../Vault/UseCase/GetVault'
import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface'
import { DecryptErroredPayloads } from '../../Encryption/UseCase/DecryptErroredPayloads'
export class HandleRootKeyChangedMessage {
constructor(
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private encryption: EncryptionProviderInterface,
private getVault: GetVault,
private _getVault: GetVault,
private _decryptErroredPayloads: DecryptErroredPayloads,
) {}
async execute(message: AsymmetricMessageSharedVaultRootKeyChanged): Promise<void> {
@@ -30,14 +30,16 @@ export class HandleRootKeyChangedMessage {
true,
)
const vault = this.getVault.execute<VaultListingInterface>({ keySystemIdentifier: rootKeyContent.systemIdentifier })
const vault = this._getVault.execute<VaultListingInterface>({
keySystemIdentifier: rootKeyContent.systemIdentifier,
})
if (!vault.isFailed()) {
await this.mutator.changeItem<VaultListingMutator>(vault.getValue(), (mutator) => {
mutator.rootKeyParams = rootKeyContent.keyParams
})
}
await this.encryption.decryptErroredPayloads()
await this._decryptErroredPayloads.execute()
void this.sync.sync({ sourceDescription: 'Not awaiting due to this event handler running from sync response' })
}
@@ -16,13 +16,13 @@ export class ProcessAcceptedVaultInvite {
constructor(
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private createOrEditContact: CreateOrEditContact,
private _createOrEditContact: CreateOrEditContact,
) {}
async execute(
message: AsymmetricMessageSharedVaultInvite,
sharedVaultUuid: string,
senderUuid: string,
ownerUuid: string,
): Promise<void> {
const { rootKey: rootKeyContent, trustedContacts, metadata } = message.data
@@ -34,7 +34,7 @@ export class ProcessAcceptedVaultInvite {
description: metadata.description,
sharing: {
sharedVaultUuid: sharedVaultUuid,
ownerUserUuid: senderUuid,
ownerUserUuid: ownerUuid,
},
}
@@ -47,7 +47,7 @@ export class ProcessAcceptedVaultInvite {
await this.mutator.createItem(ContentType.TYPES.VaultListing, FillItemContentSpecialized(content), true)
for (const contact of trustedContacts) {
await this.createOrEditContact.execute({
await this._createOrEditContact.execute({
name: contact.name,
contactUuid: contact.contactUuid,
publicKey: contact.publicKeySet.encryption,
@@ -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
@@ -31,6 +31,7 @@ export interface ComponentManagerInterface {
setPermissionDialogUIHandler(handler: (dialog: PermissionDialog) => void): void
findComponentWithPackageIdentifier(identifier: string): ComponentInterface | undefined
editorForNote(note: SNNote): UIFeature<EditorFeatureDescription | IframeComponentFeatureDescription>
getDefaultEditorIdentifier(currentTag?: SNTag): string
@@ -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'
@@ -13,7 +8,7 @@ import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { ContactServiceEvent, ContactServiceInterface } from '../Contacts/ContactServiceInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { UserClientInterface } from '../User/UserClientInterface'
import { UserServiceInterface } from '../User/UserServiceInterface'
import { CollaborationIDData, Version1CollaborationId } from './CollaborationID'
import { ValidateItemSigner } from './UseCase/ValidateItemSigner'
import { ItemSignatureValidationResult } from './UseCase/Types/ItemSignatureValidationResult'
@@ -25,16 +20,13 @@ 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,
private session: SessionsClientInterface,
private crypto: PureCryptoInterface,
private user: UserClientInterface,
private user: UserServiceInterface,
private selfContactManager: SelfContactManager,
private encryption: EncryptionProviderInterface,
private _deleteContact: DeleteContact,
@@ -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
}
}
@@ -18,7 +18,7 @@ export class DeleteContact implements UseCaseInterface<void> {
throw new Error('Cannot delete self')
}
const vaults = this.getOwnedVaults.execute({ userUuid: dto.ownUserUuid })
const vaults = this.getOwnedVaults.execute()
if (vaults.isFailed()) {
return Result.fail('Failed to get owned vaults')
}
@@ -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,
})
}
}
}
@@ -20,7 +20,6 @@ import {
KeySystemRootKeyParamsInterface,
PortablePublicKeySet,
} from '@standardnotes/models'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
export interface EncryptionProviderInterface {
initialize(): Promise<void>
@@ -72,7 +71,6 @@ export interface EncryptionProviderInterface {
}
>
decryptErroredPayloads(): Promise<void>
deleteWorkspaceSpecificKeyStateFromDevice(): Promise<void>
unwrapRootKey(wrappingKey: RootKeyInterface): Promise<void>
@@ -110,9 +108,6 @@ export interface EncryptionProviderInterface {
rootKeyToken: string,
): KeySystemItemsKeyInterface
getKeyPair(): PkcKeyPair
getSigningKeyPair(): PkcKeyPair
asymmetricSignatureVerifyDetached(
encryptedString: AsymmetricallyEncryptedString,
): AsymmetricSignatureVerificationDetachedResult
@@ -1,3 +1,4 @@
import { GetKeyPairs } from './UseCase/GetKeyPairs'
import { FindDefaultItemsKey } from './UseCase/ItemsKey/FindDefaultItemsKey'
import { InternalEventInterface } from './../Internal/InternalEventInterface'
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
@@ -43,7 +44,7 @@ import {
PortablePublicKeySet,
RootKeyParamsInterface,
} from '@standardnotes/models'
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import {
extendArray,
isNotUndefined,
@@ -73,7 +74,6 @@ import { DecryptedParameters } from '@standardnotes/encryption/src/Domain/Types/
import { RootKeyManager } from '../RootKeyManager/RootKeyManager'
import { RootKeyManagerEvent } from '../RootKeyManager/RootKeyManagerEvent'
import { CreateNewItemsKeyWithRollback } from './UseCase/ItemsKey/CreateNewItemsKeyWithRollback'
import { DecryptErroredTypeAPayloads } from './UseCase/TypeA/DecryptErroredPayloads'
import { CreateNewDefaultItemsKey } from './UseCase/ItemsKey/CreateNewDefaultItemsKey'
import { DecryptTypeAPayload } from './UseCase/TypeA/DecryptPayload'
import { DecryptTypeAPayloadWithKeyLookup } from './UseCase/TypeA/DecryptPayloadWithKeyLookup'
@@ -126,12 +126,12 @@ export class EncryptionService
private crypto: PureCryptoInterface,
private _createNewItemsKeyWithRollback: CreateNewItemsKeyWithRollback,
private _findDefaultItemsKey: FindDefaultItemsKey,
private _decryptErroredRootPayloads: DecryptErroredTypeAPayloads,
private _rootKeyEncryptPayloadWithKeyLookup: EncryptTypeAPayloadWithKeyLookup,
private _rootKeyEncryptPayload: EncryptTypeAPayload,
private _rootKeyDecryptPayload: DecryptTypeAPayload,
private _rootKeyDecryptPayloadWithKeyLookup: DecryptTypeAPayloadWithKeyLookup,
private _createDefaultItemsKey: CreateNewDefaultItemsKey,
private _getKeyPairs: GetKeyPairs,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -157,7 +157,6 @@ export class EncryptionService
;(this.crypto as unknown) = undefined
;(this._createNewItemsKeyWithRollback as unknown) = undefined
;(this._findDefaultItemsKey as unknown) = undefined
;(this._decryptErroredRootPayloads as unknown) = undefined
;(this._rootKeyEncryptPayloadWithKeyLookup as unknown) = undefined
;(this._rootKeyEncryptPayload as unknown) = undefined
;(this._rootKeyDecryptPayload as unknown) = undefined
@@ -167,28 +166,6 @@ export class EncryptionService
super.deinit()
}
/** @throws */
getKeyPair(): PkcKeyPair {
const rootKey = this.getRootKey()
if (!rootKey?.encryptionKeyPair) {
throw new Error('Account keypair not found')
}
return rootKey.encryptionKeyPair
}
/** @throws */
getSigningKeyPair(): PkcKeyPair {
const rootKey = this.getRootKey()
if (!rootKey?.signingKeyPair) {
throw new Error('Account keypair not found')
}
return rootKey.signingKeyPair
}
hasSigningKeyPair(): boolean {
return !!this.getRootKey()?.signingKeyPair
}
@@ -244,12 +221,6 @@ export class EncryptionService
return this._createNewItemsKeyWithRollback.execute()
}
public async decryptErroredPayloads(): Promise<void> {
await this._decryptErroredRootPayloads.execute()
await this.itemsEncryption.decryptErroredItemPayloads()
}
public itemsKeyForEncryptedPayload(
payload: EncryptedPayloadInterface,
): ItemsKeyInterface | KeySystemItemsKeyInterface | undefined {
@@ -279,7 +250,9 @@ export class EncryptionService
usesKeySystemRootKeyWithKeyLookup,
} = split
const signingKeyPair = this.hasSigningKeyPair() ? this.getSigningKeyPair() : undefined
const keys = this._getKeyPairs.execute()
const signingKeyPair = keys.isFailed() ? undefined : keys.getValue().signing
if (usesRootKey) {
const rootKeyEncrypted = await this._rootKeyEncryptPayload.executeMany(
@@ -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)
}
}
@@ -0,0 +1,18 @@
import { DecryptErroredTypeAPayloads } from './TypeA/DecryptErroredPayloads'
import { ItemsEncryptionService } from './../../ItemsEncryption/ItemsEncryption'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
export class DecryptErroredPayloads implements UseCaseInterface<void> {
constructor(
private itemsEncryption: ItemsEncryptionService,
private _decryptErroredRootPayloads: DecryptErroredTypeAPayloads,
) {}
async execute(): Promise<Result<void>> {
await this._decryptErroredRootPayloads.execute()
await this.itemsEncryption.decryptErroredItemPayloads()
return Result.ok()
}
}
@@ -0,0 +1,25 @@
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { RootKeyManager } from '../../RootKeyManager/RootKeyManager'
type UsecaseResult = {
encryption: PkcKeyPair
signing: PkcKeyPair
}
export class GetKeyPairs implements SyncUseCaseInterface<UsecaseResult> {
constructor(private rootKeyManager: RootKeyManager) {}
execute(): Result<UsecaseResult> {
const rootKey = this.rootKeyManager.getRootKey()
if (!rootKey?.encryptionKeyPair || !rootKey?.signingKeyPair) {
return Result.fail('Account keypair not found')
}
return Result.ok({
encryption: rootKey.encryptionKeyPair,
signing: rootKey.signingKeyPair,
})
}
}
@@ -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 : ''
}`,
-33
View File
@@ -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>
@@ -1,15 +1,11 @@
import { DiscardItemsLocally } from './../UseCase/DiscardItemsLocally'
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults'
import { IsVaultOwner } from './../VaultUser/UseCase/IsVaultOwner'
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault'
import { ShareContactWithVault } from './UseCase/ShareContactWithVault'
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault'
import { FindContact } from './../Contacts/UseCase/FindContact'
import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage'
import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation'
import { HandleKeyPairChange } from './../Contacts/UseCase/HandleKeyPairChange'
import { CreateSharedVault } from './UseCase/CreateSharedVault'
import { GetVault } from './../Vault/UseCase/GetVault'
@@ -29,20 +25,16 @@ describe('SharedVaultService', () => {
const items = {} as jest.Mocked<ItemManagerInterface>
items.addObserver = jest.fn()
const encryption = {} as jest.Mocked<EncryptionProviderInterface>
const session = {} as jest.Mocked<SessionsClientInterface>
const getVault = {} as jest.Mocked<GetVault>
const getOwnedVaults = {} as jest.Mocked<GetOwnedSharedVaults>
const createSharedVaultUseCase = {} as jest.Mocked<CreateSharedVault>
const handleKeyPairChange = {} as jest.Mocked<HandleKeyPairChange>
const notifyVaultUsersOfKeyRotation = {} as jest.Mocked<NotifyVaultUsersOfKeyRotation>
const sendVaultDataChangeMessage = {} as jest.Mocked<SendVaultDataChangedMessage>
const findContact = {} as jest.Mocked<FindContact>
const deleteThirdPartyVault = {} as jest.Mocked<DeleteThirdPartyVault>
const shareContactWithVault = {} as jest.Mocked<ShareContactWithVault>
const convertToSharedVault = {} as jest.Mocked<ConvertToSharedVault>
const deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
const isVaultAdmin = {} as jest.Mocked<IsVaultOwner>
const discardItemsLocally = {} as jest.Mocked<DiscardItemsLocally>
const eventBus = {} as jest.Mocked<InternalEventBusInterface>
@@ -50,20 +42,16 @@ describe('SharedVaultService', () => {
service = new SharedVaultService(
items,
encryption,
session,
getVault,
getOwnedVaults,
createSharedVaultUseCase,
handleKeyPairChange,
notifyVaultUsersOfKeyRotation,
sendVaultDataChangeMessage,
findContact,
deleteThirdPartyVault,
shareContactWithVault,
convertToSharedVault,
deleteSharedVaultUseCase,
isVaultAdmin,
discardItemsLocally,
eventBus,
)
@@ -19,21 +19,16 @@ 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'
import { ShareContactWithVault } from './UseCase/ShareContactWithVault'
import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation'
import { CreateSharedVault } from './UseCase/CreateSharedVault'
import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage'
import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault'
import { GetVault } from '../Vault/UseCase/GetVault'
import { ContentType, NotificationType, Uuid } from '@standardnotes/domain-core'
import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange'
import { FindContact } from '../Contacts/UseCase/FindContact'
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
import { IsVaultOwner } from '../VaultUser/UseCase/IsVaultOwner'
import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults'
export class SharedVaultService
@@ -42,20 +37,16 @@ export class SharedVaultService
{
constructor(
private items: ItemManagerInterface,
private encryption: EncryptionProviderInterface,
private session: SessionsClientInterface,
private _getVault: GetVault,
private _getOwnedSharedVaults: GetOwnedSharedVaults,
private _createSharedVault: CreateSharedVault,
private _handleKeyPairChange: HandleKeyPairChange,
private _notifyVaultUsersOfKeyRotation: NotifyVaultUsersOfKeyRotation,
private _sendVaultDataChangeMessage: SendVaultDataChangedMessage,
private _findContact: FindContact,
private _deleteThirdPartyVault: DeleteThirdPartyVault,
private _shareContactWithVault: ShareContactWithVault,
private _convertToSharedVault: ConvertToSharedVault,
private _deleteSharedVault: DeleteSharedVault,
private _isVaultAdmin: IsVaultOwner,
private _discardItemsLocally: DiscardItemsLocally,
eventBus: InternalEventBusInterface,
) {
@@ -68,32 +59,20 @@ export class SharedVaultService
}
}),
)
this.eventDisposers.push(
items.addObserver<VaultListingInterface>(ContentType.TYPES.VaultListing, ({ changed, source }) => {
if (source === PayloadEmitSource.LocalChanged && changed.length > 0) {
void this.handleVaultListingsChange(changed)
}
}),
)
}
override deinit(): void {
super.deinit()
;(this.items as unknown) = undefined
;(this.encryption as unknown) = undefined
;(this.session as unknown) = undefined
;(this._getVault as unknown) = undefined
;(this._createSharedVault as unknown) = undefined
;(this._handleKeyPairChange as unknown) = undefined
;(this._notifyVaultUsersOfKeyRotation as unknown) = undefined
;(this._sendVaultDataChangeMessage as unknown) = undefined
;(this._findContact as unknown) = undefined
;(this._deleteThirdPartyVault as unknown) = undefined
;(this._shareContactWithVault as unknown) = undefined
;(this._convertToSharedVault as unknown) = undefined
;(this._deleteSharedVault as unknown) = undefined
;(this._isVaultAdmin as unknown) = undefined
}
async handleEvent(event: InternalEventInterface): Promise<void> {
@@ -106,21 +85,16 @@ 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]
await this.handleVaultRootKeyRotatedEvent(payload.vault)
break
}
case SyncEvent.ReceivedRemoteSharedVaults:
void this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
break
}
}
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>({
@@ -141,33 +115,6 @@ export class SharedVaultService
}
}
private isCurrentUserVaultOwner(sharedVault: SharedVaultListingInterface): boolean {
if (!sharedVault.sharing.ownerUserUuid) {
throw new Error(`Shared vault ${sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
}
return sharedVault.sharing.ownerUserUuid === this.session.userUuid
}
private async handleVaultRootKeyRotatedEvent(vault: VaultListingInterface): Promise<void> {
if (!vault.isSharedVaultListing()) {
return
}
if (!this.isCurrentUserVaultOwner(vault)) {
return
}
await this._notifyVaultUsersOfKeyRotation.execute({
sharedVault: vault,
senderUuid: this.session.getSureUser().uuid,
keys: {
encryption: this.encryption.getKeyPair(),
signing: this.encryption.getSigningKeyPair(),
},
})
}
async createSharedVault(dto: {
name: string
description?: string
@@ -198,23 +145,6 @@ export class SharedVaultService
}
}
private async handleVaultListingsChange(vaults: VaultListingInterface[]): Promise<void> {
for (const vault of vaults) {
if (!vault.isSharedVaultListing()) {
continue
}
await this._sendVaultDataChangeMessage.execute({
vault,
senderUuid: this.session.getSureUser().uuid,
keys: {
encryption: this.encryption.getKeyPair(),
signing: this.encryption.getSigningKeyPair(),
},
})
}
}
public async deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
return this._deleteSharedVault.execute({ sharedVault })
}
@@ -224,17 +154,12 @@ export class SharedVaultService
throw new Error('Cannot share self contact')
}
const ownedVaults = this._getOwnedSharedVaults.execute({ userUuid: this.session.userUuid }).getValue()
const ownedVaults = this._getOwnedSharedVaults.execute().getValue()
for (const vault of ownedVaults) {
await this._shareContactWithVault.execute({
keys: {
encryption: this.encryption.getKeyPair(),
signing: this.encryption.getSigningKeyPair(),
},
sharedVault: vault,
contactToShare: contact,
senderUserUuid: this.session.getSureUser().uuid,
})
}
}
@@ -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
@@ -5,18 +5,17 @@ import { GetSharedVaults } from './GetSharedVaults'
export class GetOwnedSharedVaults implements SyncUseCaseInterface<SharedVaultListingInterface[]> {
constructor(
private getSharedVaults: GetSharedVaults,
private isVaultOwnwer: IsVaultOwner,
private _getSharedVaults: GetSharedVaults,
private _isVaultOwnwer: IsVaultOwner,
) {}
execute(dto: { userUuid: string }): Result<SharedVaultListingInterface[]> {
const sharedVaults = this.getSharedVaults.execute().getValue()
execute(): Result<SharedVaultListingInterface[]> {
const sharedVaults = this._getSharedVaults.execute().getValue()
const ownedVaults = sharedVaults.filter((vault) => {
return this.isVaultOwnwer
return this._isVaultOwnwer
.execute({
sharedVault: vault,
userUuid: dto.userUuid,
})
.getValue()
})
@@ -2,30 +2,26 @@ import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
import { AsymmetricMessageSharedVaultInvite, SharedVaultListingInterface } from '@standardnotes/models'
import { SharedVaultInviteServerHash, isErrorResponse } from '@standardnotes/responses'
import { SendVaultKeyChangedMessage } from './SendVaultKeyChangedMessage'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { InviteToVault } from '../../VaultInvite/UseCase/InviteToVault'
import { GetVaultContacts } from '../../VaultUser/UseCase/GetVaultContacts'
import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
import { FindContact } from '../../Contacts/UseCase/FindContact'
import { GetKeyPairs } from '../../Encryption/UseCase/GetKeyPairs'
type Params = {
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
sharedVault: SharedVaultListingInterface
senderUuid: string
}
export class NotifyVaultUsersOfKeyRotation implements UseCaseInterface<void> {
constructor(
private findContact: FindContact,
private sendKeyChangedMessage: SendVaultKeyChangedMessage,
private inviteToVault: InviteToVault,
private inviteServer: SharedVaultInvitesServerInterface,
private getVaultContacts: GetVaultContacts,
private decryptOwnMessage: DecryptOwnMessage<AsymmetricMessageSharedVaultInvite>,
private _findContact: FindContact,
private _sendKeyChangedMessage: SendVaultKeyChangedMessage,
private _inviteToVault: InviteToVault,
private _inviteServer: SharedVaultInvitesServerInterface,
private _getVaultContacts: GetVaultContacts,
private _decryptOwnMessage: DecryptOwnMessage<AsymmetricMessageSharedVaultInvite>,
private _getKeyPairs: GetKeyPairs,
) {}
async execute(params: Params): Promise<Result<void>> {
@@ -44,20 +40,25 @@ export class NotifyVaultUsersOfKeyRotation implements UseCaseInterface<void> {
await this.deleteAllInvites(params.sharedVault.sharing.sharedVaultUuid)
const contacts = await this.getVaultContacts.execute({
const contacts = await this._getVaultContacts.execute({
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
readFromCache: false,
})
const keys = this._getKeyPairs.execute()
if (keys.isFailed()) {
return Result.fail('Cannot send metadata changed message; keys not found')
}
for (const invite of existingInvites.getValue()) {
const recipient = this.findContact.execute({ userUuid: invite.user_uuid })
const recipient = this._findContact.execute({ userUuid: invite.user_uuid })
if (recipient.isFailed()) {
continue
}
const decryptedPreviousInvite = this.decryptOwnMessage.execute({
const decryptedPreviousInvite = this._decryptOwnMessage.execute({
message: invite.encrypted_message,
privateKey: params.keys.encryption.privateKey,
privateKey: keys.getValue().encryption.privateKey,
recipientPublicKey: recipient.getValue().publicKeySet.encryption,
})
@@ -65,13 +66,11 @@ export class NotifyVaultUsersOfKeyRotation implements UseCaseInterface<void> {
return Result.fail(decryptedPreviousInvite.getError())
}
await this.inviteToVault.execute({
keys: params.keys,
await this._inviteToVault.execute({
sharedVault: params.sharedVault,
sharedVaultContacts: !contacts.isFailed() ? contacts.getValue() : [],
recipient: recipient.getValue(),
permission: invite.permission,
senderUuid: params.senderUuid,
})
}
@@ -79,11 +78,9 @@ export class NotifyVaultUsersOfKeyRotation implements UseCaseInterface<void> {
}
private async performSendKeyChangeMessage(params: Params): Promise<Result<void>> {
const result = await this.sendKeyChangedMessage.execute({
const result = await this._sendKeyChangedMessage.execute({
keySystemIdentifier: params.sharedVault.systemIdentifier,
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
senderUuid: params.senderUuid,
keys: params.keys,
})
if (result.isFailed()) {
@@ -94,7 +91,7 @@ export class NotifyVaultUsersOfKeyRotation implements UseCaseInterface<void> {
}
private async deleteAllInvites(sharedVaultUuid: string): Promise<Result<void>> {
const response = await this.inviteServer.deleteAllSharedVaultInvites({
const response = await this._inviteServer.deleteAllSharedVaultInvites({
sharedVaultUuid: sharedVaultUuid,
})
@@ -106,7 +103,7 @@ export class NotifyVaultUsersOfKeyRotation implements UseCaseInterface<void> {
}
private async getExistingInvites(sharedVaultUuid: string): Promise<Result<SharedVaultInviteServerHash[]>> {
const response = await this.inviteServer.getOutboundUserInvites()
const response = await this._inviteServer.getOutboundUserInvites()
if (isErrorResponse(response)) {
return Result.fail(`Failed to get outbound user invites ${JSON.stringify(response)}`)
@@ -1,3 +1,5 @@
import { UserServiceInterface } from './../../User/UserServiceInterface'
import { IsVaultOwner } from './../../VaultUser/UseCase/IsVaultOwner'
import {
AsymmetricMessagePayloadType,
AsymmetricMessageSharedVaultMetadataChanged,
@@ -12,24 +14,26 @@ import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessa
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { GetReplaceabilityIdentifier } from '../../AsymmetricMessage/UseCase/GetReplaceabilityIdentifier'
import { FindContact } from '../../Contacts/UseCase/FindContact'
import { GetKeyPairs } from '../../Encryption/UseCase/GetKeyPairs'
export class SendVaultDataChangedMessage implements UseCaseInterface<void> {
constructor(
private encryptMessage: EncryptMessage,
private findContact: FindContact,
private getVaultUsers: GetVaultUsers,
private sendMessage: SendMessage,
private users: UserServiceInterface,
private _encryptMessage: EncryptMessage,
private _findContact: FindContact,
private _getVaultUsers: GetVaultUsers,
private _sendMessage: SendMessage,
private _isVaultOwner: IsVaultOwner,
private _getKeyPairs: GetKeyPairs,
) {}
async execute(params: {
vault: SharedVaultListingInterface
senderUuid: string
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
async execute(params: { vault: SharedVaultListingInterface }): Promise<Result<void>> {
const isOwner = this._isVaultOwner.execute({ sharedVault: params.vault }).getValue()
if (!isOwner) {
return Result.ok()
}
}): Promise<Result<void>> {
const users = await this.getVaultUsers.execute({
const users = await this._getVaultUsers.execute({
sharedVaultUuid: params.vault.sharing.sharedVaultUuid,
readFromCache: false,
})
@@ -37,20 +41,25 @@ export class SendVaultDataChangedMessage implements UseCaseInterface<void> {
return Result.fail('Cannot send metadata changed message; users not found')
}
const keys = this._getKeyPairs.execute()
if (keys.isFailed()) {
return Result.fail('Cannot send metadata changed message; keys not found')
}
const errors: string[] = []
for (const user of users.getValue()) {
if (user.user_uuid === params.senderUuid) {
if (user.user_uuid === this.users.sureUser.uuid) {
continue
}
const trustedContact = this.findContact.execute({ userUuid: user.user_uuid })
const trustedContact = this._findContact.execute({ userUuid: user.user_uuid })
if (trustedContact.isFailed()) {
continue
}
const sendMessageResult = await this.sendToContact({
vault: params.vault,
keys: params.keys,
keys: keys.getValue(),
contact: trustedContact.getValue(),
})
@@ -84,7 +93,7 @@ export class SendVaultDataChangedMessage implements UseCaseInterface<void> {
},
}
const encryptedMessage = this.encryptMessage.execute({
const encryptedMessage = this._encryptMessage.execute({
message: message,
keys: params.keys,
recipientPublicKey: params.contact.publicKeySet.encryption,
@@ -100,7 +109,7 @@ export class SendVaultDataChangedMessage implements UseCaseInterface<void> {
params.vault.systemIdentifier,
)
const sendMessageResult = await this.sendMessage.execute({
const sendMessageResult = await this._sendMessage.execute({
recipientUuid: params.contact.contactUuid,
encryptedMessage: encryptedMessage.getValue(),
replaceabilityIdentifier,
@@ -1,3 +1,4 @@
import { UserServiceInterface } from './../../User/UserServiceInterface'
import {
AsymmetricMessagePayloadType,
AsymmetricMessageSharedVaultRootKeyChanged,
@@ -13,38 +14,38 @@ import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { GetReplaceabilityIdentifier } from '../../AsymmetricMessage/UseCase/GetReplaceabilityIdentifier'
import { FindContact } from '../../Contacts/UseCase/FindContact'
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
import { GetKeyPairs } from '../../Encryption/UseCase/GetKeyPairs'
export class SendVaultKeyChangedMessage implements UseCaseInterface<void> {
constructor(
private encryptMessage: EncryptMessage,
private users: UserServiceInterface,
private keyManager: KeySystemKeyManagerInterface,
private findContact: FindContact,
private sendMessage: SendMessage,
private getVaultUsers: GetVaultUsers,
private _encryptMessage: EncryptMessage,
private _findContact: FindContact,
private _sendMessage: SendMessage,
private _getVaultUsers: GetVaultUsers,
private _getKeyPairs: GetKeyPairs,
) {}
async execute(params: {
keySystemIdentifier: KeySystemIdentifier
sharedVaultUuid: string
senderUuid: string
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
}): Promise<Result<void>> {
const users = await this.getVaultUsers.execute({ sharedVaultUuid: params.sharedVaultUuid, readFromCache: false })
async execute(params: { keySystemIdentifier: KeySystemIdentifier; sharedVaultUuid: string }): Promise<Result<void>> {
const users = await this._getVaultUsers.execute({ sharedVaultUuid: params.sharedVaultUuid, readFromCache: false })
if (users.isFailed()) {
return Result.fail('Cannot send root key changed message; users not found')
}
const keys = this._getKeyPairs.execute()
if (keys.isFailed()) {
return Result.fail('Cannot send root key changed message; keys not found')
}
const errors: string[] = []
for (const user of users.getValue()) {
if (user.user_uuid === params.senderUuid) {
if (user.user_uuid === this.users.sureUser.uuid) {
continue
}
const trustedContact = this.findContact.execute({ userUuid: user.user_uuid })
const trustedContact = this._findContact.execute({ userUuid: user.user_uuid })
if (trustedContact.isFailed()) {
continue
}
@@ -52,7 +53,7 @@ export class SendVaultKeyChangedMessage implements UseCaseInterface<void> {
const result = await this.sendToContact({
keySystemIdentifier: params.keySystemIdentifier,
sharedVaultUuid: params.sharedVaultUuid,
keys: params.keys,
keys: keys.getValue(),
contact: trustedContact.getValue(),
})
@@ -87,7 +88,7 @@ export class SendVaultKeyChangedMessage implements UseCaseInterface<void> {
data: { recipientUuid: params.contact.contactUuid, rootKey: keySystemRootKey.content },
}
const encryptedMessage = this.encryptMessage.execute({
const encryptedMessage = this._encryptMessage.execute({
message: message,
keys: params.keys,
recipientPublicKey: params.contact.publicKeySet.encryption,
@@ -103,7 +104,7 @@ export class SendVaultKeyChangedMessage implements UseCaseInterface<void> {
params.keySystemIdentifier,
)
const sendMessageResult = await this.sendMessage.execute({
const sendMessageResult = await this._sendMessage.execute({
recipientUuid: params.contact.contactUuid,
encryptedMessage: encryptedMessage.getValue(),
replaceabilityIdentifier,
@@ -1,37 +1,35 @@
import { UserServiceInterface } from './../../User/UserServiceInterface'
import {
TrustedContactInterface,
SharedVaultListingInterface,
AsymmetricMessagePayloadType,
} from '@standardnotes/models'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { FindContact } from '../../Contacts/UseCase/FindContact'
import { GetVaultUsers } from '../../VaultUser/UseCase/GetVaultUsers'
import { GetKeyPairs } from '../../Encryption/UseCase/GetKeyPairs'
export class ShareContactWithVault implements UseCaseInterface<void> {
constructor(
private findContact: FindContact,
private encryptMessage: EncryptMessage,
private sendMessage: SendMessage,
private getVaultUsers: GetVaultUsers,
private users: UserServiceInterface,
private _findContact: FindContact,
private _encryptMessage: EncryptMessage,
private _sendMessage: SendMessage,
private _getVaultUsers: GetVaultUsers,
private _getKeyPairs: GetKeyPairs,
) {}
async execute(params: {
keys: {
encryption: PkcKeyPair
signing: PkcKeyPair
}
senderUserUuid: string
sharedVault: SharedVaultListingInterface
contactToShare: TrustedContactInterface
}): Promise<Result<void>> {
if (params.sharedVault.sharing.ownerUserUuid !== params.senderUserUuid) {
if (params.sharedVault.sharing.ownerUserUuid !== this.users.sureUser.uuid) {
return Result.fail('Cannot share contact; user is not the owner of the shared vault')
}
const users = await this.getVaultUsers.execute({
const users = await this._getVaultUsers.execute({
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
readFromCache: false,
})
@@ -44,8 +42,13 @@ export class ShareContactWithVault implements UseCaseInterface<void> {
return Result.ok()
}
const keys = this._getKeyPairs.execute()
if (keys.isFailed()) {
return Result.fail('Cannot share contact; keys not found')
}
for (const vaultUser of users.getValue()) {
if (vaultUser.user_uuid === params.senderUserUuid) {
if (vaultUser.user_uuid === this.users.sureUser.uuid) {
continue
}
@@ -53,12 +56,12 @@ export class ShareContactWithVault implements UseCaseInterface<void> {
continue
}
const vaultUserAsContact = this.findContact.execute({ userUuid: vaultUser.user_uuid })
const vaultUserAsContact = this._findContact.execute({ userUuid: vaultUser.user_uuid })
if (vaultUserAsContact.isFailed()) {
continue
}
const encryptedMessage = this.encryptMessage.execute({
const encryptedMessage = this._encryptMessage.execute({
message: {
type: AsymmetricMessagePayloadType.ContactShare,
data: {
@@ -66,7 +69,7 @@ export class ShareContactWithVault implements UseCaseInterface<void> {
trustedContact: params.contactToShare.content,
},
},
keys: params.keys,
keys: keys.getValue(),
recipientPublicKey: vaultUserAsContact.getValue().publicKeySet.encryption,
})
@@ -74,7 +77,7 @@ export class ShareContactWithVault implements UseCaseInterface<void> {
continue
}
await this.sendMessage.execute({
await this._sendMessage.execute({
recipientUuid: vaultUserAsContact.getValue().contactUuid,
encryptedMessage: encryptedMessage.getValue(),
replaceabilityIdentifier: undefined,
@@ -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>
@@ -1,3 +1,4 @@
import { DecryptErroredPayloads } from './../Encryption/UseCase/DecryptErroredPayloads'
import { ReencryptTypeAItems } from './../Encryption/UseCase/TypeA/ReencryptTypeAItems'
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
import { UserApiServiceInterface } from '@standardnotes/api'
@@ -27,6 +28,7 @@ describe('UserService', () => {
let protectionService: ProtectionsClientInterface
let userApiService: UserApiServiceInterface
let reencryptTypeAItems: ReencryptTypeAItems
let decryptErroredPayloads: DecryptErroredPayloads
let internalEventBus: InternalEventBusInterface
const createService = () =>
@@ -41,6 +43,7 @@ describe('UserService', () => {
protectionService,
userApiService,
reencryptTypeAItems,
decryptErroredPayloads,
internalEventBus,
)
@@ -25,7 +25,7 @@ import {
} from '../Challenge'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { AbstractService } from '../Service/AbstractService'
import { UserClientInterface } from './UserClientInterface'
import { UserServiceInterface } from './UserServiceInterface'
import { DeinitSource } from '../Application/DeinitSource'
import { StoragePersistencePolicies } from '../Storage/StorageTypes'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
@@ -38,10 +38,11 @@ import { SignedInOrRegisteredEventPayload } from './SignedInOrRegisteredEventPay
import { CredentialsChangeFunctionResponse } from './CredentialsChangeFunctionResponse'
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
import { ReencryptTypeAItems } from '../Encryption/UseCase/TypeA/ReencryptTypeAItems'
import { DecryptErroredPayloads } from '../Encryption/UseCase/DecryptErroredPayloads'
export class UserService
extends AbstractService<AccountEvent, AccountEventData>
implements UserClientInterface, InternalEventHandlerInterface
implements UserServiceInterface, InternalEventHandlerInterface
{
private signingIn = false
private registering = false
@@ -60,6 +61,7 @@ export class UserService
private protections: ProtectionsClientInterface,
private userApi: UserApiServiceInterface,
private _reencryptTypeAItems: ReencryptTypeAItems,
private _decryptErroredPayloads: DecryptErroredPayloads,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -77,6 +79,7 @@ export class UserService
;(this.protections as unknown) = undefined
;(this.userApi as unknown) = undefined
;(this._reencryptTypeAItems as unknown) = undefined
;(this._decryptErroredPayloads as unknown) = undefined
}
async handleEvent(event: InternalEventInterface): Promise<void> {
@@ -104,18 +107,26 @@ export class UserService
})
.then(() => {
if (!payload.awaitSync) {
void this.encryption.decryptErroredPayloads()
void this._decryptErroredPayloads.execute()
}
})
if (payload.awaitSync) {
await syncPromise
await this.encryption.decryptErroredPayloads()
await this._decryptErroredPayloads.execute()
}
}
}
get user(): User | undefined {
return this.sessions.getUser()
}
get sureUser(): User {
return this.sessions.getSureUser()
}
getUserUuid(): string {
return this.sessions.userUuid
}
@@ -2,13 +2,15 @@ import { Base64String } from '@standardnotes/sncrypto-common'
import { KeyParamsOrigination, UserRequestType } from '@standardnotes/common'
import { DeinitSource } from '../Application/DeinitSource'
import { UserRegistrationResponseBody } from '@standardnotes/api'
import { HttpResponse, SignInResponse } from '@standardnotes/responses'
import { HttpResponse, SignInResponse, User } from '@standardnotes/responses'
import { AbstractService } from '../Service/AbstractService'
import { AccountEventData } from './AccountEventData'
import { AccountEvent } from './AccountEvent'
import { CredentialsChangeFunctionResponse } from './CredentialsChangeFunctionResponse'
export interface UserClientInterface extends AbstractService<AccountEvent, AccountEventData> {
export interface UserServiceInterface extends AbstractService<AccountEvent, AccountEventData> {
get user(): User | undefined
get sureUser(): User
getUserUuid(): string
isSignedIn(): boolean
@@ -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
}

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