Compare commits

..
Author SHA1 Message Date
StandardNotes CI e4b858f2e8 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].2
2023-07-12 12:22:07 +00:00
Karol Sójko 325737bfbd chore: fix ContentType usage (#2353)
* chore: fix ContentType usage

* chore: fix specs
2023-07-12 13:53:29 +02:00
StandardNotes CI d057cdff84 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2023-07-12 10:06:57 +00:00
Aman Harwara 0528c06ce8 chore: release 2023-07-12 15:05:45 +05:30
StandardNotes CI bd906f2a12 chore(release): publish
- @standardnotes/[email protected].1
 - @standardnotes/[email protected]
2023-07-12 09:06:16 +00:00
Aman Harwara 669b395709 chore: fix crash on ios 2023-07-12 13:08:08 +05:30
StandardNotes CI da4967434a chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2023-07-11 19:40:10 +00:00
Aman Harwara 96897163fb chore: update lexical 2023-07-12 00:42:26 +05:30
Aman Harwara 3dc179c7b0 feat: On Android, you can now share text & files from other apps directly into Standard Notes (#2352) 2023-07-12 00:16:32 +05:30
StandardNotes CI 1f5354c816 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected]
2023-07-11 15:46:44 +00:00
Karol Sójko f097f6e354 chore(api): fix request params and make api package publishable (#2351)
* chore(api): fix request params and make api package publishable

* chore(api): fix deps

* chore(encryption): fix privacy
2023-07-11 17:19:37 +02:00
StandardNotes CI 97456f7c0b chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected]
2023-07-11 09:02:46 +00:00
Karol Sójko 937f9d98d7 chore(api): fix missing exports 2023-07-11 10:34:35 +02:00
StandardNotes CI 07410cefb7 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-07-10 15:32:29 +00:00
Aman Harwara de6994f743 fix: Fixed issue where files would not download correctly on mobile if they had special characters in the name 2023-07-10 20:27:52 +05:30
StandardNotes CI 5c87f3a3b4 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].9
2023-07-10 13:32:35 +00:00
Mo b257af17aa internal: temporarily disable home server imports 2023-07-10 08:00:04 -05:00
StandardNotes CI 3a75afc817 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].8
2023-07-10 09:29:39 +00:00
Aman Harwara e4488dcb8c chore: take popover offset into account when fitting inside rect 2023-07-10 14:29:49 +05:30
StandardNotes CI bb997312e8 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].7
2023-07-07 15:19:11 +00:00
Aman Harwara b229a96e58 chore: bump node version 2023-07-07 20:16:21 +05:30
Aman Harwara 7f0bb4e127 refactor: use Fetch API for HttpService (#2349) 2023-07-07 19:26:31 +05:30
StandardNotes CI bf9449b67d chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].6
2023-07-07 06:37:18 +00:00
Aman Harwara 67359801bb chore: fix PageUp and PageDown not scrolling in preferences view 2023-07-06 22:13:34 +05:30
StandardNotes CI 53e978c30e chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].9
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
2023-07-06 15:33:57 +00:00
Aman Harwara ebc9fcb995 chore: fix blank screen issue when moving iOS floating keyboard 2023-07-06 20:30:49 +05:30
StandardNotes CI 7923e9f198 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].8
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected].4
2023-07-06 14:32:38 +00:00
Aman Harwara 9081607dc7 chore: fix challenge modal automatically closing 2023-07-06 19:27:14 +05:30
Mo c8e52b667c internal: change password preprocessing step (#2347) 2023-07-06 08:51:06 -05:00
StandardNotes CI 5c6ccaf4e1 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].7
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected].3
2023-07-05 16:57:29 +00:00
Mo 0ab6b5c6fe chore(fix): fixes sync response not correctly identifying all errors (#2346) 2023-07-05 11:28:45 -05:00
StandardNotes CI 2a050fd966 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].6
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected].2
2023-07-05 16:26:01 +00:00
Mo 40f0d3d5bf chore: merge 2023-07-05 10:54:16 -05:00
Mo c9c93d143a internal: move home server into internal flag 2023-07-05 10:53:10 -05:00
StandardNotes CI a52828cd45 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].5
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2023-07-05 15:50:04 +00:00
Aman Harwara 2611c88cee chore: fix modal positioning when using themed desktop titlebar 2023-07-05 20:43:55 +05:30
StandardNotes CI d4db3e055c chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected].0
2023-07-04 15:29:47 +00:00
Karol Sójko 05528782a4 feat: New one-click Home Server, now in Labs. Launch your own self-hosted server instance with just 1 click from the Preferences window. (#2345)
* fix(desktop): add home server coming-soon message on windows platform

* fix(desktop): typos and remove feature block

* fix(desktop): instantiate home server only upon starting
2023-07-04 16:55:37 +02:00
StandardNotes CI 9664d31c4e chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].3
2023-07-04 13:02:42 +00:00
Mo b06999d25b refactor: root key manager (#2344) 2023-07-04 07:31:50 -05:00
StandardNotes CI b4a90025c4 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2023-07-04 09:33:32 +00:00
Aman Harwara 31b42553be chore: improve super formatting toolbar positioning 2023-07-04 14:33:24 +05:30
StandardNotes CI 41311801c0 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected].1
2023-07-03 19:20:31 +00:00
Mo ccc9d73b3b chore: make home server internal feature 2023-07-03 13:53:03 -05:00
StandardNotes CI 1f144e062e chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
2023-07-03 14:58:46 +00:00
Mo 40f8985ede chore: implement removeApplication 2023-07-03 09:21:09 -05:00
Mo 27fc4be886 chore: fix mobile device interface 2023-07-03 09:08:27 -05:00
Mo 96f42643a9 feat: New one-click Home Server, now in Labs. Launch your own self-hosted server instance with just 1 click from the Preferences window. (#2341) 2023-07-03 08:30:48 -05:00
Mo d79e7b14b1 chore: legacy fixes (#2343) 2023-07-03 08:03:25 -05:00
Mo 6d5cbcb396 chore: ci 2023-07-03 07:03:06 -05:00
StandardNotes CI 20bbd70595 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2023-07-02 16:14:29 +00:00
Aman Harwara 5839f7d075 chore: fix empty super note error on exporting to markdown 2023-07-02 21:17:13 +05:30
StandardNotes CI 6a6cbe57a1 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected].2
2023-07-02 15:46:14 +00:00
Aman Harwara a8bc20e99e fix: Fixed issue where subfolders would show one extra item count 2023-07-02 20:46:33 +05:30
StandardNotes CI d42acab032 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2023-07-02 10:59:59 +00:00
Aman Harwara efa0669dbc chore: show non-editable link bar to allow opening auto-links 2023-07-02 15:29:02 +05:30
StandardNotes CI a6d7844ad4 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2023-07-02 09:43:13 +00:00
Aman Harwara 929642195d feat: Link and formatting toolbars for super are now unified on web/desktop to allow formatting existing links and updated link editor on mobile (#2342) 2023-07-02 14:45:49 +05:30
StandardNotes CI f354a50c26 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-06-30 18:25:33 +00:00
Aman Harwara ab1937226f chore: make "potentially out of sync" warning more visible on mobile 2023-06-30 23:22:15 +05:30
StandardNotes CI 64729a816c chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-06-30 16:30:45 +00:00
Aman Harwara c9a95c818b chore: fix session modal error after revoking session 2023-06-30 20:57:57 +05:30
505 changed files with 8444 additions and 4073 deletions
+1
View File
@@ -17,6 +17,7 @@ on:
- 'packages/sncrypto-web/**'
- 'packages/snjs/**'
- 'packages/utils/**'
workflow_dispatch:
jobs:
test:
+1 -1
View File
@@ -1 +1 @@
16.15.1
18.6.0
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+32
View File
@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-12)
**Note:** Version bump only for package @standardnotes/api
## [1.26.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-11)
**Note:** Version bump only for package @standardnotes/api
## [1.26.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-11)
**Note:** Version bump only for package @standardnotes/api
## [1.26.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/api
## [1.26.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/api
## [1.26.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/api
## [1.26.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/api
## [1.26.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/api
## [1.26.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-06-30)
**Note:** Version bump only for package @standardnotes/api
+2 -1
View File
@@ -6,4 +6,5 @@ module.exports = {
transform: {
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
},
};
coverageThreshold: {},
}
+3 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.26.18",
"version": "1.26.26",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -36,9 +36,8 @@
"typescript": "*"
},
"dependencies": {
"@standardnotes/common": "^1.48.3",
"@standardnotes/domain-core": "^1.12.0",
"@standardnotes/encryption": "workspace:*",
"@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.22.0",
"@standardnotes/models": "workspace:*",
"@standardnotes/responses": "workspace:*",
"@standardnotes/security": "^1.7.6",
@@ -0,0 +1,144 @@
import { Environment } from '@standardnotes/models'
import { HttpVerb } from '@standardnotes/responses'
import { FetchRequestHandler } from './FetchRequestHandler'
import { HttpErrorResponseBody, HttpRequest } from '@standardnotes/responses'
import { ErrorMessage } from '../Error'
describe('FetchRequestHandler', () => {
const snjsVersion = 'snjsVersion'
const appVersion = 'appVersion'
const environment = Environment.Web
const requestHandler = new FetchRequestHandler(snjsVersion, appVersion, environment)
it('should create a request', () => {
const httpRequest: HttpRequest = {
url: 'http://localhost:3000/test',
verb: HttpVerb.Get,
external: false,
authentication: 'authentication',
customHeaders: [],
params: {
key: 'value',
},
}
const request = requestHandler['createRequest'](httpRequest)
expect(request).toBeInstanceOf(Request)
expect(request.url).toBe(httpRequest.url)
expect(request.method).toBe(httpRequest.verb)
expect(request.headers.get('X-SNJS-Version')).toBe(snjsVersion)
expect(request.headers.get('X-Application-Version')).toBe(`${Environment[environment]}-${appVersion}`)
expect(request.headers.get('Content-Type')).toBe('application/json')
})
it('should get url for url and params', () => {
const urlWithoutExistingParams = requestHandler['urlForUrlAndParams']('url', { key: 'value' })
expect(urlWithoutExistingParams).toBe('url?key=value')
const urlWithExistingParams = requestHandler['urlForUrlAndParams']('url?key=value', { key2: 'value2' })
expect(urlWithExistingParams).toBe('url?key=value&key2=value2')
})
it('should create request body if not GET', () => {
const body = requestHandler['createRequestBody']({
url: 'url',
verb: HttpVerb.Post,
external: false,
authentication: 'authentication',
customHeaders: [],
params: {
key: 'value',
},
})
expect(body).toBe('{"key":"value"}')
})
it('should not create request body if GET', () => {
const body = requestHandler['createRequestBody']({
url: 'url',
verb: HttpVerb.Get,
external: false,
authentication: 'authentication',
customHeaders: [],
params: {
key: 'value',
},
})
expect(body).toBeUndefined()
})
it('should handle json response', async () => {
const fetchResponse = new Response('{"key":"value"}', {
status: 200,
headers: {
'Content-Type': 'application/json',
},
})
const response = await requestHandler['handleFetchResponse'](fetchResponse)
expect(response).toEqual({
status: 200,
headers: new Map<string, string | null>([['content-type', 'application/json']]),
data: {
key: 'value',
},
key: 'value',
})
})
it('should handle non-json response', async () => {
const fetchResponse = new Response('body', {
status: 200,
headers: {
'Content-Type': 'text/plain',
},
})
const response = await requestHandler['handleFetchResponse'](fetchResponse)
expect(response.status).toBe(200)
expect(response.headers).toEqual(new Map<string, string | null>([['content-type', 'text/plain']]))
expect(response.data).toBeInstanceOf(ArrayBuffer)
})
it('should have ratelimit error when forbidden', async () => {
const fetchResponse = new Response('body', {
status: 403,
headers: {
'Content-Type': 'text/plain',
},
})
const response = await requestHandler['handleFetchResponse'](fetchResponse)
expect(response.status).toBe(403)
expect(response.headers).toEqual(new Map<string, string | null>([['content-type', 'text/plain']]))
expect((response.data as HttpErrorResponseBody).error).toEqual({
message: ErrorMessage.RateLimited,
})
})
describe('should return ErrorResponse when status is not >=200 and <500', () => {
it('should add unknown error message when response has no data', async () => {
const fetchResponse = new Response('', {
status: 599,
headers: {
'Content-Type': 'text/plain',
},
})
const response = await requestHandler['handleFetchResponse'](fetchResponse)
expect(response.status).toBe(599)
expect(response.headers).toEqual(new Map<string, string | null>([['content-type', 'text/plain']]))
expect((response.data as HttpErrorResponseBody).error).toEqual({
message: 'Unknown error',
})
})
})
})
@@ -0,0 +1,178 @@
import {
HttpErrorResponse,
HttpRequest,
HttpRequestParams,
HttpResponse,
HttpStatusCode,
HttpVerb,
isErrorResponse,
} from '@standardnotes/responses'
import { RequestHandlerInterface } from './RequestHandlerInterface'
import { Environment } from '@standardnotes/models'
import { isString } from 'lodash'
import { ErrorMessage } from '../Error'
export class FetchRequestHandler implements RequestHandlerInterface {
constructor(
protected readonly snjsVersion: string,
protected readonly appVersion: string,
protected readonly environment: Environment,
) {}
async handleRequest<T>(httpRequest: HttpRequest): Promise<HttpResponse<T>> {
const request = this.createRequest(httpRequest)
const response = await this.runRequest<T>(request, this.createRequestBody(httpRequest))
return response
}
private createRequest(httpRequest: HttpRequest): Request {
if (httpRequest.params && httpRequest.verb === HttpVerb.Get && Object.keys(httpRequest.params).length > 0) {
httpRequest.url = this.urlForUrlAndParams(httpRequest.url, httpRequest.params)
}
const headers: Record<string, string> = {}
if (!httpRequest.external) {
headers['X-SNJS-Version'] = this.snjsVersion
const appVersionHeaderValue = `${Environment[this.environment]}-${this.appVersion}`
headers['X-Application-Version'] = appVersionHeaderValue
if (httpRequest.authentication) {
headers['Authorization'] = 'Bearer ' + httpRequest.authentication
}
}
let contentTypeIsSet = false
if (httpRequest.customHeaders && httpRequest.customHeaders.length > 0) {
httpRequest.customHeaders.forEach(({ key, value }) => {
headers[key] = value
if (key === 'Content-Type') {
contentTypeIsSet = true
}
})
}
if (!contentTypeIsSet && !httpRequest.external) {
headers['Content-Type'] = 'application/json'
}
return new Request(httpRequest.url, {
method: httpRequest.verb,
headers,
})
}
private async runRequest<T>(request: Request, body?: string | Uint8Array | undefined): Promise<HttpResponse<T>> {
const fetchResponse = await fetch(request, {
body,
})
const response = await this.handleFetchResponse<T>(fetchResponse)
return response
}
private async handleFetchResponse<T>(fetchResponse: Response): Promise<HttpResponse<T>> {
const httpStatus = fetchResponse.status
const response: HttpResponse<T> = {
status: httpStatus,
headers: new Map<string, string | null>(),
data: {} as T,
}
fetchResponse.headers.forEach((value, key) => {
;(<Map<string, string | null>>response.headers).set(key, value)
})
try {
if (httpStatus !== HttpStatusCode.NoContent) {
let body
const contentTypeHeader = response.headers?.get('content-type') || response.headers?.get('Content-Type')
if (contentTypeHeader?.includes('application/json')) {
body = JSON.parse(await fetchResponse.text())
} else {
body = await fetchResponse.arrayBuffer()
}
/**
* v0 APIs do not have a `data` top-level object. In such cases, mimic
* the newer response body style by putting all the top-level
* properties inside a `data` object.
*/
if (!body.data) {
response.data = body
}
if (!isString(body)) {
Object.assign(response, body)
}
}
} catch (error) {
console.error(error)
}
if (httpStatus >= HttpStatusCode.Success && httpStatus < HttpStatusCode.InternalServerError) {
if (httpStatus === HttpStatusCode.Forbidden && isErrorResponse(response)) {
if (!response.data.error) {
response.data.error = {
message: ErrorMessage.RateLimited,
}
} else {
response.data.error.message = ErrorMessage.RateLimited
}
}
return response
} else {
const errorResponse = response as HttpErrorResponse
if (!errorResponse.data) {
errorResponse.data = {
error: {
message: 'Unknown error',
},
}
}
if (isString(errorResponse.data)) {
errorResponse.data = {
error: {
message: errorResponse.data,
},
}
}
if (!errorResponse.data.error) {
errorResponse.data.error = {
message: 'Unknown error',
}
}
return errorResponse
}
}
private urlForUrlAndParams(url: string, params: HttpRequestParams) {
const keyValueString = Object.keys(params as Record<string, unknown>)
.map((key) => {
return key + '=' + encodeURIComponent((params as Record<string, unknown>)[key] as string)
})
.join('&')
if (url.includes('?')) {
return url + '&' + keyValueString
} else {
return url + '?' + keyValueString
}
}
private createRequestBody(httpRequest: HttpRequest): string | Uint8Array | undefined {
if (
httpRequest.params !== undefined &&
[HttpVerb.Post, HttpVerb.Put, HttpVerb.Patch, HttpVerb.Delete].includes(httpRequest.verb)
) {
return JSON.stringify(httpRequest.params)
}
return httpRequest.rawBytes
}
}
+7 -162
View File
@@ -1,4 +1,4 @@
import { isString, joinPaths, sleep } from '@standardnotes/utils'
import { joinPaths, sleep } from '@standardnotes/utils'
import { Environment } from '@standardnotes/models'
import { Session, SessionToken } from '@standardnotes/domain-core'
import {
@@ -8,15 +8,14 @@ import {
HttpRequest,
HttpResponse,
HttpResponseMeta,
HttpErrorResponse,
isErrorResponse,
} from '@standardnotes/responses'
import { HttpServiceInterface } from './HttpServiceInterface'
import { XMLHttpRequestState } from './XMLHttpRequestState'
import { ErrorMessage } from '../Error/ErrorMessage'
import { Paths } from '../Server/Auth/Paths'
import { SessionRefreshResponseBody } from '../Response/Auth/SessionRefreshResponseBody'
import { FetchRequestHandler } from './FetchRequestHandler'
import { RequestHandlerInterface } from './RequestHandlerInterface'
export class HttpService implements HttpServiceInterface {
private session: Session | null
@@ -27,8 +26,11 @@ export class HttpService implements HttpServiceInterface {
private updateMetaCallback!: (meta: HttpResponseMeta) => void
private refreshSessionCallback!: (session: Session) => void
private requestHandler: RequestHandlerInterface
constructor(private environment: Environment, private appVersion: string, private snjsVersion: string) {
this.session = null
this.requestHandler = new FetchRequestHandler(this.snjsVersion, this.appVersion, this.environment)
}
setCallbacks(
@@ -131,9 +133,7 @@ export class HttpService implements HttpServiceInterface {
httpRequest.authentication = this.session?.accessToken.value
}
const request = this.createXmlRequest(httpRequest)
const response = await this.runRequest<T>(request, this.createRequestBody(httpRequest))
const response = await this.requestHandler.handleRequest<T>(httpRequest)
if (response.meta && !httpRequest.external) {
this.updateMetaCallback?.(response.meta)
@@ -209,159 +209,4 @@ export class HttpService implements HttpServiceInterface {
return true
}
private createRequestBody(httpRequest: HttpRequest): string | Uint8Array | undefined {
if (
httpRequest.params !== undefined &&
[HttpVerb.Post, HttpVerb.Put, HttpVerb.Patch, HttpVerb.Delete].includes(httpRequest.verb)
) {
return JSON.stringify(httpRequest.params)
}
return httpRequest.rawBytes
}
private createXmlRequest(httpRequest: HttpRequest) {
const request = new XMLHttpRequest()
if (httpRequest.params && httpRequest.verb === HttpVerb.Get && Object.keys(httpRequest.params).length > 0) {
httpRequest.url = this.urlForUrlAndParams(httpRequest.url, httpRequest.params)
}
request.open(httpRequest.verb, httpRequest.url, true)
request.responseType = httpRequest.responseType ?? ''
if (!httpRequest.external) {
request.setRequestHeader('X-SNJS-Version', this.snjsVersion)
const appVersionHeaderValue = `${Environment[this.environment]}-${this.appVersion}`
request.setRequestHeader('X-Application-Version', appVersionHeaderValue)
if (httpRequest.authentication) {
request.setRequestHeader('Authorization', 'Bearer ' + httpRequest.authentication)
}
}
let contenTypeIsSet = false
if (httpRequest.customHeaders && httpRequest.customHeaders.length > 0) {
httpRequest.customHeaders.forEach(({ key, value }) => {
request.setRequestHeader(key, value)
if (key === 'Content-Type') {
contenTypeIsSet = true
}
})
}
if (!contenTypeIsSet && !httpRequest.external) {
request.setRequestHeader('Content-Type', 'application/json')
}
return request
}
private async runRequest<T>(request: XMLHttpRequest, body?: string | Uint8Array): Promise<HttpResponse<T>> {
return new Promise((resolve) => {
request.onreadystatechange = () => {
this.stateChangeHandlerForRequest(request, resolve)
}
request.send(body)
})
}
private stateChangeHandlerForRequest<T>(request: XMLHttpRequest, resolve: (response: HttpResponse<T>) => void) {
if (request.readyState !== XMLHttpRequestState.Completed) {
return
}
const httpStatus = request.status
const response: HttpResponse<T> = {
status: httpStatus,
headers: new Map<string, string | null>(),
data: {} as T,
}
const responseHeaderLines = request
.getAllResponseHeaders()
?.trim()
.split(/[\r\n]+/)
responseHeaderLines?.forEach((responseHeaderLine) => {
const parts = responseHeaderLine.split(': ')
const name = parts.shift() as string
const value = parts.join(': ')
;(<Map<string, string | null>>response.headers).set(name, value)
})
try {
if (httpStatus !== HttpStatusCode.NoContent) {
let body
const contentTypeHeader = response.headers?.get('content-type') || response.headers?.get('Content-Type')
if (contentTypeHeader?.includes('application/json')) {
body = JSON.parse(request.responseText)
} else {
body = request.response
}
/**
* v0 APIs do not have a `data` top-level object. In such cases, mimic
* the newer response body style by putting all the top-level
* properties inside a `data` object.
*/
if (!body.data) {
response.data = body
}
if (!isString(body)) {
Object.assign(response, body)
}
}
} catch (error) {
console.error(error)
}
if (httpStatus >= HttpStatusCode.Success && httpStatus < HttpStatusCode.InternalServerError) {
if (
httpStatus === HttpStatusCode.Forbidden &&
response.data &&
(response as HttpErrorResponse).data.error !== undefined
) {
;(response as HttpErrorResponse).data.error.message = ErrorMessage.RateLimited
}
resolve(response)
} else {
const errorResponse = response as HttpErrorResponse
if (!errorResponse.data) {
errorResponse.data = {
error: {
message: 'Unknown error',
},
}
}
if (isString(errorResponse.data)) {
errorResponse.data = {
error: {
message: errorResponse.data,
},
}
}
if (!errorResponse.data.error) {
errorResponse.data.error = {
message: 'Unknown error',
}
}
resolve(errorResponse)
}
}
private urlForUrlAndParams(url: string, params: HttpRequestParams) {
const keyValueString = Object.keys(params as Record<string, unknown>)
.map((key) => {
return key + '=' + encodeURIComponent((params as Record<string, unknown>)[key] as string)
})
.join('&')
if (url.includes('?')) {
return url + '&' + keyValueString
} else {
return url + '?' + keyValueString
}
}
}
@@ -0,0 +1,5 @@
import { HttpRequest, HttpResponse } from '@standardnotes/responses'
export interface RequestHandlerInterface {
handleRequest<T>(httpRequest: HttpRequest): Promise<HttpResponse<T>>
}
@@ -1,8 +1,7 @@
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type SubscriptionInviteCancelRequestParams = {
[ApiEndpointParam.ApiVersion]: ApiVersion.v0
[ApiEndpointParam.ApiVersion]: string
inviteUuid: string
[additionalParam: string]: unknown
}
@@ -1,8 +1,7 @@
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type SubscriptionInviteDeclineRequestParams = {
[ApiEndpointParam.ApiVersion]: ApiVersion.v0
[ApiEndpointParam.ApiVersion]: string
inviteUuid: string
[additionalParam: string]: unknown
}
@@ -1,7 +1,6 @@
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type SubscriptionInviteListRequestParams = {
[ApiEndpointParam.ApiVersion]: ApiVersion.v0
[ApiEndpointParam.ApiVersion]: string
[additionalParam: string]: unknown
}
@@ -1,8 +1,7 @@
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type SubscriptionInviteRequestParams = {
[ApiEndpointParam.ApiVersion]: ApiVersion.v0
[ApiEndpointParam.ApiVersion]: string
identifier: string
[additionalParam: string]: unknown
}
@@ -1,9 +1,8 @@
import { AnyKeyParamsContent } from '@standardnotes/common'
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type UserRegistrationRequestParams = AnyKeyParamsContent & {
[ApiEndpointParam.ApiVersion]: ApiVersion.v0
[ApiEndpointParam.ApiVersion]: string
[additionalParam: string]: unknown
password: string
email: string
@@ -1,7 +1,6 @@
import { ApiEndpointParam } from '@standardnotes/responses'
import { ApiVersion } from '../../Api/ApiVersion'
export type UserUpdateRequestParams = {
[ApiEndpointParam.ApiVersion]: ApiVersion.v0
[ApiEndpointParam.ApiVersion]: string
user_uuid: string
}
+2
View File
@@ -13,7 +13,9 @@ export * from './Subscription/SubscriptionInviteCancelRequestParams'
export * from './Subscription/SubscriptionInviteDeclineRequestParams'
export * from './Subscription/SubscriptionInviteListRequestParams'
export * from './Subscription/SubscriptionInviteRequestParams'
export * from './User/UserDeletionRequestParams'
export * from './User/UserRegistrationRequestParams'
export * from './User/UserUpdateRequestParams'
export * from './UserRequest/UserRequestRequestParams'
export * from './WebSocket/WebSocketConnectionTokenRequestParams'
export * from './SharedVault/SharedVaultMoveType'
+104
View File
@@ -3,6 +3,110 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.84](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-12)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.83](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-12)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.82](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-11)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.81](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-11)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.80](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-11)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.79](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-10)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.78](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-10)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.77](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-10)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.76](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.75](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.74](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.73](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.72](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.71](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.70](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.69](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.68](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.67](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.66](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.65](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.64](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-02)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.63](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-02)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.62](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-02)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.61](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-02)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.60](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-06-30)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.59](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-06-30)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.58](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-06-30)
**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.58",
"version": "1.1.84",
"private": true,
"scripts": {
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
+3 -1
View File
@@ -9,5 +9,7 @@ test/data/tmp/
.idea
.env
data/*
codeqldb
yarn-error.log
yarn-error.log
+108
View File
@@ -3,6 +3,114 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.108.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-12)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.14](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-12)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-11)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-11)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-11)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-10)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-10)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-10)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-07)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/desktop
# [3.108.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
### Features
* New one-click Home Server, now in Labs. Launch your own self-hosted server instance with just 1 click from the Preferences window. ([#2345](https://github.com/standardnotes/app/issues/2345)) ([0552878](https://github.com/standardnotes/app/commit/05528782a4e9684dbd86fd12b77eb86727d6c414))
## [3.107.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.107.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.107.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/desktop
# [3.107.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
### Features
* New one-click Home Server, now in Labs. Launch your own self-hosted server instance with just 1 click from the Preferences window. ([#2341](https://github.com/standardnotes/app/issues/2341)) ([96f4264](https://github.com/standardnotes/app/commit/96f42643a98586052bfb5262ca797fd029c1e925))
## [3.106.73](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-02)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.72](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-02)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.71](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-02)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.70](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-02)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.69](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-06-30)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.68](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-06-30)
**Note:** Version bump only for package @standardnotes/desktop
## [3.106.67](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-06-30)
**Note:** Version bump only for package @standardnotes/desktop
+1 -1
View File
@@ -28,6 +28,7 @@ export function initializeApplication(args: { app: Electron.App; ipcMain: Electr
if (isDev()) {
/** Expose the app's state as a global variable. Useful for debugging */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(global as any).appState = state
setTimeout(() => {
@@ -124,7 +125,6 @@ async function finishApplicationInitialization({ app, shell, state }: { app: App
})
if (state.isRunningVersionForFirstTime()) {
console.log('Clearing window cache')
await windowState.window.webContents.session.clearCache()
}
@@ -0,0 +1,60 @@
import path from 'path'
import { shell } from 'electron'
import { DirectoryManagerInterface } from '@standardnotes/snjs'
import { FilesManagerInterface } from '../File/FilesManagerInterface'
export class DirectoryManager implements DirectoryManagerInterface {
private lastErrorMessage: string | undefined
constructor(private filesManager: FilesManagerInterface) {}
async presentDirectoryPickerForLocationChangeAndTransferOld(
appendPath: string,
oldLocation?: string,
): Promise<string | undefined> {
try {
this.lastErrorMessage = undefined
const selectedDirectory = await this.filesManager.openDirectoryPicker('Select')
if (!selectedDirectory) {
return undefined
}
const newPath = path.join(selectedDirectory, path.normalize(appendPath))
await this.filesManager.ensureDirectoryExists(newPath)
if (oldLocation) {
const result = await this.filesManager.moveDirectory(path.normalize(oldLocation), newPath)
if (result.isFailed()) {
this.lastErrorMessage = result.getError()
return undefined
}
const deletingDirectoryResult = await this.filesManager.deleteDir(path.normalize(oldLocation))
if (deletingDirectoryResult.isFailed()) {
this.lastErrorMessage = deletingDirectoryResult.getError()
return undefined
}
}
return newPath
} catch (error) {
this.lastErrorMessage = (error as Error).message
return undefined
}
}
async openLocation(location: string): Promise<void> {
void shell.openPath(location)
}
async getDirectoryManagerLastErrorMessage(): Promise<string | undefined> {
return this.lastErrorMessage
}
}
@@ -5,8 +5,8 @@ import path from 'path'
import { URL } from 'url'
import { extensions as str } from './Strings'
import { Paths } from './Types/Paths'
import { FileDoesNotExist } from './Utils/FileUtils'
import { app } from 'electron'
import { FileErrorCodes } from './File/FileErrorCodes'
const Protocol = 'http'
@@ -80,7 +80,7 @@ function onRequestError(error: Error | { code: string }, response: ServerRespons
let responseCode: number
let message: string
if ('code' in error && error.code === FileDoesNotExist) {
if ('code' in error && error.code === FileErrorCodes.FileDoesNotExist) {
responseCode = 404
message = str().missingExtension
} else {
@@ -0,0 +1,6 @@
export enum FileErrorCodes {
FileDoesNotExist = 'ENOENT',
FileAlreadyExists = 'EEXIST',
OperationNotPermitted = 'EPERM',
DeviceIsBusy = 'EBUSY',
}
@@ -0,0 +1,314 @@
import { dialog } from 'electron'
import fs, { PathLike } from 'fs'
import fse from 'fs-extra'
import { debounce } from 'lodash'
import path from 'path'
import yauzl from 'yauzl'
import { Result } from '@standardnotes/domain-core'
import { removeFromArray } from '../Utils/Utils'
import { FileErrorCodes } from './FileErrorCodes'
import { FilesManagerInterface } from './FilesManagerInterface'
export class FilesManager implements FilesManagerInterface {
debouncedJSONDiskWriter(durationMs: number, location: string, data: () => unknown): () => void {
let writingToDisk = false
return debounce(async () => {
if (writingToDisk) {
return
}
writingToDisk = true
try {
await this.writeJSONFile(location, data())
} catch (error) {
console.error(error)
} finally {
writingToDisk = false
}
}, durationMs)
}
async openDirectoryPicker(buttonLabel?: string): Promise<string | undefined> {
const result = await dialog.showOpenDialog({
properties: ['openDirectory', 'showHiddenFiles', 'createDirectory'],
buttonLabel: buttonLabel,
})
return result.filePaths[0]
}
async readJSONFile<T>(filepath: string): Promise<T | undefined> {
try {
const data = await fs.promises.readFile(filepath, 'utf8')
return JSON.parse(data)
} catch (error) {
return undefined
}
}
readJSONFileSync<T>(filepath: string): T {
const data = fs.readFileSync(filepath, 'utf8')
return JSON.parse(data)
}
async writeJSONFile(filepath: string, data: unknown): Promise<void> {
await this.ensureDirectoryExists(path.dirname(filepath))
await fs.promises.writeFile(filepath, JSON.stringify(data, null, 2), 'utf8')
}
async writeFile(filepath: string, data: string): Promise<void> {
await this.ensureDirectoryExists(path.dirname(filepath))
await fs.promises.writeFile(filepath, data, 'utf8')
}
writeJSONFileSync(filepath: string, data: unknown): void {
fs.writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf8')
}
async ensureDirectoryExists(dirPath: string): Promise<void> {
try {
const stat = await fs.promises.lstat(dirPath)
if (!stat.isDirectory()) {
throw new Error('Tried to create a directory where a file of the same ' + `name already exists: ${dirPath}`)
}
} catch (error: any) {
if (error.code === FileErrorCodes.FileDoesNotExist) {
/**
* No directory here. Make sure there is a *parent* directory, and then
* create it.
*/
await this.ensureDirectoryExists(path.dirname(dirPath))
/** Now that its parent(s) exist, create the directory */
try {
await fs.promises.mkdir(dirPath)
} catch (error: any) {
if (error.code === FileErrorCodes.FileAlreadyExists) {
/**
* A concurrent process must have created the directory already.
* Make sure it *is* a directory and not something else.
*/
await this.ensureDirectoryExists(dirPath)
} else {
throw error
}
}
} else {
throw error
}
}
}
async deleteDir(dirPath: string): Promise<Result<string>> {
try {
fse.removeSync(dirPath)
return Result.ok('Directory deleted successfully')
} catch (error) {
return Result.fail((error as Error).message)
}
}
async deleteDirContents(dirPath: string): Promise<void> {
/**
* Scan the directory up to ten times, to handle cases where files are being added while
* the directory's contents are being deleted
*/
for (let i = 1, maxTries = 10; i < maxTries; i++) {
const children = await fs.promises.readdir(dirPath, {
withFileTypes: true,
})
if (children.length === 0) {
break
}
for (const child of children) {
const childPath = path.join(dirPath, child.name)
if (child.isDirectory()) {
await this.deleteDirContents(childPath)
try {
await fs.promises.rmdir(childPath)
} catch (error) {
if (error !== FileErrorCodes.FileDoesNotExist) {
throw error
}
}
} else {
await this.deleteFile(childPath)
}
}
}
}
isChildOfDir(parent: string, potentialChild: string): boolean {
const relative = path.relative(parent, potentialChild)
return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative)
}
async moveDirectory(dir: string, destination: string): Promise<Result<string>> {
try {
await fse.move(dir, destination, { overwrite: true })
return Result.ok('Directory moved successfully')
} catch (error) {
return Result.fail((error as Error).message)
}
}
async moveDirContents(srcDir: string, destDir: string): Promise<Result<string>> {
try {
let srcDirectoryContents = await fs.promises.readdir(srcDir)
await this.ensureDirectoryExists(destDir)
if (this.isChildOfDir(srcDir, destDir)) {
srcDirectoryContents = srcDirectoryContents.filter((name) => {
return !this.isChildOfDir(destDir, path.join(srcDir, name))
})
removeFromArray(srcDirectoryContents, path.basename(destDir))
}
const directoryNames = []
const fileNames = []
for (const contentName of srcDirectoryContents) {
const stats = await fs.promises.lstat(path.join(srcDir, contentName))
if (stats.isDirectory()) {
directoryNames.push(contentName)
continue
}
fileNames.push(contentName)
}
for (const directoryName of directoryNames) {
const result = await this.moveDirContents(path.join(srcDir, directoryName), path.join(destDir, directoryName))
if (result.isFailed()) {
return result
}
}
await this.moveFiles(
fileNames.map((fileName) => path.join(srcDir, fileName)),
destDir,
)
return Result.ok('Directory contents moved successfully')
} catch (error) {
console.error(error)
return Result.fail(`Could not move directory contentes: ${(error as Error).message}`)
}
}
async extractZip(source: string, dest: string): Promise<void> {
return new Promise((resolve, reject) => {
yauzl.open(source, { lazyEntries: true, autoClose: true }, (err, zipFile) => {
let cancelled = false
const tryReject = (err: Error) => {
if (!cancelled) {
cancelled = true
reject(err)
}
}
if (err) {
return tryReject(err)
}
if (!zipFile) {
return tryReject(new Error('zipFile === undefined'))
}
zipFile.readEntry()
zipFile.on('close', resolve)
zipFile.on('entry', (entry) => {
if (cancelled) {
return
}
const isEntryDirectory = entry.fileName.endsWith('/')
if (isEntryDirectory) {
zipFile.readEntry()
return
}
zipFile.openReadStream(entry, async (err, stream) => {
if (cancelled) {
return
}
if (err) {
return tryReject(err)
}
if (!stream) {
return tryReject(new Error('stream === undefined'))
}
stream.on('error', tryReject)
const filepath = path.join(dest, entry.fileName)
try {
await this.ensureDirectoryExists(path.dirname(filepath))
} catch (error: any) {
return tryReject(error)
}
const writeStream = fs.createWriteStream(filepath).on('error', tryReject).on('error', tryReject)
stream.pipe(writeStream).on('close', () => {
zipFile.readEntry()
})
})
})
})
})
}
async moveFiles(sources: string[], destDir: string): Promise<void[]> {
await this.ensureDirectoryExists(destDir)
return Promise.all(sources.map((fileName) => this.moveFile(fileName, path.join(destDir, path.basename(fileName)))))
}
async moveFile(source: PathLike, destination: PathLike): Promise<void> {
try {
await fs.promises.rename(source, destination)
} catch (_error) {
/** Fall back to copying and then deleting. */
await fs.promises.copyFile(source, destination, fs.constants.COPYFILE_FICLONE_FORCE)
await fs.promises.unlink(source)
}
}
async deleteFileIfExists(filePath: PathLike): Promise<void> {
try {
await this.deleteFile(filePath)
} catch {
return
}
}
async deleteFile(filePath: PathLike): Promise<void> {
for (let i = 1, maxTries = 10; i < maxTries; i++) {
try {
await fs.promises.unlink(filePath)
break
} catch (error: any) {
if (error.code === FileErrorCodes.OperationNotPermitted || error.code === FileErrorCodes.DeviceIsBusy) {
await new Promise((resolve) => setTimeout(resolve, 300))
continue
} else if (error.code === FileErrorCodes.FileDoesNotExist) {
/** Already deleted */
break
}
throw error
}
}
}
}
@@ -0,0 +1,23 @@
import { Result } from '@standardnotes/domain-core'
import { PathLike } from 'fs'
export interface FilesManagerInterface {
debouncedJSONDiskWriter(durationMs: number, location: string, data: () => unknown): () => void
openDirectoryPicker(buttonLabel?: string): Promise<string | undefined>
readJSONFile<T>(filepath: string): Promise<T | undefined>
readJSONFileSync<T>(filepath: string): T
writeJSONFile(filepath: string, data: unknown): Promise<void>
writeFile(filepath: string, data: string): Promise<void>
writeJSONFileSync(filepath: string, data: unknown): void
ensureDirectoryExists(dirPath: string): Promise<void>
deleteDir(dirPath: string): Promise<Result<string>>
deleteDirContents(dirPath: string): Promise<void>
isChildOfDir(parent: string, potentialChild: string): boolean
moveDirectory(dir: string, destination: string): Promise<Result<string>>
moveDirContents(srcDir: string, destDir: string): Promise<Result<string>>
extractZip(source: string, dest: string): Promise<void>
moveFiles(sources: string[], destDir: string): Promise<void[]>
moveFile(source: PathLike, destination: PathLike): Promise<void>
deleteFileIfExists(filePath: PathLike): Promise<void>
deleteFile(filePath: PathLike): Promise<void>
}
@@ -9,23 +9,14 @@ import {
} from '@web/Application/Device/DesktopSnjsExports'
import { AppState } from 'app/AppState'
import { promises as fs, existsSync } from 'fs'
import { WebContents, shell } from 'electron'
import { WebContents } from 'electron'
import { StoreKeys } from '../Store/StoreKeys'
import path from 'path'
import {
deleteFileIfExists,
ensureDirectoryExists,
moveDirContents,
moveFile,
openDirectoryPicker,
readJSONFile,
writeFile,
writeJSONFile,
} from '../Utils/FileUtils'
import { FileDownloader } from './FileDownloader'
import { FileReadOperation } from './FileReadOperation'
import { Paths } from '../Types/Paths'
import { MessageToWebApp } from '../../Shared/IpcMessages'
import { FilesManagerInterface } from '../File/FilesManagerInterface'
const TextBackupFileExtension = '.txt'
@@ -39,7 +30,11 @@ export class FilesBackupManager implements FileBackupsDevice {
private readOperations: Map<string, FileReadOperation> = new Map()
private plaintextMappingCache?: PlaintextBackupsMapping
constructor(private appState: AppState, private webContents: WebContents) {}
constructor(
private appState: AppState,
private webContents: WebContents,
private filesManager: FilesManagerInterface,
) {}
private async findUuidForPlaintextBackupFileName(
backupsDirectory: string,
@@ -72,16 +67,16 @@ export class FilesBackupManager implements FileBackupsDevice {
return
}
await ensureDirectoryExists(newLocation)
await this.filesManager.ensureDirectoryExists(newLocation)
const legacyMappingLocation = path.join(legacyLocation, 'info.json')
const newMappingLocation = this.getFileBackupsMappingFilePath(newLocation)
await ensureDirectoryExists(path.dirname(newMappingLocation))
await this.filesManager.ensureDirectoryExists(path.dirname(newMappingLocation))
if (existsSync(legacyMappingLocation)) {
await moveFile(legacyMappingLocation, newMappingLocation)
await this.filesManager.moveFile(legacyMappingLocation, newMappingLocation)
}
await moveDirContents(legacyLocation, newLocation)
await this.filesManager.moveDirContents(legacyLocation, newLocation)
}
public async isLegacyFilesBackupsEnabled(): Promise<boolean> {
@@ -111,33 +106,12 @@ export class FilesBackupManager implements FileBackupsDevice {
return path.join(Paths.homeDir, LegacyTextBackupsDirectory)
}
public async presentDirectoryPickerForLocationChangeAndTransferOld(
appendPath: string,
oldLocation?: string,
): Promise<string | undefined> {
const selectedDirectory = await openDirectoryPicker('Select')
if (!selectedDirectory) {
return undefined
}
const newPath = path.join(selectedDirectory, path.normalize(appendPath))
await ensureDirectoryExists(newPath)
if (oldLocation) {
await moveDirContents(path.normalize(oldLocation), newPath)
}
return newPath
}
private getFileBackupsMappingFilePath(backupsLocation: string): string {
return path.join(backupsLocation, '.settings', 'info.json')
}
private async getFileBackupsMappingFileFromDisk(backupsLocation: string): Promise<FileBackupsMapping | undefined> {
return readJSONFile<FileBackupsMapping>(this.getFileBackupsMappingFilePath(backupsLocation))
return this.filesManager.readJSONFile<FileBackupsMapping>(this.getFileBackupsMappingFilePath(backupsLocation))
}
private defaulFileBackupstMappingFileValue(): FileBackupsMapping {
@@ -158,12 +132,8 @@ export class FilesBackupManager implements FileBackupsDevice {
return data
}
async openLocation(location: string): Promise<void> {
void shell.openPath(location)
}
private async saveFilesBackupsMappingFile(location: string, file: FileBackupsMapping): Promise<'success' | 'failed'> {
await writeJSONFile(this.getFileBackupsMappingFilePath(location), file)
await this.filesManager.writeJSONFile(this.getFileBackupsMappingFilePath(location), file)
return 'success'
}
@@ -182,9 +152,9 @@ export class FilesBackupManager implements FileBackupsDevice {
const metaFilePath = path.join(fileDir, FileBackupsConstantsV1.MetadataFileName)
const binaryPath = path.join(fileDir, FileBackupsConstantsV1.BinaryFileName)
await ensureDirectoryExists(fileDir)
await this.filesManager.ensureDirectoryExists(fileDir)
await writeFile(metaFilePath, metaFile)
await this.filesManager.writeFile(metaFilePath, metaFile)
const downloader = new FileDownloader(
downloadRequest.chunkSizes,
@@ -247,7 +217,7 @@ export class FilesBackupManager implements FileBackupsDevice {
let success: boolean
try {
await ensureDirectoryExists(location)
await this.filesManager.ensureDirectoryExists(location)
const name = `${new Date().toISOString().replace(/:/g, '-')}${TextBackupFileExtension}`
const filePath = path.join(location, name)
await fs.writeFile(filePath, data)
@@ -262,7 +232,7 @@ export class FilesBackupManager implements FileBackupsDevice {
async copyDecryptScript(location: string) {
try {
await ensureDirectoryExists(location)
await this.filesManager.ensureDirectoryExists(location)
await fs.copyFile(Paths.decryptScript, path.join(location, path.basename(Paths.decryptScript)))
} catch (error) {
console.error(error)
@@ -274,14 +244,14 @@ export class FilesBackupManager implements FileBackupsDevice {
}
private async getPlaintextMappingFileFromDisk(location: string): Promise<PlaintextBackupsMapping | undefined> {
return readJSONFile<PlaintextBackupsMapping>(this.getPlaintextMappingFilePath(location))
return this.filesManager.readJSONFile<PlaintextBackupsMapping>(this.getPlaintextMappingFilePath(location))
}
private async savePlaintextBackupsMappingFile(
location: string,
file: PlaintextBackupsMapping,
): Promise<'success' | 'failed'> {
await writeJSONFile(this.getPlaintextMappingFilePath(location), file)
await this.filesManager.writeJSONFile(this.getPlaintextMappingFilePath(location), file)
return 'success'
}
@@ -324,7 +294,7 @@ export class FilesBackupManager implements FileBackupsDevice {
const records = mapping.files[uuid]
for (const record of records) {
const filePath = path.join(location, record.path)
await deleteFileIfExists(filePath)
await this.filesManager.deleteFileIfExists(filePath)
}
mapping.files[uuid] = []
}
@@ -337,12 +307,12 @@ export class FilesBackupManager implements FileBackupsDevice {
return records.find((record) => record.tag === tag)
}
await ensureDirectoryExists(absolutePath)
await this.filesManager.ensureDirectoryExists(absolutePath)
const relativePath = forTag ?? ''
const filenameWithSlashesEscaped = filename.replace(/\//g, '\u2215')
const fileAbsolutePath = path.join(absolutePath, relativePath, filenameWithSlashesEscaped)
await writeFile(fileAbsolutePath, data)
await this.filesManager.writeFile(fileAbsolutePath, data)
const existingRecord = findMappingRecord(forTag)
if (!existingRecord) {
@@ -385,6 +355,10 @@ export class FilesBackupManager implements FileBackupsDevice {
const watcher = fs.watch(backupsDirectory, { recursive: true })
for await (const event of watcher) {
const { eventType, filename } = event
if (!filename) {
continue
}
if (eventType !== 'change' && eventType !== 'rename') {
continue
}
@@ -0,0 +1,7 @@
import { HomeServerEnvironmentConfiguration } from '@standardnotes/snjs'
export interface HomeServerConfigurationFile {
version: '1.0.0'
info: Record<string, string>
configuration: HomeServerEnvironmentConfiguration
}
@@ -0,0 +1,284 @@
import path from 'path'
import {
HomeServerManagerInterface,
HomeServerEnvironmentConfiguration,
Result,
} from '@web/Application/Device/DesktopSnjsExports'
import { WebContents } from 'electron'
import { MessageToWebApp } from '../../Shared/IpcMessages'
import { FilesManagerInterface } from '../File/FilesManagerInterface'
import { HomeServerConfigurationFile } from './HomeServerConfigurationFile'
import { isWindows } from '../Types/Platforms'
const os = require('os')
interface TempHomeServerInterface {
start(configuration?: unknown): Promise<Result<string>>
activatePremiumFeatures(username: string): Promise<Result<string>>
stop(): Promise<Result<string>>
isRunning(): Promise<boolean>
}
export class HomeServerManager implements HomeServerManagerInterface {
private readonly HOME_SERVER_CONFIGURATION_FILE_NAME = 'config.json'
private homeServerConfiguration: HomeServerEnvironmentConfiguration | undefined
private homeServerDataLocation: string | undefined
private lastErrorMessage: string | undefined
private logs: string[] = []
private readonly LOGS_BUFFER_SIZE = 1000
private homeServer?: TempHomeServerInterface
constructor(private webContents: WebContents, private filesManager: FilesManagerInterface) {}
async getHomeServerUrl(): Promise<string | undefined> {
const homeServerConfiguration = await this.getHomeServerConfigurationObject()
if (!homeServerConfiguration) {
return undefined
}
return `http://${this.getLocalIP()}:${homeServerConfiguration.port}`
}
async isHomeServerRunning(): Promise<boolean> {
return this.homeServer?.isRunning() ?? false
}
async getHomeServerLastErrorMessage(): Promise<string | undefined> {
return this.lastErrorMessage
}
async activatePremiumFeatures(username: string): Promise<string | undefined> {
if (!this.homeServer) {
return
}
const result = await this.homeServer.activatePremiumFeatures(username)
if (result.isFailed()) {
return result.getError()
}
}
async getHomeServerConfiguration(): Promise<string | undefined> {
if (this.homeServerConfiguration) {
return JSON.stringify(this.homeServerConfiguration)
}
if (!this.homeServerDataLocation) {
return undefined
}
const homeServerConfiguration = await this.filesManager.readJSONFile<HomeServerConfigurationFile>(
path.join(this.homeServerDataLocation, this.HOME_SERVER_CONFIGURATION_FILE_NAME),
)
if (!homeServerConfiguration) {
return undefined
}
return JSON.stringify(homeServerConfiguration.configuration)
}
async setHomeServerConfiguration(configurationJSONString: string): Promise<void> {
try {
if (!this.homeServerDataLocation) {
throw new Error('Home server data location is not set.')
}
const homeServerConfiguration = JSON.parse(configurationJSONString) as HomeServerEnvironmentConfiguration
await this.filesManager.ensureDirectoryExists(this.homeServerDataLocation)
const configurationFile: HomeServerConfigurationFile = {
version: '1.0.0',
info: {
warning: 'Do not edit this file.',
information:
'The values below are encrypted with a key created by the desktop application after installation. The key is stored in your secure device keychain.',
instructions:
'Put this file inside your home server data location to restore your home server configuration.',
},
configuration: homeServerConfiguration,
}
await this.filesManager.writeJSONFile(
path.join(this.homeServerDataLocation, this.HOME_SERVER_CONFIGURATION_FILE_NAME),
configurationFile,
)
this.homeServerConfiguration = homeServerConfiguration
} catch (error) {
console.error(`Could not save server configuration: ${(error as Error).message}`)
}
}
async setHomeServerDataLocation(location: string): Promise<void> {
this.homeServerDataLocation = location
}
async stopHomeServer(): Promise<string | undefined> {
if (!this.homeServer) {
return
}
const result = await this.homeServer.stop()
if (result.isFailed()) {
return result.getError()
}
return undefined
}
async startHomeServer(): Promise<string | undefined> {
await this.lazyLoadHomeServerOnApplicablePlatforms()
if (!this.homeServer) {
return
}
try {
this.lastErrorMessage = undefined
this.logs = []
let homeServerConfiguration = await this.getHomeServerConfigurationObject()
if (!homeServerConfiguration) {
homeServerConfiguration = this.generateHomeServerConfiguration()
}
await this.setHomeServerConfiguration(JSON.stringify(homeServerConfiguration))
if (!this.homeServerDataLocation) {
this.lastErrorMessage = 'Home server data location is not set.'
return this.lastErrorMessage
}
const {
jwtSecret,
authJwtSecret,
encryptionServerKey,
pseudoKeyParamsKey,
valetTokenSecret,
port,
logLevel,
databaseEngine,
mysqlConfiguration,
} = homeServerConfiguration as HomeServerEnvironmentConfiguration
const environment: { [name: string]: string } = {
JWT_SECRET: jwtSecret,
AUTH_JWT_SECRET: authJwtSecret,
ENCRYPTION_SERVER_KEY: encryptionServerKey,
PSEUDO_KEY_PARAMS_KEY: pseudoKeyParamsKey,
VALET_TOKEN_SECRET: valetTokenSecret,
FILES_SERVER_URL: (await this.getHomeServerUrl()) as string,
LOG_LEVEL: logLevel ?? 'info',
VERSION: 'desktop',
PORT: port.toString(),
DB_TYPE: databaseEngine,
}
if (mysqlConfiguration !== undefined) {
environment.DB_HOST = mysqlConfiguration.host
if (mysqlConfiguration.port) {
environment.DB_PORT = mysqlConfiguration.port.toString()
}
environment.DB_USERNAME = mysqlConfiguration.username
environment.DB_PASSWORD = mysqlConfiguration.password
environment.DB_DATABASE = mysqlConfiguration.database
}
const result = await this.homeServer.start({
dataDirectoryPath: this.homeServerDataLocation,
environment,
logStreamCallback: this.appendLogs.bind(this),
})
if (result.isFailed()) {
this.lastErrorMessage = result.getError()
return this.lastErrorMessage
}
this.webContents.send(MessageToWebApp.HomeServerStarted, await this.getHomeServerUrl())
} catch (error) {
return (error as Error).message
}
}
async getHomeServerLogs(): Promise<string[]> {
return this.logs
}
private appendLogs(log: Buffer): void {
this.logs.push(log.toString())
if (this.logs.length > this.LOGS_BUFFER_SIZE) {
this.logs.shift()
}
}
private generateRandomKey(length: number): string {
return require('crypto').randomBytes(length).toString('hex')
}
private getLocalIP() {
const interfaces = os.networkInterfaces()
for (const interfaceName in interfaces) {
const addresses = interfaces[interfaceName]
for (const address of addresses) {
if (address.family === 'IPv4' && !address.internal) {
return address.address
}
}
}
}
private async getHomeServerConfigurationObject(): Promise<HomeServerEnvironmentConfiguration | undefined> {
try {
const homeServerConfigurationJSON = await this.getHomeServerConfiguration()
if (!homeServerConfigurationJSON) {
return undefined
}
return JSON.parse(homeServerConfigurationJSON)
} catch (error) {
console.error(`Could not get home server configuration: ${(error as Error).message}`)
}
}
private generateHomeServerConfiguration(): HomeServerEnvironmentConfiguration {
const jwtSecret = this.generateRandomKey(32)
const authJwtSecret = this.generateRandomKey(32)
const encryptionServerKey = this.generateRandomKey(32)
const pseudoKeyParamsKey = this.generateRandomKey(32)
const valetTokenSecret = this.generateRandomKey(32)
const port = 3127
const configuration: HomeServerEnvironmentConfiguration = {
jwtSecret,
authJwtSecret,
encryptionServerKey,
pseudoKeyParamsKey,
valetTokenSecret,
port,
databaseEngine: 'sqlite',
logLevel: 'info',
}
return configuration
}
private async lazyLoadHomeServerOnApplicablePlatforms(): Promise<void> {
if (isWindows()) {
return
}
if (this.homeServer) {
return
}
}
}
@@ -4,9 +4,9 @@ import path from 'path'
import { pipeline as pipelineFn } from 'stream'
import { promisify } from 'util'
import { MessageType } from '../../../../test/TestIpcMessage'
import { ensureDirectoryExists } from '../Utils/FileUtils'
import { handleTestMessage } from '../Utils/Testing'
import { isTesting } from '../Utils/Utils'
import { FilesManager } from '../File/FilesManager'
const pipeline = promisify(pipelineFn)
@@ -21,7 +21,7 @@ if (isTesting()) {
* not exist)
*/
export async function downloadFile(url: string, filePath: string): Promise<void> {
await ensureDirectoryExists(path.dirname(filePath))
await new FilesManager().ensureDirectoryExists(path.dirname(filePath))
const response = await get(url)
await pipeline(
/**
@@ -5,19 +5,12 @@ import path from 'path'
import { MessageToWebApp } from '../../Shared/IpcMessages'
import { AppName } from '../Strings'
import { Paths } from '../Types/Paths'
import {
debouncedJSONDiskWriter,
deleteDir,
deleteDirContents,
ensureDirectoryExists,
extractZip,
FileDoesNotExist,
moveDirContents,
readJSONFile,
} from '../Utils/FileUtils'
import { timeout } from '../Utils/Utils'
import { downloadFile, getJSON } from './Networking'
import { Component, MappingFile, PackageInfo, PackageManagerInterface, SyncTask } from './PackageManagerInterface'
import { FilesManagerInterface } from '../File/FilesManagerInterface'
import { FilesManager } from '../File/FilesManager'
import { FileErrorCodes } from '../File/FileErrorCodes'
function logMessage(...message: any) {
log.info('PackageManager[Info]:', ...message)
@@ -33,26 +26,27 @@ function logError(...message: any) {
class MappingFileHandler {
static async create() {
let mapping: MappingFile
const filesManager = new FilesManager()
try {
const result = await readJSONFile<MappingFile>(Paths.extensionsMappingJson)
const result = await filesManager.readJSONFile<MappingFile>(Paths.extensionsMappingJson)
mapping = result || {}
} catch (error: any) {
/**
* Mapping file might be absent (first start, corrupted data)
*/
if (error.code === FileDoesNotExist) {
await ensureDirectoryExists(path.dirname(Paths.extensionsMappingJson))
if (error.code === FileErrorCodes.FileDoesNotExist) {
await filesManager.ensureDirectoryExists(path.dirname(Paths.extensionsMappingJson))
} else {
logError(error)
}
mapping = {}
}
return new MappingFileHandler(mapping)
return new MappingFileHandler(mapping, filesManager)
}
constructor(private mapping: MappingFile) {}
constructor(private mapping: MappingFile, private filesManager: FilesManagerInterface) {}
get = (componendId: string) => {
return this.mapping[componendId]
@@ -63,12 +57,14 @@ class MappingFileHandler {
location,
version,
}
this.writeToDisk()
this.filesManager.debouncedJSONDiskWriter(100, Paths.extensionsMappingJson, () => this.mapping)
}
remove = (componentId: string) => {
delete this.mapping[componentId]
this.writeToDisk()
this.filesManager.debouncedJSONDiskWriter(100, Paths.extensionsMappingJson, () => this.mapping)
}
getInstalledVersionForComponent = async (component: Component): Promise<string> => {
@@ -83,15 +79,13 @@ class MappingFileHandler {
*/
const paths = pathsForComponent(component)
const packagePath = path.join(paths.absolutePath, 'package.json')
const response = await readJSONFile<{ version: string }>(packagePath)
const response = await this.filesManager.readJSONFile<{ version: string }>(packagePath)
if (!response) {
return ''
}
this.set(component.uuid, paths.relativePath, response.version)
return response.version
}
private writeToDisk = debouncedJSONDiskWriter(100, Paths.extensionsMappingJson, () => this.mapping)
}
export async function initializePackageManager(webContents: Electron.WebContents): Promise<PackageManagerInterface> {
@@ -230,7 +224,7 @@ async function syncComponents(webContents: Electron.WebContents, mapping: Mappin
await checkForUpdate(webContents, mapping, component)
}
} catch (error: any) {
if (error.code === FileDoesNotExist) {
if (error.code === FileErrorCodes.FileDoesNotExist) {
/** We have a component but no content. Install the component */
await installComponent(webContents, mapping, component, component.content.package_info, version)
} else {
@@ -289,7 +283,7 @@ async function unnestLegacyStructure(dir: string) {
const sourceDir = path.join(dir, fileNames[0])
const destDir = dir
await moveDirContents(sourceDir, destDir)
await new FilesManager().moveDirContents(sourceDir, destDir)
}
async function installComponent(
@@ -331,13 +325,15 @@ async function installComponent(
downloadFile(downloadUrl, paths.downloadPath),
(async () => {
/** Clear the component's directory before extracting the zip. */
await ensureDirectoryExists(paths.absolutePath)
await deleteDirContents(paths.absolutePath)
const filesManager = new FilesManager()
await filesManager.ensureDirectoryExists(paths.absolutePath)
await filesManager.deleteDirContents(paths.absolutePath)
})(),
])
logMessage('Extracting', paths.downloadPath, 'to', paths.absolutePath)
await extractZip(paths.downloadPath, paths.absolutePath)
const filesManager = new FilesManager()
await filesManager.extractZip(paths.downloadPath, paths.absolutePath)
const legacyStructure = await usesLegacyNestedFolderStructure(paths.absolutePath)
if (legacyStructure) {
@@ -348,7 +344,7 @@ async function installComponent(
try {
/** Try to read 'sn.main' field from 'package.json' file */
const packageJsonPath = path.join(paths.absolutePath, 'package.json')
const packageJson = await readJSONFile<{
const packageJson = await filesManager.readJSONFile<{
sn?: { main?: string }
version?: string
}>(packageJsonPath)
@@ -403,6 +399,8 @@ async function uninstallComponent(mapping: MappingFileHandler, uuid: string) {
/** No mapping for component */
return
}
await deleteDir(path.join(Paths.userDataDir, componentMapping.location))
mapping.remove(uuid)
const result = await new FilesManager().deleteDir(path.join(Paths.userDataDir, componentMapping.location))
if (!result.isFailed()) {
mapping.remove(uuid)
}
}
@@ -10,7 +10,9 @@ import {
FileBackupsMapping,
FileBackupReadToken,
FileBackupReadChunkResponse,
HomeServerManagerInterface,
PlaintextBackupsMapping,
DirectoryManagerInterface,
} from '@web/Application/Device/DesktopSnjsExports'
import { app, BrowserWindow } from 'electron'
import { KeychainInterface } from '../Keychain/KeychainInterface'
@@ -34,6 +36,8 @@ export class RemoteBridge implements CrossProcessBridge {
private menus: MenuManagerInterface,
private fileBackups: FileBackupsDevice,
private media: MediaManagerInterface,
private homeServerManager: HomeServerManagerInterface,
private directoryManager: DirectoryManagerInterface,
) {}
get exposableValue(): CrossProcessBridge {
@@ -63,6 +67,8 @@ export class RemoteBridge implements CrossProcessBridge {
getFileBackupReadToken: this.getFileBackupReadToken.bind(this),
readNextChunk: this.readNextChunk.bind(this),
askForMediaAccess: this.askForMediaAccess.bind(this),
startHomeServer: this.startHomeServer.bind(this),
stopHomeServer: this.stopHomeServer.bind(this),
wasLegacyTextBackupsExplicitlyDisabled: this.wasLegacyTextBackupsExplicitlyDisabled.bind(this),
getLegacyTextBackupsLocation: this.getLegacyTextBackupsLocation.bind(this),
saveTextBackupData: this.saveTextBackupData.bind(this),
@@ -70,6 +76,7 @@ export class RemoteBridge implements CrossProcessBridge {
openLocation: this.openLocation.bind(this),
presentDirectoryPickerForLocationChangeAndTransferOld:
this.presentDirectoryPickerForLocationChangeAndTransferOld.bind(this),
getDirectoryManagerLastErrorMessage: this.getDirectoryManagerLastErrorMessage.bind(this),
getPlaintextBackupsMappingFile: this.getPlaintextBackupsMappingFile.bind(this),
persistPlaintextBackupsMappingFile: this.persistPlaintextBackupsMappingFile.bind(this),
getTextBackupsCount: this.getTextBackupsCount.bind(this),
@@ -77,6 +84,14 @@ export class RemoteBridge implements CrossProcessBridge {
getUserDocumentsDirectory: this.getUserDocumentsDirectory.bind(this),
monitorPlaintextBackupsLocationForChanges: this.monitorPlaintextBackupsLocationForChanges.bind(this),
joinPaths: this.joinPaths.bind(this),
setHomeServerConfiguration: this.setHomeServerConfiguration.bind(this),
getHomeServerConfiguration: this.getHomeServerConfiguration.bind(this),
setHomeServerDataLocation: this.setHomeServerDataLocation.bind(this),
activatePremiumFeatures: this.activatePremiumFeatures.bind(this),
isHomeServerRunning: this.isHomeServerRunning.bind(this),
getHomeServerLogs: this.getHomeServerLogs.bind(this),
getHomeServerUrl: this.getHomeServerUrl.bind(this),
getHomeServerLastErrorMessage: this.getHomeServerLastErrorMessage.bind(this),
}
}
@@ -201,15 +216,19 @@ export class RemoteBridge implements CrossProcessBridge {
return this.fileBackups.savePlaintextNoteBackup(location, uuid, name, tags, data)
}
openLocation(path: string): Promise<void> {
return this.fileBackups.openLocation(path)
async openLocation(path: string): Promise<void> {
return this.directoryManager.openLocation(path)
}
presentDirectoryPickerForLocationChangeAndTransferOld(
async presentDirectoryPickerForLocationChangeAndTransferOld(
appendPath: string,
oldLocation?: string | undefined,
): Promise<string | undefined> {
return this.fileBackups.presentDirectoryPickerForLocationChangeAndTransferOld(appendPath, oldLocation)
return this.directoryManager.presentDirectoryPickerForLocationChangeAndTransferOld(appendPath, oldLocation)
}
async getDirectoryManagerLastErrorMessage(): Promise<string | undefined> {
return this.directoryManager.getDirectoryManagerLastErrorMessage()
}
getPlaintextBackupsMappingFile(location: string): Promise<PlaintextBackupsMapping> {
@@ -243,4 +262,44 @@ export class RemoteBridge implements CrossProcessBridge {
askForMediaAccess(type: 'camera' | 'microphone'): Promise<boolean> {
return this.media.askForMediaAccess(type)
}
async startHomeServer(): Promise<string | undefined> {
return this.homeServerManager.startHomeServer()
}
async stopHomeServer(): Promise<string | undefined> {
return this.homeServerManager.stopHomeServer()
}
async setHomeServerConfiguration(configurationJSONString: string): Promise<void> {
return this.homeServerManager.setHomeServerConfiguration(configurationJSONString)
}
async getHomeServerConfiguration(): Promise<string | undefined> {
return this.homeServerManager.getHomeServerConfiguration()
}
async setHomeServerDataLocation(location: string): Promise<void> {
return this.homeServerManager.setHomeServerDataLocation(location)
}
async activatePremiumFeatures(username: string): Promise<string | undefined> {
return this.homeServerManager.activatePremiumFeatures(username)
}
async isHomeServerRunning(): Promise<boolean> {
return this.homeServerManager.isHomeServerRunning()
}
async getHomeServerLogs(): Promise<string[]> {
return this.homeServerManager.getHomeServerLogs()
}
async getHomeServerUrl(): Promise<string | undefined> {
return this.homeServerManager.getHomeServerUrl()
}
async getHomeServerLastErrorMessage(): Promise<string | undefined> {
return this.homeServerManager.getHomeServerLastErrorMessage()
}
}
@@ -11,6 +11,8 @@ export enum StoreKeys {
UseNativeKeychain = 'useNativeKeychain',
LastRunVersion = 'LastRunVersion',
HomeServerDataLocation = 'HomeServerDataLocation',
LegacyTextBackupsLocation = 'backupsLocation',
LegacyTextBackupsDisabled = 'backupsDisabled',
@@ -1,9 +1,9 @@
import fs from 'fs'
import { Language } from '../SpellcheckerManager'
import { FileDoesNotExist } from '../Utils/FileUtils'
import { ensureIsBoolean, isBoolean } from '../Utils/Utils'
import { StoreData, StoreKeys } from './StoreKeys'
import { logError } from './Store'
import { FileErrorCodes } from '../File/FileErrorCodes'
export function createSanitizedStoreData(data: any = {}): StoreData {
return {
@@ -69,7 +69,7 @@ export function parseDataFile(filePath: string) {
return createSanitizedStoreData(userData)
} catch (error: any) {
console.log('Error reading store file', error)
if (error.code !== FileDoesNotExist) {
if (error.code !== FileErrorCodes.FileDoesNotExist) {
logError(error)
}
@@ -1,291 +0,0 @@
import { dialog } from 'electron'
import fs, { PathLike } from 'fs'
import { debounce } from 'lodash'
import path from 'path'
import yauzl from 'yauzl'
import { removeFromArray } from '../Utils/Utils'
export const FileDoesNotExist = 'ENOENT'
export const FileAlreadyExists = 'EEXIST'
const OperationNotPermitted = 'EPERM'
const DeviceIsBusy = 'EBUSY'
export function debouncedJSONDiskWriter(durationMs: number, location: string, data: () => unknown): () => void {
let writingToDisk = false
return debounce(async () => {
if (writingToDisk) {
return
}
writingToDisk = true
try {
await writeJSONFile(location, data())
} catch (error) {
console.error(error)
} finally {
writingToDisk = false
}
}, durationMs)
}
export async function openDirectoryPicker(buttonLabel?: string): Promise<string | undefined> {
const result = await dialog.showOpenDialog({
properties: ['openDirectory', 'showHiddenFiles', 'createDirectory'],
buttonLabel: buttonLabel,
})
return result.filePaths[0]
}
export async function readJSONFile<T>(filepath: string): Promise<T | undefined> {
try {
const data = await fs.promises.readFile(filepath, 'utf8')
return JSON.parse(data)
} catch (error) {
return undefined
}
}
export function readJSONFileSync<T>(filepath: string): T {
const data = fs.readFileSync(filepath, 'utf8')
return JSON.parse(data)
}
export async function writeJSONFile(filepath: string, data: unknown): Promise<void> {
await ensureDirectoryExists(path.dirname(filepath))
await fs.promises.writeFile(filepath, JSON.stringify(data, null, 2), 'utf8')
}
export async function writeFile(filepath: string, data: string): Promise<void> {
await ensureDirectoryExists(path.dirname(filepath))
await fs.promises.writeFile(filepath, data, 'utf8')
}
export function writeJSONFileSync(filepath: string, data: unknown): void {
fs.writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf8')
}
/** Creates the directory if it doesn't exist. */
export async function ensureDirectoryExists(dirPath: string): Promise<void> {
try {
const stat = await fs.promises.lstat(dirPath)
if (!stat.isDirectory()) {
throw new Error('Tried to create a directory where a file of the same ' + `name already exists: ${dirPath}`)
}
} catch (error: any) {
if (error.code === FileDoesNotExist) {
/**
* No directory here. Make sure there is a *parent* directory, and then
* create it.
*/
await ensureDirectoryExists(path.dirname(dirPath))
/** Now that its parent(s) exist, create the directory */
try {
await fs.promises.mkdir(dirPath)
} catch (error: any) {
if (error.code === FileAlreadyExists) {
/**
* A concurrent process must have created the directory already.
* Make sure it *is* a directory and not something else.
*/
await ensureDirectoryExists(dirPath)
} else {
throw error
}
}
} else {
throw error
}
}
}
/**
* Deletes a directory (handling recursion.)
* @param {string} dirPath the path of the directory
*/
export async function deleteDir(dirPath: string): Promise<void> {
try {
await deleteDirContents(dirPath)
} catch (error: any) {
if (error.code === FileDoesNotExist) {
/** Directory has already been deleted. */
return
}
throw error
}
await fs.promises.rmdir(dirPath)
}
export async function deleteDirContents(dirPath: string): Promise<void> {
/**
* Scan the directory up to ten times, to handle cases where files are being added while
* the directory's contents are being deleted
*/
for (let i = 1, maxTries = 10; i < maxTries; i++) {
const children = await fs.promises.readdir(dirPath, {
withFileTypes: true,
})
if (children.length === 0) {
break
}
for (const child of children) {
const childPath = path.join(dirPath, child.name)
if (child.isDirectory()) {
await deleteDirContents(childPath)
try {
await fs.promises.rmdir(childPath)
} catch (error) {
if (error !== FileDoesNotExist) {
throw error
}
}
} else {
await deleteFile(childPath)
}
}
}
}
function isChildOfDir(parent: string, potentialChild: string) {
const relative = path.relative(parent, potentialChild)
return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
}
export async function moveDirContents(srcDir: string, destDir: string): Promise<void> {
let fileNames: string[]
try {
fileNames = await fs.promises.readdir(srcDir)
} catch (error) {
console.error(error)
return
}
await ensureDirectoryExists(destDir)
if (isChildOfDir(srcDir, destDir)) {
fileNames = fileNames.filter((name) => {
return !isChildOfDir(destDir, path.join(srcDir, name))
})
removeFromArray(fileNames, path.basename(destDir))
}
try {
await moveFiles(
fileNames.map((fileName) => path.join(srcDir, fileName)),
destDir,
)
} catch (error) {
console.error(error)
}
}
export async function extractZip(source: string, dest: string): Promise<void> {
return new Promise((resolve, reject) => {
yauzl.open(source, { lazyEntries: true, autoClose: true }, (err, zipFile) => {
let cancelled = false
const tryReject = (err: Error) => {
if (!cancelled) {
cancelled = true
reject(err)
}
}
if (err) {
return tryReject(err)
}
if (!zipFile) {
return tryReject(new Error('zipFile === undefined'))
}
zipFile.readEntry()
zipFile.on('close', resolve)
zipFile.on('entry', (entry) => {
if (cancelled) {
return
}
const isEntryDirectory = entry.fileName.endsWith('/')
if (isEntryDirectory) {
zipFile.readEntry()
return
}
zipFile.openReadStream(entry, async (err, stream) => {
if (cancelled) {
return
}
if (err) {
return tryReject(err)
}
if (!stream) {
return tryReject(new Error('stream === undefined'))
}
stream.on('error', tryReject)
const filepath = path.join(dest, entry.fileName)
try {
await ensureDirectoryExists(path.dirname(filepath))
} catch (error: any) {
return tryReject(error)
}
const writeStream = fs.createWriteStream(filepath).on('error', tryReject).on('error', tryReject)
stream.pipe(writeStream).on('close', () => {
zipFile.readEntry()
})
})
})
})
})
}
export async function moveFiles(sources: string[], destDir: string): Promise<void[]> {
await ensureDirectoryExists(destDir)
return Promise.all(sources.map((fileName) => moveFile(fileName, path.join(destDir, path.basename(fileName)))))
}
export async function moveFile(source: PathLike, destination: PathLike) {
try {
await fs.promises.rename(source, destination)
} catch (_error) {
/** Fall back to copying and then deleting. */
await fs.promises.copyFile(source, destination, fs.constants.COPYFILE_FICLONE_FORCE)
await fs.promises.unlink(source)
}
}
export async function deleteFileIfExists(filePath: PathLike): Promise<void> {
try {
await deleteFile(filePath)
} catch {
return
}
}
/** Deletes a file, handling EPERM and EBUSY errors on Windows. */
export async function deleteFile(filePath: PathLike): Promise<void> {
for (let i = 1, maxTries = 10; i < maxTries; i++) {
try {
await fs.promises.unlink(filePath)
break
} catch (error: any) {
if (error.code === OperationNotPermitted || error.code === DeviceIsBusy) {
await new Promise((resolve) => setTimeout(resolve, 300))
continue
} else if (error.code === FileDoesNotExist) {
/** Already deleted */
break
}
throw error
}
}
}
@@ -24,6 +24,9 @@ import { checkForUpdate, setupUpdates } from './UpdateManager'
import { handleTestMessage, send } from './Utils/Testing'
import { isTesting, lowercaseDriveLetter } from './Utils/Utils'
import { initializeZoomManager } from './ZoomManager'
import { HomeServerManager } from './HomeServer/HomeServerManager'
import { FilesManager } from './File/FilesManager'
import { DirectoryManager } from './Directory/DirectoryManager'
const WINDOW_DEFAULT_WIDTH = 1100
const WINDOW_DEFAULT_HEIGHT = 800
@@ -58,6 +61,7 @@ export async function createWindowState({
const services = await createWindowServices(window, appState, appLocale)
require('@electron/remote/main').enable(window.webContents)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(global as any).RemoteBridge = new RemoteBridge(
window,
Keychain,
@@ -72,6 +76,8 @@ export async function createWindowState({
services.menuManager,
services.fileBackupsManager,
services.mediaManager,
services.homeServerManager,
services.directoryManager,
)
const shouldOpenUrl = (url: string) => url.startsWith('http') || url.startsWith('mailto')
@@ -201,6 +207,11 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
const spellcheckerManager = createSpellcheckerManager(appState.store, window.webContents, appLocale)
const mediaManager = new MediaManager()
const filesManager = new FilesManager()
const directoryManager = new DirectoryManager(filesManager)
const homeServerManager = new HomeServerManager(window.webContents, filesManager)
if (isTesting()) {
handleTestMessage(MessageType.SpellCheckerManager, () => spellcheckerManager)
}
@@ -213,7 +224,7 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
spellcheckerManager,
})
const fileBackupsManager = new FilesBackupManager(appState, window.webContents)
const fileBackupsManager = new FilesBackupManager(appState, window.webContents, filesManager)
return {
updateManager,
@@ -224,6 +235,8 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
searchManager,
fileBackupsManager,
mediaManager,
homeServerManager,
directoryManager,
}
}
@@ -1,7 +1,11 @@
import { FileBackupsDevice } from '@web/Application/Device/DesktopSnjsExports'
import {
DirectoryManagerInterface,
FileBackupsDevice,
HomeServerManagerInterface,
} from '@web/Application/Device/DesktopSnjsExports'
import { Component } from '../Main/Packages/PackageManagerInterface'
export interface CrossProcessBridge extends FileBackupsDevice {
export interface CrossProcessBridge extends FileBackupsDevice, DirectoryManagerInterface, HomeServerManagerInterface {
get extServerHost(): string
get useNativeKeychain(): boolean
get rendererPath(): string
@@ -25,6 +25,46 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
super(appVersion)
}
async getHomeServerUrl(): Promise<string | undefined> {
return this.remoteBridge.getHomeServerUrl()
}
async getHomeServerLastErrorMessage(): Promise<string | undefined> {
return this.remoteBridge.getHomeServerLastErrorMessage()
}
async isHomeServerRunning(): Promise<boolean> {
return this.remoteBridge.isHomeServerRunning()
}
async activatePremiumFeatures(username: string): Promise<string | undefined> {
return this.remoteBridge.activatePremiumFeatures(username)
}
async setHomeServerConfiguration(configurationJSONString: string): Promise<void> {
return this.remoteBridge.setHomeServerConfiguration(configurationJSONString)
}
async getHomeServerConfiguration(): Promise<string | undefined> {
return this.remoteBridge.getHomeServerConfiguration()
}
async setHomeServerDataLocation(location: string): Promise<void> {
return this.remoteBridge.setHomeServerDataLocation(location)
}
startHomeServer(): Promise<string | undefined> {
return this.remoteBridge.startHomeServer()
}
stopHomeServer(): Promise<string | undefined> {
return this.remoteBridge.stopHomeServer()
}
getHomeServerLogs(): Promise<string[]> {
return this.remoteBridge.getHomeServerLogs()
}
openLocation(path: string): Promise<void> {
return this.remoteBridge.openLocation(path)
}
@@ -36,6 +76,10 @@ export class DesktopDevice extends WebOrDesktopDevice implements DesktopDeviceIn
return this.remoteBridge.presentDirectoryPickerForLocationChangeAndTransferOld(appendPath, oldLocation)
}
getDirectoryManagerLastErrorMessage(): Promise<string | undefined> {
return this.remoteBridge.getDirectoryManagerLastErrorMessage()
}
getFilesBackupsMappingFile(location: string): Promise<FileBackupsMapping> {
return this.remoteBridge.getFilesBackupsMappingFile(location)
}
@@ -19,6 +19,11 @@ process.once('loaded', function () {
setInstallComponentCompleteHandler: (handler: MainEventHandler) =>
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, handler),
setHomeServerStartedHandler: (handler: MainEventHandler) =>
ipcRenderer.on(MessageToWebApp.HomeServerStarted, handler),
setConsoleLogHandler: (handler: MainEventHandler) => ipcRenderer.on(MessageToWebApp.ConsoleLog, handler),
}
contextBridge.exposeInMainWorld('electronMainEvents', mainEvents)
@@ -54,6 +54,12 @@ window.onload = () => {
void loadAndStartApplication()
}
window.onunload = () => {
if (window.device) {
void window.device.stopHomeServer()
}
}
/** @returns whether the keychain structure is up to date or not */
async function migrateKeychain(remoteBridge: CrossProcessBridge): Promise<boolean> {
if (!remoteBridge.useNativeKeychain) {
@@ -122,7 +128,7 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
/* Use custom title bar. Take the sn-titlebar-height off of
the app content height so its not overflowing */
sheet.insertRule(
'[role="dialog"] { position: relative; height: calc(100vh - var(--sn-desktop-titlebar-height)) !important; margin-top: var(--sn-desktop-titlebar-height) !important; }',
'[role="dialog"] { height: calc(100vh - var(--sn-desktop-titlebar-height)) !important; top: var(--sn-desktop-titlebar-height); }',
sheet.cssRules.length,
)
sheet.insertRule(
@@ -144,10 +150,19 @@ window.electronMainEvents.setWindowFocusedHandler(() => {
window.webClient.windowGainedFocus()
})
window.electronMainEvents.setInstallComponentCompleteHandler((_: IpcRendererEvent, data: any) => {
void window.webClient.onComponentInstallationComplete(data.component, undefined)
window.electronMainEvents.setConsoleLogHandler((_: IpcRendererEvent, message: unknown) => {
window.webClient.consoleLog(message as string)
})
window.electronMainEvents.setInstallComponentCompleteHandler((_: IpcRendererEvent, data: unknown) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
void window.webClient.onComponentInstallationComplete((data as any).component, undefined)
})
window.electronMainEvents.setWatchedDirectoriesChangeHandler((_: IpcRendererEvent, changes: unknown) => {
void window.webClient.handleWatchedDirectoriesChanges(changes as DesktopWatchedDirectoriesChanges)
})
window.electronMainEvents.setHomeServerStartedHandler((_: IpcRendererEvent, serverUrl: unknown) => {
void window.webClient.handleHomeServerStarted(serverUrl as string)
})
@@ -8,4 +8,6 @@ export interface ElectronMainEvents {
setWindowFocusedHandler(handler: MainEventHandler): void
setInstallComponentCompleteHandler(handler: MainEventHandler): void
setWatchedDirectoriesChangeHandler(handler: MainEventHandler): void
setHomeServerStartedHandler(handler: MainEventHandler): void
setConsoleLogHandler(handler: MainEventHandler): void
}
@@ -4,6 +4,8 @@ export enum MessageToWebApp {
WindowFocused = 'window-focused',
InstallComponentComplete = 'install-component-complete',
WatchedDirectoriesChanges = 'watched-directories-changes',
HomeServerStarted = 'home-server-started',
ConsoleLog = 'console-log',
}
export enum MessageToMainProcess {
@@ -67,6 +67,7 @@ module.exports = function ({ onlyTranspileTypescript = false, experimentalFeatur
module: moduleConfig,
externals: {
keytar: 'commonjs keytar',
"@standardnotes/home-server": "commonjs @standardnotes/home-server",
},
optimization: {
minimizer: [
+6 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.106.67",
"version": "3.108.15",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
@@ -35,6 +35,7 @@
},
"dependencies": {
"@electron/remote": "^2.0.9",
"@standardnotes/domain-core": "^1.22.0",
"@standardnotes/electron-clear-data": "1.1.1",
"@standardnotes/web": "workspace:*",
"axios": "^1.1.3",
@@ -44,7 +45,7 @@
"electron": "22.1.0",
"electron-log": "^4.4.8",
"electron-updater": "^5.3.0",
"fs-extra": "^10.1.0",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"mobx": "^6.7.0"
@@ -52,6 +53,7 @@
"devDependencies": {
"@babel/core": "*",
"@babel/preset-env": "*",
"@types/fs-extra": "^11.0.1",
"@types/lodash": "^4.14.189",
"@types/mime-types": "^2.1.1",
"@types/node": "18",
@@ -107,7 +109,8 @@
"NSCameraUsageDescription": "Standard Notes requires access to your camera to enable the Moments feature."
},
"asarUnpack": [
"node_modules/keytar"
"node_modules/keytar",
"node_modules/@standardnotes/home-server"
],
"target": [
"dmg",
+1
View File
@@ -2,6 +2,7 @@
"compilerOptions": {
"target": "ES2019",
"moduleResolution": "node",
"module": "es2020",
"types": ["node"],
"allowJs": true,
"noEmit": false,
+20
View File
@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.49](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-12)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.48](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-06)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.47](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-05)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.46](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-04)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.45](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-03)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.44](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-06-30)
**Note:** Version bump only for package @standardnotes/encryption
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.21.44",
"version": "1.21.49",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -28,7 +28,8 @@
"typescript": "*"
},
"dependencies": {
"@standardnotes/common": "^1.48.3",
"@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.22.0",
"@standardnotes/models": "workspace:*",
"@standardnotes/responses": "workspace:*",
"@standardnotes/sncrypto-common": "workspace:*",
@@ -1,4 +1,5 @@
import { ContentType, ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/common'
import { ContentType } from '@standardnotes/domain-core'
import {
ConflictStrategy,
DecryptedItem,
@@ -10,7 +11,7 @@ import {
} from '@standardnotes/models'
export function isItemsKey(x: unknown): x is ItemsKeyInterface {
return (x as ItemsKeyInterface).content_type === ContentType.ItemsKey
return (x as ItemsKeyInterface).content_type === ContentType.TYPES.ItemsKey
}
/**
@@ -1,6 +1,10 @@
import { ContentType } from '@standardnotes/common'
import { DecryptedItemMutator, ItemsKeyContent, RegisterItemClass } from '@standardnotes/models'
import { ContentType } from '@standardnotes/domain-core'
import { SNItemsKey } from './ItemsKey'
import { ItemsKeyMutator } from './ItemsKeyMutator'
RegisterItemClass(ContentType.ItemsKey, SNItemsKey, ItemsKeyMutator as unknown as DecryptedItemMutator<ItemsKeyContent>)
RegisterItemClass(
ContentType.TYPES.ItemsKey,
SNItemsKey,
ItemsKeyMutator as unknown as DecryptedItemMutator<ItemsKeyContent>,
)
@@ -1,4 +1,5 @@
import { ContentType, ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/common'
import { ContentType } from '@standardnotes/domain-core'
import {
ConflictStrategy,
DecryptedItem,
@@ -10,7 +11,7 @@ import {
} from '@standardnotes/models'
export function isKeySystemItemsKey(x: unknown): x is KeySystemItemsKeyInterface {
return (x as KeySystemItemsKeyInterface).content_type === ContentType.KeySystemItemsKey
return (x as KeySystemItemsKeyInterface).content_type === ContentType.TYPES.KeySystemItemsKey
}
/**
@@ -1,10 +1,11 @@
import { ContentType } from '@standardnotes/common'
import { DecryptedItemMutator, KeySystemItemsKeyContent, RegisterItemClass } from '@standardnotes/models'
import { ContentType } from '@standardnotes/domain-core'
import { KeySystemItemsKey } from './KeySystemItemsKey'
import { KeySystemItemsKeyMutator } from './KeySystemItemsKeyMutator'
RegisterItemClass(
ContentType.KeySystemItemsKey,
ContentType.TYPES.KeySystemItemsKey,
KeySystemItemsKey,
KeySystemItemsKeyMutator as unknown as DecryptedItemMutator<KeySystemItemsKeyContent>,
)
@@ -1,4 +1,4 @@
import { ContentType, ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/common'
import {
DecryptedPayload,
FillItemContentSpecialized,
@@ -9,13 +9,14 @@ import {
} from '@standardnotes/models'
import { UuidGenerator } from '@standardnotes/utils'
import { SNRootKey } from './RootKey'
import { ContentType } from '@standardnotes/domain-core'
export function CreateNewRootKey<K extends RootKeyInterface>(content: RootKeyContentSpecialized): K {
const uuid = UuidGenerator.GenerateUuid()
const payload = new DecryptedPayload<RootKeyContent>({
uuid: uuid,
content_type: ContentType.RootKey,
content_type: ContentType.TYPES.RootKey,
content: FillRootKeyContent(content),
...PayloadTimestampDefaults(),
})
@@ -1,4 +1,4 @@
import { ContentType, KeyParamsOrigination, ProtocolVersion, ProtocolVersionLength } from '@standardnotes/common'
import { KeyParamsOrigination, ProtocolVersion, ProtocolVersionLength } from '@standardnotes/common'
import {
CreateDecryptedItemFromPayload,
DecryptedPayload,
@@ -32,6 +32,7 @@ import { PublicKeySet } from '../Types/PublicKeySet'
import { AsymmetricDecryptResult } from '../Types/AsymmetricDecryptResult'
import { AsymmetricSignatureVerificationDetachedResult } from '../Types/AsymmetricSignatureVerificationDetachedResult'
import { AsyncOperatorInterface } from '../OperatorInterface/AsyncOperatorInterface'
import { ContentType } from '@standardnotes/domain-core'
const NO_IV = '00000000000000000000000000000000'
@@ -71,7 +72,7 @@ export class SNProtocolOperator001 implements OperatorInterface, AsyncOperatorIn
public createItemsKey(): ItemsKeyInterface {
const payload = new DecryptedPayload({
uuid: UuidGenerator.GenerateUuid(),
content_type: ContentType.ItemsKey,
content_type: ContentType.TYPES.ItemsKey,
content: this.generateNewItemsKeyContent(),
...PayloadTimestampDefaults(),
})
@@ -1,5 +1,6 @@
import * as Common from '@standardnotes/common'
import * as Models from '@standardnotes/models'
import { ContentType } from '@standardnotes/domain-core'
import { ItemContent, PayloadTimestampDefaults } from '@standardnotes/models'
import * as Utils from '@standardnotes/utils'
import { UuidGenerator } from '@standardnotes/utils'
@@ -44,7 +45,7 @@ export class SNProtocolOperator002 extends SNProtocolOperator001 {
public override createItemsKey(): Models.ItemsKeyInterface {
const payload = new Models.DecryptedPayload({
uuid: UuidGenerator.GenerateUuid(),
content_type: Common.ContentType.ItemsKey,
content_type: ContentType.TYPES.ItemsKey,
content: this.generateNewItemsKeyContent(),
...PayloadTimestampDefaults(),
})
@@ -1,4 +1,4 @@
import { ContentType, KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common'
import { KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common'
import {
CreateDecryptedItemFromPayload,
DecryptedPayload,
@@ -14,6 +14,7 @@ import { CreateNewRootKey } from '../../Keys/RootKey/Functions'
import { Create003KeyParams } from '../../Keys/RootKey/KeyParamsFunctions'
import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams'
import { SNProtocolOperator002 } from '../002/Operator002'
import { ContentType } from '@standardnotes/domain-core'
/**
* @legacy
@@ -46,7 +47,7 @@ export class SNProtocolOperator003 extends SNProtocolOperator002 {
const content = this.generateNewItemsKeyContent()
const payload = new DecryptedPayload({
uuid: UuidGenerator.GenerateUuid(),
content_type: ContentType.ItemsKey,
content_type: ContentType.TYPES.ItemsKey,
content: FillItemContent(content),
...PayloadTimestampDefaults(),
})
@@ -1,9 +1,10 @@
import { ContentType, ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/common'
import { DecryptedPayload, ItemContent, ItemsKeyContent, PayloadTimestampDefaults } from '@standardnotes/models'
import { SNItemsKey } from '../../Keys/ItemsKey/ItemsKey'
import { SNProtocolOperator004 } from './Operator004'
import { getMockedCrypto } from './MockedCrypto'
import { deconstructEncryptedPayloadString } from './V004AlgorithmHelpers'
import { ContentType } from '@standardnotes/domain-core'
describe('operator 004', () => {
const crypto = getMockedCrypto()
@@ -31,7 +32,7 @@ describe('operator 004', () => {
it('should generateEncryptedParameters', () => {
const payload = {
uuid: '123',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
content: { foo: 'bar' } as unknown as jest.Mocked<ItemContent>,
...PayloadTimestampDefaults(),
} as jest.Mocked<DecryptedPayload>
@@ -39,7 +40,7 @@ describe('operator 004', () => {
const key = new SNItemsKey(
new DecryptedPayload<ItemsKeyContent>({
uuid: 'key-456',
content_type: ContentType.ItemsKey,
content_type: ContentType.TYPES.ItemsKey,
content: {
itemsKey: 'secret',
version: ProtocolVersion.V004,
@@ -56,7 +57,7 @@ describe('operator 004', () => {
key_system_identifier: undefined,
shared_vault_uuid: undefined,
content: '004:random-string:<e>{"foo"|"bar"}<e>:base64-{"u"|"123","v"|"004"}:base64-{}',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
enc_item_key: '004:random-string:<e>random-string<e>:base64-{"u"|"123","v"|"004"}:base64-{}',
version: '004',
})
@@ -13,7 +13,7 @@ import {
RootKeyInterface,
KeySystemRootKeyParamsInterface,
} from '@standardnotes/models'
import { ContentType, KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common'
import { KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common'
import { HexString, PkcKeyPair, PureCryptoInterface, Utf8String } from '@standardnotes/sncrypto-common'
import { V004Algorithm } from '../../Algorithm'
import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams'
@@ -48,6 +48,7 @@ import { AsymmetricSignatureVerificationDetachedResult } from '../Types/Asymmetr
import { AsymmetricSignatureVerificationDetachedUseCase } from './UseCase/Asymmetric/AsymmetricSignatureVerificationDetached'
import { DeriveKeySystemRootKeyUseCase } from './UseCase/KeySystem/DeriveKeySystemRootKey'
import { SyncOperatorInterface } from '../OperatorInterface/SyncOperatorInterface'
import { ContentType } from '@standardnotes/domain-core'
export class SNProtocolOperator004 implements OperatorInterface, SyncOperatorInterface {
constructor(protected readonly crypto: PureCryptoInterface) {}
@@ -76,7 +77,7 @@ export class SNProtocolOperator004 implements OperatorInterface, SyncOperatorInt
public createItemsKey(): ItemsKeyInterface {
const payload = new DecryptedPayload({
uuid: UuidGenerator.GenerateUuid(),
content_type: ContentType.ItemsKey,
content_type: ContentType.TYPES.ItemsKey,
content: this.generateNewItemsKeyContent(),
key_system_identifier: undefined,
shared_vault_uuid: undefined,
@@ -10,7 +10,8 @@ import {
} from '@standardnotes/models'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { V004Algorithm } from '../../../../Algorithm'
import { ContentType, ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/common'
import { ContentType } from '@standardnotes/domain-core'
export class CreateKeySystemItemsKeyUseCase {
constructor(private readonly crypto: PureCryptoInterface) {}
@@ -31,7 +32,7 @@ export class CreateKeySystemItemsKeyUseCase {
const transferPayload: DecryptedTransferPayload = {
uuid: dto.uuid,
content_type: ContentType.KeySystemItemsKey,
content_type: ContentType.TYPES.KeySystemItemsKey,
key_system_identifier: dto.keySystemIdentifier,
shared_vault_uuid: dto.sharedVaultUuid,
content: content,
@@ -12,7 +12,8 @@ import {
PayloadTimestampDefaults,
KeySystemRootKeyParamsInterface,
} from '@standardnotes/models'
import { ContentType, ProtocolVersion } from '@standardnotes/common'
import { ProtocolVersion } from '@standardnotes/common'
import { ContentType } from '@standardnotes/domain-core'
export class DeriveKeySystemRootKeyUseCase {
constructor(private readonly crypto: PureCryptoInterface) {}
@@ -44,7 +45,7 @@ export class DeriveKeySystemRootKeyUseCase {
const payload = new DecryptedPayload<KeySystemRootKeyContent>({
uuid: uuid,
content_type: ContentType.KeySystemRootKey,
content_type: ContentType.TYPES.KeySystemRootKey,
content: FillItemContentSpecialized(content),
...PayloadTimestampDefaults(),
})
@@ -1,5 +1,5 @@
import { CreateAnyKeyParams } from '../../../../Keys/RootKey/KeyParamsFunctions'
import { AnyKeyParamsContent, ContentType, ProtocolVersion } from '@standardnotes/common'
import { AnyKeyParamsContent, ProtocolVersion } from '@standardnotes/common'
import { GenerateAuthenticatedDataUseCase } from './GenerateAuthenticatedData'
import {
DecryptedPayloadInterface,
@@ -8,6 +8,7 @@ import {
RootKeyInterface,
} from '@standardnotes/models'
import { KeySystemItemsKey } from '../../../../Keys/KeySystemItemsKey/KeySystemItemsKey'
import { ContentType } from '@standardnotes/domain-core'
describe('generate authenticated data use case', () => {
let usecase: GenerateAuthenticatedDataUseCase
@@ -19,7 +20,7 @@ describe('generate authenticated data use case', () => {
it('should include key params if payload being encrypted is an items key', () => {
const payload = {
uuid: '123',
content_type: ContentType.ItemsKey,
content_type: ContentType.TYPES.ItemsKey,
} as jest.Mocked<DecryptedPayloadInterface>
const keyParams = CreateAnyKeyParams({
@@ -42,7 +43,7 @@ describe('generate authenticated data use case', () => {
it('should include root key params if payload is a key system items key', () => {
const payload = {
uuid: '123',
content_type: ContentType.KeySystemItemsKey,
content_type: ContentType.TYPES.KeySystemItemsKey,
shared_vault_uuid: 'shared-vault-uuid-123',
key_system_identifier: 'key-system-identifier-123',
} as jest.Mocked<DecryptedPayloadInterface>
@@ -52,7 +53,7 @@ describe('generate authenticated data use case', () => {
keyParams: {
seed: 'seed-123',
},
content_type: ContentType.KeySystemRootKey,
content_type: ContentType.TYPES.KeySystemRootKey,
token: '123',
} as jest.Mocked<KeySystemRootKeyInterface>
@@ -70,7 +71,7 @@ describe('generate authenticated data use case', () => {
it('should include key system identifier and shared vault uuid', () => {
const payload = {
uuid: '123',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
shared_vault_uuid: 'shared-vault-uuid-123',
key_system_identifier: 'key-system-identifier-123',
} as jest.Mocked<DecryptedPayloadInterface>
@@ -78,7 +79,7 @@ describe('generate authenticated data use case', () => {
const itemsKey = {
creationTimestamp: 123,
keyVersion: ProtocolVersion.V004,
content_type: ContentType.KeySystemItemsKey,
content_type: ContentType.TYPES.KeySystemItemsKey,
} as jest.Mocked<KeySystemItemsKey>
const authenticatedData = usecase.execute(payload, itemsKey)
@@ -94,11 +95,11 @@ describe('generate authenticated data use case', () => {
it('should include only uuid and version if non-keysystem item with items key', () => {
const payload = {
uuid: '123',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
} as jest.Mocked<DecryptedPayloadInterface>
const itemsKey = {
content_type: ContentType.ItemsKey,
content_type: ContentType.TYPES.ItemsKey,
} as jest.Mocked<ItemsKeyInterface>
const authenticatedData = usecase.execute(payload, itemsKey)
@@ -1,10 +1,10 @@
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { getMockedCrypto } from '../../MockedCrypto'
import { GenerateDecryptedParametersUseCase } from './GenerateDecryptedParameters'
import { ContentType } from '@standardnotes/common'
import { DecryptedPayloadInterface, ItemsKeyInterface } from '@standardnotes/models'
import { GenerateEncryptedParametersUseCase } from './GenerateEncryptedParameters'
import { EncryptedInputParameters, EncryptedOutputParameters } from '../../../../Types/EncryptedParameters'
import { ContentType } from '@standardnotes/domain-core'
describe('generate decrypted parameters usecase', () => {
let crypto: PureCryptoInterface
@@ -18,7 +18,7 @@ describe('generate decrypted parameters usecase', () => {
itemsKey = {
uuid: 'items-key-id',
itemsKey: 'items-key',
content_type: ContentType.ItemsKey,
content_type: ContentType.TYPES.ItemsKey,
} as jest.Mocked<ItemsKeyInterface>
})
@@ -28,7 +28,7 @@ describe('generate decrypted parameters usecase', () => {
content: {
text: plaintext,
},
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
} as unknown as jest.Mocked<DecryptedPayloadInterface>
const encryptedParametersUsecase = new GenerateEncryptedParametersUseCase(crypto)

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