mirror of
https://github.com/standardnotes/app
synced 2026-09-13 18:46:08 -04:00
Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8863ee82bb | ||
|
|
6a4b5c5cdc | ||
|
|
cd1669f56f | ||
|
|
037007f0ec | ||
|
|
e7ca12babe | ||
|
|
c4c33db1a4 | ||
|
|
734b986294 | ||
|
|
59db63f052 | ||
|
|
20226c3269 | ||
|
|
a1352d9f65 | ||
|
|
7d21046b51 | ||
|
|
a365c17c46 | ||
|
|
c1981334e1 | ||
|
|
1e3acd50e9 | ||
|
|
8de6023848 | ||
|
|
01ba715eba | ||
|
|
3733707bf1 | ||
|
|
cbbe913cd6 | ||
|
|
c13dd883a4 | ||
|
|
1377846f3f | ||
|
|
f9ee197f04 | ||
|
|
c944eb9365 | ||
|
|
4609053bd0 | ||
|
|
1c10214104 | ||
|
|
1602d8157f | ||
|
|
ed1d583476 | ||
|
|
05aff2776b |
@@ -48,16 +48,6 @@ jobs:
|
||||
uses: martinbeentjes/npm-get-version-action@main
|
||||
with:
|
||||
path: packages/mobile
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
tag_name: "@standardnotes/mobile@${{ steps.package-version.outputs.current-version}}"
|
||||
prerelease: true
|
||||
draft: true
|
||||
name: "Mobile Dev ${{ steps.package-version.outputs.current-version }}"
|
||||
files: |
|
||||
packages/mobile/android/app/build/outputs/apk/dev/release/app-dev-release.apk
|
||||
ios:
|
||||
defaults:
|
||||
run:
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.11.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add workspaces api ([#1765](https://github.com/standardnotes/app/issues/1765)) ([01ba715](https://github.com/standardnotes/app/commit/01ba715eba987a7da1ee062fec0b3593a7a453ed))
|
||||
|
||||
# [1.10.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
* experimental 005 operator ([#1753](https://github.com/standardnotes/app/issues/1753)) ([cbbe913](https://github.com/standardnotes/app/commit/cbbe913cd6eb694dd27997927bd5c45e8a64cc09))
|
||||
|
||||
## [1.9.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -10,10 +10,10 @@ module.exports = {
|
||||
},
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 20,
|
||||
functions: 66,
|
||||
lines: 63,
|
||||
statements: 63
|
||||
branches: 22,
|
||||
functions: 69,
|
||||
lines: 67,
|
||||
statements: 67
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.9.2",
|
||||
"version": "1.11.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { RootKeyParamsInterface } from '@standardnotes/models'
|
||||
|
||||
import { ErrorMessage } from '../../Error/ErrorMessage'
|
||||
import { ApiCallError } from '../../Error/ApiCallError'
|
||||
import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse'
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum WorkspaceApiOperations {
|
||||
Creating,
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServerInterface'
|
||||
|
||||
import { WorkspaceApiOperations } from './WorkspaceApiOperations'
|
||||
import { WorkspaceApiService } from './WorkspaceApiService'
|
||||
|
||||
describe('WorkspaceApiService', () => {
|
||||
let workspaceServer: WorkspaceServerInterface
|
||||
|
||||
const createService = () => new WorkspaceApiService(workspaceServer)
|
||||
|
||||
beforeEach(() => {
|
||||
workspaceServer = {} as jest.Mocked<WorkspaceServerInterface>
|
||||
workspaceServer.createWorkspace = jest.fn().mockReturnValue({
|
||||
data: { uuid: '1-2-3' },
|
||||
} as jest.Mocked<WorkspaceCreationResponse>)
|
||||
})
|
||||
|
||||
it('should create a workspace', async () => {
|
||||
const response = await createService().createWorkspace({
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
})
|
||||
expect(workspaceServer.createWorkspace).toHaveBeenCalledWith({
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not create a workspace if it is already creating', async () => {
|
||||
const service = createService()
|
||||
Object.defineProperty(service, 'operationsInProgress', {
|
||||
get: () => new Map([[WorkspaceApiOperations.Creating, true]]),
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await service.createWorkspace({
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not create a workspace if the server fails', async () => {
|
||||
workspaceServer.createWorkspace = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
let error = null
|
||||
try {
|
||||
await createService().createWorkspace({
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
})
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ErrorMessage } from '../../Error/ErrorMessage'
|
||||
import { ApiCallError } from '../../Error/ApiCallError'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
import { WorkspaceServerInterface } from '../../Server/Workspace/WorkspaceServerInterface'
|
||||
|
||||
import { WorkspaceApiServiceInterface } from './WorkspaceApiServiceInterface'
|
||||
import { WorkspaceApiOperations } from './WorkspaceApiOperations'
|
||||
|
||||
export class WorkspaceApiService implements WorkspaceApiServiceInterface {
|
||||
private operationsInProgress: Map<WorkspaceApiOperations, boolean>
|
||||
|
||||
constructor(private workspaceServer: WorkspaceServerInterface) {
|
||||
this.operationsInProgress = new Map()
|
||||
}
|
||||
|
||||
async createWorkspace(dto: {
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
publicKey: string
|
||||
workspaceName?: string
|
||||
}): Promise<WorkspaceCreationResponse> {
|
||||
if (this.operationsInProgress.get(WorkspaceApiOperations.Creating)) {
|
||||
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||
}
|
||||
|
||||
this.operationsInProgress.set(WorkspaceApiOperations.Creating, true)
|
||||
|
||||
try {
|
||||
const response = await this.workspaceServer.createWorkspace({
|
||||
encryptedPrivateKey: dto.encryptedPrivateKey,
|
||||
encryptedWorkspaceKey: dto.encryptedWorkspaceKey,
|
||||
publicKey: dto.publicKey,
|
||||
workspaceName: dto.workspaceName,
|
||||
})
|
||||
|
||||
this.operationsInProgress.set(WorkspaceApiOperations.Creating, false)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { WorkspaceCreationResponse } from '../../Response'
|
||||
|
||||
export interface WorkspaceApiServiceInterface {
|
||||
createWorkspace(dto: {
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
publicKey: string
|
||||
workspaceName?: string
|
||||
}): Promise<WorkspaceCreationResponse>
|
||||
}
|
||||
@@ -5,3 +5,5 @@ export * from './User/UserApiService'
|
||||
export * from './User/UserApiServiceInterface'
|
||||
export * from './WebSocket/WebSocketApiService'
|
||||
export * from './WebSocket/WebSocketApiServiceInterface'
|
||||
export * from './Workspace/WorkspaceApiService'
|
||||
export * from './Workspace/WorkspaceApiServiceInterface'
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export type WorkspaceCreationRequestParams = {
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
publicKey: string
|
||||
workspaceName?: string
|
||||
[additionalParam: string]: unknown
|
||||
}
|
||||
@@ -6,3 +6,4 @@ export * from './Subscription/SubscriptionInviteListRequestParams'
|
||||
export * from './Subscription/SubscriptionInviteRequestParams'
|
||||
export * from './User/UserRegistrationRequestParams'
|
||||
export * from './WebSocket/WebSocketConnectionTokenRequestParams'
|
||||
export * from './Workspace/WorkspaceCreationRequestParams'
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
import { WorkspaceCreationResponseBody } from './WorkspaceCreationResponseBody'
|
||||
|
||||
export interface WorkspaceCreationResponse extends HttpResponse {
|
||||
data: Either<WorkspaceCreationResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type WorkspaceCreationResponseBody = {
|
||||
uuid: string
|
||||
}
|
||||
@@ -12,3 +12,5 @@ export * from './User/UserRegistrationResponse'
|
||||
export * from './User/UserRegistrationResponseBody'
|
||||
export * from './WebSocket/WebSocketConnectionTokenResponse'
|
||||
export * from './WebSocket/WebSocketConnectionTokenResponseBody'
|
||||
export * from './Workspace/WorkspaceCreationResponse'
|
||||
export * from './Workspace/WorkspaceCreationResponseBody'
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const WorkspacePaths = {
|
||||
createWorkspace: '/v1/workspaces',
|
||||
}
|
||||
|
||||
export const Paths = {
|
||||
v1: {
|
||||
...WorkspacePaths,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { HttpServiceInterface } from '../../Http'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
|
||||
import { WorkspaceServer } from './WorkspaceServer'
|
||||
|
||||
describe('WorkspaceServer', () => {
|
||||
let httpService: HttpServiceInterface
|
||||
|
||||
const createServer = () => new WorkspaceServer(httpService)
|
||||
|
||||
beforeEach(() => {
|
||||
httpService = {} as jest.Mocked<HttpServiceInterface>
|
||||
httpService.post = jest.fn().mockReturnValue({
|
||||
data: { uuid: '1-2-3' },
|
||||
} as jest.Mocked<WorkspaceCreationResponse>)
|
||||
})
|
||||
|
||||
it('should create a workspace', async () => {
|
||||
const response = await createServer().createWorkspace({
|
||||
encryptedPrivateKey: 'foo',
|
||||
encryptedWorkspaceKey: 'bar',
|
||||
publicKey: 'buzz',
|
||||
})
|
||||
|
||||
expect(response).toEqual({
|
||||
data: {
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
import { HttpServiceInterface } from '../../Http/HttpServiceInterface'
|
||||
import { WorkspaceCreationRequestParams } from '../../Request/Workspace/WorkspaceCreationRequestParams'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
|
||||
import { Paths } from './Paths'
|
||||
import { WorkspaceServerInterface } from './WorkspaceServerInterface'
|
||||
|
||||
export class WorkspaceServer implements WorkspaceServerInterface {
|
||||
constructor(private httpService: HttpServiceInterface) {}
|
||||
|
||||
async createWorkspace(params: WorkspaceCreationRequestParams): Promise<WorkspaceCreationResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.createWorkspace, params)
|
||||
|
||||
return response as WorkspaceCreationResponse
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { WorkspaceCreationRequestParams } from '../../Request/Workspace/WorkspaceCreationRequestParams'
|
||||
import { WorkspaceCreationResponse } from '../../Response/Workspace/WorkspaceCreationResponse'
|
||||
|
||||
export interface WorkspaceServerInterface {
|
||||
createWorkspace(params: WorkspaceCreationRequestParams): Promise<WorkspaceCreationResponse>
|
||||
}
|
||||
@@ -4,3 +4,5 @@ export * from './User/UserServer'
|
||||
export * from './User/UserServerInterface'
|
||||
export * from './WebSocket/WebSocketServer'
|
||||
export * from './WebSocket/WebSocketServerInterface'
|
||||
export * from './Workspace/WorkspaceServer'
|
||||
export * from './Workspace/WorkspaceServerInterface'
|
||||
|
||||
@@ -3,6 +3,40 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.23.200](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.199](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.198](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **desktop:** use different method to send messages across main and render without node integration in preload ([#1769](https://github.com/standardnotes/app/issues/1769)) ([20226c3](https://github.com/standardnotes/app/commit/20226c326945ad9dc524f6dcbfc1c1274befb652))
|
||||
|
||||
## [3.23.197](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.196](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.195](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.194](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.193](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.23.192](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -5,6 +5,7 @@ import { autoUpdater } from 'electron-updater'
|
||||
import { action, autorun, computed, makeObservable, observable } from 'mobx'
|
||||
import { MessageType } from '../../../test/TestIpcMessage'
|
||||
import { AppState } from '../../AppState'
|
||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||
import { BackupsManagerInterface } from './Backups/BackupsManagerInterface'
|
||||
import { StoreKeys } from './Store/StoreKeys'
|
||||
import { updates as str } from './Strings'
|
||||
@@ -113,12 +114,12 @@ export function setupUpdates(window: BrowserWindow, appState: AppState, backupsM
|
||||
setInterval(checkUpdateSafety, oneHour)
|
||||
|
||||
autoUpdater.on('update-downloaded', (info: { version?: string }) => {
|
||||
window.webContents.send('update-available', null)
|
||||
window.webContents.send(MessageToWebApp.UpdateAvailable, null)
|
||||
updateState.autoUpdateHasBeenDownloaded(info.version || null)
|
||||
})
|
||||
|
||||
autoUpdater.on('error', logError)
|
||||
autoUpdater.on('update-available', (info: { version?: string }) => {
|
||||
autoUpdater.on(MessageToWebApp.UpdateAvailable, (info: { version?: string }) => {
|
||||
updateState.checkedForUpdate(info.version || null)
|
||||
if (updateState.enableAutoUpdate) {
|
||||
const canUpdate = checkUpdateSafety()
|
||||
|
||||
@@ -166,6 +166,7 @@ async function createWindow(store: Store): Promise<Electron.BrowserWindow> {
|
||||
spellcheck: true,
|
||||
nodeIntegration: isTesting(),
|
||||
contextIsolation: true,
|
||||
sandbox: true,
|
||||
preload: Paths.preloadJs,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,42 +1,28 @@
|
||||
import { IpcRendererEvent } from 'electron/renderer'
|
||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||
const { ipcRenderer } = require('electron')
|
||||
const path = require('path')
|
||||
const rendererPath = path.join('file://', __dirname, '/renderer.js')
|
||||
const RemoteBridge = require('@electron/remote').getGlobal('RemoteBridge')
|
||||
const { contextBridge } = require('electron')
|
||||
|
||||
type MainEventCallback = (event: IpcRendererEvent, value: any) => void
|
||||
|
||||
process.once('loaded', function () {
|
||||
contextBridge.exposeInMainWorld('electronRemoteBridge', RemoteBridge.exposableValue)
|
||||
|
||||
listenForIpcEventsFromMainProcess()
|
||||
contextBridge.exposeInMainWorld('electronMainEvents', {
|
||||
handleUpdateAvailable: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.UpdateAvailable, callback),
|
||||
|
||||
handlePerformAutomatedBackup: (callback: MainEventCallback) =>
|
||||
ipcRenderer.on(MessageToWebApp.PerformAutomatedBackup, callback),
|
||||
|
||||
handleFinishedSavingBackup: (callback: MainEventCallback) =>
|
||||
ipcRenderer.on(MessageToWebApp.FinishedSavingBackup, callback),
|
||||
|
||||
handleWindowBlurred: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.WindowBlurred, callback),
|
||||
|
||||
handleWindowFocused: (callback: MainEventCallback) => ipcRenderer.on(MessageToWebApp.WindowFocused, callback),
|
||||
|
||||
handleInstallComponentComplete: (callback: MainEventCallback) =>
|
||||
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, callback),
|
||||
})
|
||||
})
|
||||
|
||||
function listenForIpcEventsFromMainProcess() {
|
||||
const sendMessageToRenderProcess = (message: string, payload = {}) => {
|
||||
window.postMessage(JSON.stringify({ message, data: payload }), rendererPath)
|
||||
}
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.UpdateAvailable, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.UpdateAvailable, data)
|
||||
})
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.PerformAutomatedBackup, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.PerformAutomatedBackup, data)
|
||||
})
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.FinishedSavingBackup, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.FinishedSavingBackup, data)
|
||||
})
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.WindowBlurred, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.WindowBlurred, data)
|
||||
})
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.WindowFocused, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.WindowFocused, data)
|
||||
})
|
||||
|
||||
ipcRenderer.on(MessageToWebApp.InstallComponentComplete, function (_event, data) {
|
||||
sendMessageToRenderProcess(MessageToWebApp.InstallComponentComplete, data)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DesktopClientRequiresWebMethods } from '@web/Application/Device/DesktopSnjsExports'
|
||||
import { StartApplication } from '@web/Application/Device/StartApplication'
|
||||
import { MessageToWebApp } from '../Shared/IpcMessages'
|
||||
import { IpcRendererEvent } from 'electron/renderer'
|
||||
import { CrossProcessBridge } from './CrossProcessBridge'
|
||||
import { DesktopDevice } from './DesktopDevice'
|
||||
|
||||
@@ -23,6 +23,7 @@ declare global {
|
||||
purchaseUrl: string
|
||||
startApplication: StartApplication
|
||||
zip: any
|
||||
electronMainEvents: any
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +42,6 @@ const loadAndStartApplication = async () => {
|
||||
window.device = await createDesktopDevice(remoteBridge)
|
||||
|
||||
window.startApplication(DEFAULT_SYNC_SERVER, window.device, window.enableUnfinishedFeatures, WEBSOCKET_URL)
|
||||
|
||||
listenForMessagesSentFromMainToPreloadToUs(window.device)
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
@@ -134,35 +133,26 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
|
||||
}
|
||||
}
|
||||
|
||||
function listenForMessagesSentFromMainToPreloadToUs(device: DesktopDevice) {
|
||||
window.addEventListener('message', async (event) => {
|
||||
// We don't have access to the full file path.
|
||||
if (event.origin !== 'file://') {
|
||||
return
|
||||
}
|
||||
let payload
|
||||
try {
|
||||
payload = JSON.parse(event.data)
|
||||
} catch (e) {
|
||||
// message doesn't belong to us
|
||||
return
|
||||
}
|
||||
const receiver = window.webClient
|
||||
const message = payload.message
|
||||
const data = payload.data
|
||||
window.electronMainEvents.handleUpdateAvailable(() => {
|
||||
window.webClient.updateAvailable()
|
||||
})
|
||||
|
||||
if (message === MessageToWebApp.WindowBlurred) {
|
||||
receiver.windowLostFocus()
|
||||
} else if (message === MessageToWebApp.WindowFocused) {
|
||||
receiver.windowGainedFocus()
|
||||
} else if (message === MessageToWebApp.InstallComponentComplete) {
|
||||
receiver.onComponentInstallationComplete(data.component, undefined)
|
||||
} else if (message === MessageToWebApp.UpdateAvailable) {
|
||||
receiver.updateAvailable()
|
||||
} else if (message === MessageToWebApp.PerformAutomatedBackup) {
|
||||
void device.downloadBackup()
|
||||
} else if (message === MessageToWebApp.FinishedSavingBackup) {
|
||||
receiver.didFinishBackup(data.success)
|
||||
}
|
||||
})
|
||||
}
|
||||
window.electronMainEvents.handlePerformAutomatedBackup(() => {
|
||||
void window.device.downloadBackup()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleFinishedSavingBackup((_: IpcRendererEvent, data: any) => {
|
||||
window.webClient.didFinishBackup(data.success)
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleWindowBlurred(() => {
|
||||
window.webClient.windowLostFocus()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleWindowFocused(() => {
|
||||
window.webClient.windowGainedFocus()
|
||||
})
|
||||
|
||||
window.electronMainEvents.handleInstallComponentComplete((_: IpcRendererEvent, data: any) => {
|
||||
window.webClient.onComponentInstallationComplete(data.component, undefined)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.23.192",
|
||||
"version": "3.23.200",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.16.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
* experimental 005 operator ([#1753](https://github.com/standardnotes/app/issues/1753)) ([cbbe913](https://github.com/standardnotes/app/commit/cbbe913cd6eb694dd27997927bd5c45e8a64cc09))
|
||||
|
||||
## [1.15.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.15.11",
|
||||
"version": "1.16.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -44,3 +44,8 @@ export enum V004Algorithm {
|
||||
EncryptionKeyLength = 256,
|
||||
EncryptionNonceLength = 192,
|
||||
}
|
||||
|
||||
export enum V005Algorithm {
|
||||
AsymmetricEncryptionNonceLength = 192,
|
||||
SymmetricEncryptionNonceLength = 192,
|
||||
}
|
||||
|
||||
@@ -254,21 +254,21 @@ export class SNProtocolOperator004 implements SynchronousOperator {
|
||||
encrypted: EncryptedParameters,
|
||||
key: ItemsKeyInterface | SNRootKey,
|
||||
): DecryptedParameters<C> | ErrorDecryptingParameters {
|
||||
const itemKeyComponents = this.deconstructEncryptedPayloadString(encrypted.enc_item_key)
|
||||
const authenticatedData = this.stringToAuthenticatedData(itemKeyComponents.authenticatedData, {
|
||||
const contentKeyComponents = this.deconstructEncryptedPayloadString(encrypted.enc_item_key)
|
||||
const authenticatedData = this.stringToAuthenticatedData(contentKeyComponents.authenticatedData, {
|
||||
u: encrypted.uuid,
|
||||
v: encrypted.version,
|
||||
})
|
||||
|
||||
const useAuthenticatedString = this.authenticatedDataToString(authenticatedData)
|
||||
const itemKey = this.decryptString004(
|
||||
itemKeyComponents.ciphertext,
|
||||
const contentKey = this.decryptString004(
|
||||
contentKeyComponents.ciphertext,
|
||||
key.itemsKey,
|
||||
itemKeyComponents.nonce,
|
||||
contentKeyComponents.nonce,
|
||||
useAuthenticatedString,
|
||||
)
|
||||
|
||||
if (!itemKey) {
|
||||
if (!contentKey) {
|
||||
console.error('Error decrypting itemKey parameters', encrypted)
|
||||
return {
|
||||
uuid: encrypted.uuid,
|
||||
@@ -279,10 +279,11 @@ export class SNProtocolOperator004 implements SynchronousOperator {
|
||||
const contentComponents = this.deconstructEncryptedPayloadString(encrypted.content)
|
||||
const content = this.decryptString004(
|
||||
contentComponents.ciphertext,
|
||||
itemKey,
|
||||
contentKey,
|
||||
contentComponents.nonce,
|
||||
useAuthenticatedString,
|
||||
)
|
||||
|
||||
if (!content) {
|
||||
return {
|
||||
uuid: encrypted.uuid,
|
||||
@@ -305,6 +306,7 @@ export class SNProtocolOperator004 implements SynchronousOperator {
|
||||
V004Algorithm.ArgonMemLimit,
|
||||
V004Algorithm.ArgonOutputKeyBytes,
|
||||
)
|
||||
|
||||
const partitions = Utils.splitString(derivedKey, 2)
|
||||
const masterKey = partitions[0]
|
||||
const serverPassword = partitions[1]
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { ProtocolOperator005 } from './Operator005'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
|
||||
describe('operator 005', () => {
|
||||
let crypto: PureCryptoInterface
|
||||
let operator: ProtocolOperator005
|
||||
|
||||
beforeEach(() => {
|
||||
crypto = {} as jest.Mocked<PureCryptoInterface>
|
||||
crypto.generateRandomKey = jest.fn().mockImplementation(() => {
|
||||
return 'random-string'
|
||||
})
|
||||
crypto.xchacha20Encrypt = jest.fn().mockImplementation((text: string) => {
|
||||
return `<e>${text}<e>`
|
||||
})
|
||||
crypto.xchacha20Decrypt = jest.fn().mockImplementation((text: string) => {
|
||||
return text.split('<e>')[1]
|
||||
})
|
||||
crypto.sodiumCryptoBoxGenerateKeypair = jest.fn().mockImplementation(() => {
|
||||
return { privateKey: 'private-key', publicKey: 'public-key', keyType: 'x25519' }
|
||||
})
|
||||
crypto.sodiumCryptoBoxEasyEncrypt = jest.fn().mockImplementation((text: string) => {
|
||||
return `<e>${text}<e>`
|
||||
})
|
||||
crypto.sodiumCryptoBoxEasyDecrypt = jest.fn().mockImplementation((text: string) => {
|
||||
return text.split('<e>')[1]
|
||||
})
|
||||
|
||||
operator = new ProtocolOperator005(crypto)
|
||||
})
|
||||
|
||||
it('should generateKeyPair', () => {
|
||||
const result = operator.generateKeyPair()
|
||||
|
||||
expect(result).toEqual({ privateKey: 'private-key', publicKey: 'public-key', keyType: 'x25519' })
|
||||
})
|
||||
|
||||
it('should asymmetricEncryptKey', () => {
|
||||
const senderKeypair = operator.generateKeyPair()
|
||||
const recipientKeypair = operator.generateKeyPair()
|
||||
|
||||
const plaintext = 'foo'
|
||||
|
||||
const result = operator.asymmetricEncryptKey(plaintext, senderKeypair.privateKey, recipientKeypair.publicKey)
|
||||
|
||||
expect(result).toEqual(`${'005_KeyAsym'}:random-string:<e>foo<e>`)
|
||||
})
|
||||
|
||||
it('should asymmetricDecryptKey', () => {
|
||||
const senderKeypair = operator.generateKeyPair()
|
||||
const recipientKeypair = operator.generateKeyPair()
|
||||
const plaintext = 'foo'
|
||||
const ciphertext = operator.asymmetricEncryptKey(plaintext, senderKeypair.privateKey, recipientKeypair.publicKey)
|
||||
const decrypted = operator.asymmetricDecryptKey(ciphertext, senderKeypair.publicKey, recipientKeypair.privateKey)
|
||||
|
||||
expect(decrypted).toEqual('foo')
|
||||
})
|
||||
|
||||
it('should symmetricEncryptPrivateKey', () => {
|
||||
const keypair = operator.generateKeyPair()
|
||||
const symmetricKey = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
const encryptedKey = operator.symmetricEncryptPrivateKey(keypair.privateKey, symmetricKey)
|
||||
|
||||
expect(encryptedKey).toEqual(`${'005_KeySym'}:random-string:<e>${keypair.privateKey}<e>`)
|
||||
})
|
||||
|
||||
it('should symmetricDecryptPrivateKey', () => {
|
||||
const keypair = operator.generateKeyPair()
|
||||
const symmetricKey = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
const encryptedKey = operator.symmetricEncryptPrivateKey(keypair.privateKey, symmetricKey)
|
||||
const decryptedKey = operator.symmetricDecryptPrivateKey(encryptedKey, symmetricKey)
|
||||
|
||||
expect(decryptedKey).toEqual(keypair.privateKey)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,80 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { Base64String, HexString, PkcKeyPair, Utf8String } from '@standardnotes/sncrypto-common'
|
||||
import { V005Algorithm } from '../../Algorithm'
|
||||
import { SNProtocolOperator004 } from '../004/Operator004'
|
||||
|
||||
const VersionString = '005'
|
||||
const SymmetricCiphertextPrefix = `${VersionString}_KeySym`
|
||||
const AsymmetricCiphertextPrefix = `${VersionString}_KeyAsym`
|
||||
|
||||
export type AsymmetricallyEncryptedKey = Base64String
|
||||
export type SymmetricallyEncryptedPrivateKey = Base64String
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
* @unreleased
|
||||
*/
|
||||
export class ProtocolOperator005 extends SNProtocolOperator004 {
|
||||
public override getEncryptionDisplayName(): string {
|
||||
return 'XChaCha20-Poly1305'
|
||||
}
|
||||
|
||||
override get version(): ProtocolVersion {
|
||||
return VersionString as ProtocolVersion
|
||||
}
|
||||
|
||||
generateKeyPair(): PkcKeyPair {
|
||||
return this.crypto.sodiumCryptoBoxGenerateKeypair()
|
||||
}
|
||||
|
||||
asymmetricEncryptKey(
|
||||
keyToEncrypt: HexString,
|
||||
senderSecretKey: HexString,
|
||||
recipientPublicKey: HexString,
|
||||
): AsymmetricallyEncryptedKey {
|
||||
const nonce = this.crypto.generateRandomKey(V005Algorithm.AsymmetricEncryptionNonceLength)
|
||||
|
||||
const ciphertext = this.crypto.sodiumCryptoBoxEasyEncrypt(keyToEncrypt, nonce, senderSecretKey, recipientPublicKey)
|
||||
|
||||
return [AsymmetricCiphertextPrefix, nonce, ciphertext].join(':')
|
||||
}
|
||||
|
||||
asymmetricDecryptKey(
|
||||
keyToDecrypt: AsymmetricallyEncryptedKey,
|
||||
senderPublicKey: HexString,
|
||||
recipientSecretKey: HexString,
|
||||
): Utf8String {
|
||||
const components = keyToDecrypt.split(':')
|
||||
|
||||
const nonce = components[1]
|
||||
|
||||
return this.crypto.sodiumCryptoBoxEasyDecrypt(keyToDecrypt, nonce, senderPublicKey, recipientSecretKey)
|
||||
}
|
||||
|
||||
symmetricEncryptPrivateKey(privateKey: HexString, symmetricKey: HexString): SymmetricallyEncryptedPrivateKey {
|
||||
if (symmetricKey.length !== 64) {
|
||||
throw new Error('Symmetric key length must be 256 bits')
|
||||
}
|
||||
|
||||
const nonce = this.crypto.generateRandomKey(V005Algorithm.SymmetricEncryptionNonceLength)
|
||||
|
||||
const encryptedKey = this.crypto.xchacha20Encrypt(privateKey, nonce, symmetricKey)
|
||||
|
||||
return [SymmetricCiphertextPrefix, nonce, encryptedKey].join(':')
|
||||
}
|
||||
|
||||
symmetricDecryptPrivateKey(
|
||||
encryptedPrivateKey: SymmetricallyEncryptedPrivateKey,
|
||||
symmetricKey: HexString,
|
||||
): HexString | null {
|
||||
if (symmetricKey.length !== 64) {
|
||||
throw new Error('Symmetric key length must be 256 bits')
|
||||
}
|
||||
|
||||
const components = encryptedPrivateKey.split(':')
|
||||
|
||||
const nonce = components[1]
|
||||
|
||||
return this.crypto.xchacha20Decrypt(encryptedPrivateKey, nonce, symmetricKey)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@ import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams'
|
||||
import { KeyedDecryptionSplit } from '../../Split/KeyedDecryptionSplit'
|
||||
import { KeyedEncryptionSplit } from '../../Split/KeyedEncryptionSplit'
|
||||
|
||||
export interface EncryptionProvider {
|
||||
export interface EncryptionProviderInterface {
|
||||
encryptSplitSingle(split: KeyedEncryptionSplit): Promise<EncryptedPayloadInterface>
|
||||
|
||||
encryptSplit(split: KeyedEncryptionSplit): Promise<EncryptedPayloadInterface[]>
|
||||
@@ -14,11 +14,12 @@ export * from './Operator/001/Operator001'
|
||||
export * from './Operator/002/Operator002'
|
||||
export * from './Operator/003/Operator003'
|
||||
export * from './Operator/004/Operator004'
|
||||
export * from './Operator/005/Operator005'
|
||||
export * from './Operator/Functions'
|
||||
export * from './Operator/Operator'
|
||||
export * from './Operator/OperatorManager'
|
||||
export * from './Operator/OperatorWrapper'
|
||||
export * from './Service/Encryption/EncryptionProvider'
|
||||
export * from './Service/Encryption/EncryptionProviderInterface'
|
||||
export * from './Service/Functions'
|
||||
export * from './Service/RootKey/KeyMode'
|
||||
export * from './Service/RootKey/RootKeyServiceEvent'
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.23.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.23.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.23.11",
|
||||
"version": "1.23.12",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.10.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.10.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.10.11",
|
||||
"version": "1.10.12",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,56 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.41.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.41.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.41.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.41.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **mobile:** disable landscape for iPhone ([a365c17](https://github.com/standardnotes/app/commit/a365c17c46f38e6d8cacade47542fd4ec625bc0d))
|
||||
|
||||
### Features
|
||||
|
||||
* **mobile:** delete account option in settings ([#1768](https://github.com/standardnotes/app/issues/1768)) ([7d21046](https://github.com/standardnotes/app/commit/7d21046b5189eb14a5957bda6fa788d743d8ebb6))
|
||||
|
||||
## [3.40.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.40.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.40.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
* experimental 005 operator ([#1753](https://github.com/standardnotes/app/issues/1753)) ([cbbe913](https://github.com/standardnotes/app/commit/cbbe913cd6eb694dd27997927bd5c45e8a64cc09))
|
||||
|
||||
## [3.39.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.39.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add android platform check for status bar color sync ([1602d81](https://github.com/standardnotes/app/commit/1602d8157f9e61c6ad842bcce28f3aaf6a8f7154))
|
||||
|
||||
## [3.39.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.39.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -138,6 +138,10 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~iphone</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>supportsAlternateIcons</key>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.39.9",
|
||||
"version": "3.41.3",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -460,7 +460,7 @@ export class MobileDevice implements MobileDeviceInterface {
|
||||
}
|
||||
|
||||
reloadStatusBarStyle(animated = true) {
|
||||
if (this.statusBarBgColor) {
|
||||
if (this.statusBarBgColor && Platform.OS === 'android') {
|
||||
StatusBar.setBackgroundColor(this.statusBarBgColor, animated)
|
||||
}
|
||||
StatusBar.setBarStyle(this.isDarkMode ? 'light-content' : 'dark-content', animated)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Base64String,
|
||||
HexString,
|
||||
PkcKeyPair,
|
||||
PureCryptoInterface,
|
||||
SodiumConstant,
|
||||
StreamDecryptorResult,
|
||||
@@ -129,6 +130,28 @@ export class SNReactNativeCrypto implements PureCryptoInterface {
|
||||
}
|
||||
}
|
||||
|
||||
public sodiumCryptoBoxEasyEncrypt(
|
||||
_message: Utf8String,
|
||||
_nonce: HexString,
|
||||
_senderSecretKey: HexString,
|
||||
_recipientPublicKey: HexString,
|
||||
): Base64String {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
public sodiumCryptoBoxEasyDecrypt(
|
||||
_ciphertext: Base64String,
|
||||
_nonce: HexString,
|
||||
_senderPublicKey: HexString,
|
||||
_recipientSecretKey: HexString,
|
||||
): Utf8String {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
public sodiumCryptoBoxGenerateKeypair(): PkcKeyPair {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
public generateUUID() {
|
||||
const randomBuf = Sodium.randombytes_buf(16)
|
||||
const tempBuf = new Uint8Array(randomBuf.length / 2)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { ApplicationContext } from '@Root/ApplicationContext'
|
||||
import { ButtonCell } from '@Root/Components/ButtonCell'
|
||||
import { SectionHeader } from '@Root/Components/SectionHeader'
|
||||
import { TableSection } from '@Root/Components/TableSection'
|
||||
import { ButtonType } from '@standardnotes/snjs'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { RegularView } from './AuthSection.styled'
|
||||
|
||||
export const DeleteSection = () => {
|
||||
const application = useContext(ApplicationContext)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
|
||||
const deleteAccount = async () => {
|
||||
const message =
|
||||
"This action is irreversible. After deletion completes, you will be signed out on all devices, and this application will exit. If you have an active paid subscription, cancel the subscription first. Otherwise, if you'd like to keep the subscription, you can re-register with the same email after deletion, and your subscription will be linked back up with your account."
|
||||
const confirmed = await application!.alertService.confirm(
|
||||
message,
|
||||
'Are you sure?',
|
||||
'Delete Account',
|
||||
ButtonType.Danger,
|
||||
)
|
||||
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
setDeleting(true)
|
||||
|
||||
const result = await application!.user.deleteAccount()
|
||||
if (result.error) {
|
||||
void application!.alertService.alert('An error occurred while deleting your account. Please try again.')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<RegularView>
|
||||
<SectionHeader />
|
||||
<TableSection>
|
||||
<ButtonCell
|
||||
first
|
||||
last
|
||||
important
|
||||
leftAligned={true}
|
||||
title={deleting ? 'Deleting...' : 'Delete Account'}
|
||||
onPress={deleteAccount}
|
||||
></ButtonCell>
|
||||
</TableSection>
|
||||
</RegularView>
|
||||
)
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export const WorkspacesSection = () => {
|
||||
}
|
||||
|
||||
await appGroup.unloadCurrentAndCreateNewDescriptor()
|
||||
}, [WorkspaceAction.AddAnother, appGroup, applicationDescriptors, getWorkspaceActionConfirmation])
|
||||
}, [WorkspaceAction.AddAnother, appGroup, getWorkspaceActionConfirmation])
|
||||
|
||||
const signOutAllWorkspaces = useCallback(async () => {
|
||||
try {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ApplicationEvent, FeatureIdentifier, FeatureStatus } from '@standardnot
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { AuthSection } from './Sections/AuthSection'
|
||||
import { CompanySection } from './Sections/CompanySection'
|
||||
import { DeleteSection } from './Sections/DeleteSection'
|
||||
import { EncryptionSection } from './Sections/EncryptionSection'
|
||||
import { NewMobileSection } from './Sections/NewMobilePreview'
|
||||
import { OptionsSection } from './Sections/OptionsSection'
|
||||
@@ -68,6 +69,7 @@ export const Settings = (props: Props) => {
|
||||
<ProtectionsSection title="Protections" protectionsAvailable={protectionsAvailable} />
|
||||
<EncryptionSection encryptionAvailable={!!encryptionAvailable} title={'Encryption Status'} />
|
||||
<CompanySection title="Standard Notes" />
|
||||
{application.hasAccount() && <DeleteSection />}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.24.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
* experimental 005 operator ([#1753](https://github.com/standardnotes/app/issues/1753)) ([cbbe913](https://github.com/standardnotes/app/commit/cbbe913cd6eb694dd27997927bd5c45e8a64cc09))
|
||||
|
||||
# [1.23.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.23.0",
|
||||
"version": "1.24.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -24,11 +24,11 @@ export class DecryptedItem<C extends ItemContent = ItemContent>
|
||||
|
||||
constructor(payload: DecryptedPayloadInterface<C>) {
|
||||
super(payload)
|
||||
this.conflictOf = payload.content.conflict_of
|
||||
|
||||
const userModVal = this.getAppDomainValueWithDefault(AppDataField.UserModifiedDate, this.serverUpdatedAt || 0)
|
||||
|
||||
this.userModifiedDate = new Date(userModVal as number | Date)
|
||||
|
||||
this.conflictOf = payload.content.conflict_of
|
||||
this.updatedAtString = dateToLocalizedString(this.userModifiedDate)
|
||||
this.protected = useBoolean(this.payload.content.protected, false)
|
||||
this.trashed = useBoolean(this.payload.content.trashed, false)
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.127](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.126](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.125](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.124](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.123](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.122](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.121](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.120](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.119](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.118](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.3.117](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.3.117",
|
||||
"version": "1.3.127",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.26.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add workspaces api ([#1765](https://github.com/standardnotes/app/issues/1765)) ([01ba715](https://github.com/standardnotes/app/commit/01ba715eba987a7da1ee062fec0b3593a7a453ed))
|
||||
|
||||
# [1.25.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
* experimental 005 operator ([#1753](https://github.com/standardnotes/app/issues/1753)) ([cbbe913](https://github.com/standardnotes/app/commit/cbbe913cd6eb694dd27997927bd5c45e8a64cc09))
|
||||
|
||||
## [1.24.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = {
|
||||
global: {
|
||||
branches: 9,
|
||||
functions: 10,
|
||||
lines: 17,
|
||||
lines: 16,
|
||||
statements: 16
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.24.3",
|
||||
"version": "1.26.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { ApplicationIdentifier, ContentType } from '@standardnotes/common'
|
||||
import { BackupFile, DecryptedItemInterface, ItemStream, Platform, PrefKey, PrefValue } from '@standardnotes/models'
|
||||
import { FilesClientInterface } from '@standardnotes/files'
|
||||
import { AlertService } from '../Alert/AlertService'
|
||||
|
||||
import { AlertService } from '../Alert/AlertService'
|
||||
import { ComponentManagerInterface } from '../Component/ComponentManagerInterface'
|
||||
import { ApplicationEvent } from '../Event/ApplicationEvent'
|
||||
import { ApplicationEventCallback } from '../Event/ApplicationEventCallback'
|
||||
import { FeaturesClientInterface } from '../Feature/FeaturesClientInterface'
|
||||
import { SubscriptionClientInterface } from '../Subscription/SubscriptionClientInterface'
|
||||
import { DeviceInterface } from '../Device/DeviceInterface'
|
||||
import { WorkspaceClientInterface } from '../Workspace/WorkspaceClientInterface'
|
||||
import { ItemsClientInterface } from '../Item/ItemsClientInterface'
|
||||
import { MutatorClientInterface } from '../Mutator/MutatorClientInterface'
|
||||
import { StorageValueModes } from '../Storage/StorageTypes'
|
||||
@@ -15,7 +17,6 @@ import { StorageValueModes } from '../Storage/StorageTypes'
|
||||
import { DeinitMode } from './DeinitMode'
|
||||
import { DeinitSource } from './DeinitSource'
|
||||
import { UserClientInterface } from './UserClientInterface'
|
||||
import { DeviceInterface } from '../Device/DeviceInterface'
|
||||
|
||||
export interface ApplicationInterface {
|
||||
deinit(mode: DeinitMode, source: DeinitSource): void
|
||||
@@ -49,6 +50,7 @@ export interface ApplicationInterface {
|
||||
get user(): UserClientInterface
|
||||
get files(): FilesClientInterface
|
||||
get subscriptions(): SubscriptionClientInterface
|
||||
get workspaces(): WorkspaceClientInterface
|
||||
readonly identifier: ApplicationIdentifier
|
||||
readonly platform: Platform
|
||||
deviceInterface: DeviceInterface
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ContentType, Uuid } from '@standardnotes/common'
|
||||
import { EncryptionProvider } from '@standardnotes/encryption'
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { PayloadEmitSource, FileItem, CreateEncryptedBackupFileContextPayload } from '@standardnotes/models'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { FilesApiInterface, FileBackupMetadataFile, FileBackupsDevice, FileBackupsMapping } from '@standardnotes/files'
|
||||
@@ -15,7 +15,7 @@ export class FilesBackupService extends AbstractService {
|
||||
constructor(
|
||||
private items: ItemManagerInterface,
|
||||
private api: FilesApiInterface,
|
||||
private encryptor: EncryptionProvider,
|
||||
private encryptor: EncryptionProviderInterface,
|
||||
private device: FileBackupsDevice,
|
||||
private status: StatusServiceInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
DecryptedParameters,
|
||||
EncryptedParameters,
|
||||
encryptedParametersFromPayload,
|
||||
EncryptionProvider,
|
||||
EncryptionProviderInterface,
|
||||
ErrorDecryptingParameters,
|
||||
findDefaultItemsKey,
|
||||
FindPayloadInDecryptionSplit,
|
||||
@@ -100,7 +100,7 @@ import { EncryptionServiceEvent } from './EncryptionServiceEvent'
|
||||
* It also exposes public methods that allows consumers to retrieve an items key
|
||||
* for a particular payload, and also retrieve all available items keys.
|
||||
*/
|
||||
export class EncryptionService extends AbstractService<EncryptionServiceEvent> implements EncryptionProvider {
|
||||
export class EncryptionService extends AbstractService<EncryptionServiceEvent> implements EncryptionProviderInterface {
|
||||
private operatorManager: OperatorManager
|
||||
private readonly itemsEncryption: ItemsEncryptionService
|
||||
private readonly rootKeyEncryption: RootKeyEncryptionService
|
||||
@@ -714,7 +714,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
await this.rootKeyEncryption.createNewDefaultItemsKey()
|
||||
}
|
||||
|
||||
this.syncUnsycnedItemsKeys()
|
||||
this.syncUnsyncedItemsKeys()
|
||||
}
|
||||
|
||||
private async handleFullSyncCompletion() {
|
||||
@@ -734,7 +734,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
* items key never syncing to the account even though it is being used to encrypt synced items.
|
||||
* Until we can determine its cause, this corrective function will find any such keys and sync them.
|
||||
*/
|
||||
private syncUnsycnedItemsKeys(): void {
|
||||
private syncUnsyncedItemsKeys(): void {
|
||||
if (!this.hasAccount()) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ItemsKeyContent,
|
||||
RootKeyInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { EncryptionProvider, KeyRecoveryStrings, SNRootKeyParams } from '@standardnotes/encryption'
|
||||
import { EncryptionProviderInterface, KeyRecoveryStrings, SNRootKeyParams } from '@standardnotes/encryption'
|
||||
import { ChallengeServiceInterface } from '../Challenge/ChallengeServiceInterface'
|
||||
import { ChallengePrompt } from '../Challenge/Prompt/ChallengePrompt'
|
||||
import { ChallengeReason } from '../Challenge/Types/ChallengeReason'
|
||||
@@ -13,7 +13,7 @@ import { ChallengeValidation } from '../Challenge/Types/ChallengeValidation'
|
||||
|
||||
export async function DecryptItemsKeyWithUserFallback(
|
||||
itemsKey: EncryptedPayloadInterface,
|
||||
encryptor: EncryptionProvider,
|
||||
encryptor: EncryptionProviderInterface,
|
||||
challengor: ChallengeServiceInterface,
|
||||
): Promise<DecryptedPayloadInterface<ItemsKeyContent> | 'failed' | 'aborted'> {
|
||||
const decryptionResult = await encryptor.decryptSplitSingle<ItemsKeyContent>({
|
||||
@@ -37,7 +37,7 @@ export async function DecryptItemsKeyWithUserFallback(
|
||||
|
||||
export async function DecryptItemsKeyByPromptingUser(
|
||||
itemsKey: EncryptedPayloadInterface,
|
||||
encryptor: EncryptionProvider,
|
||||
encryptor: EncryptionProviderInterface,
|
||||
challengor: ChallengeServiceInterface,
|
||||
keyParams?: SNRootKeyParams,
|
||||
): Promise<
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PureCryptoInterface, StreamEncryptor } from '@standardnotes/sncrypto-common'
|
||||
import { FileItem } from '@standardnotes/models'
|
||||
import { EncryptionProvider } from '@standardnotes/encryption'
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { ChallengeServiceInterface } from '../Challenge'
|
||||
@@ -19,7 +19,7 @@ describe('fileService', () => {
|
||||
let crypto: PureCryptoInterface
|
||||
let challengor: ChallengeServiceInterface
|
||||
let fileService: FileService
|
||||
let encryptor: EncryptionProvider
|
||||
let encryptor: EncryptionProviderInterface
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -41,7 +41,7 @@ describe('fileService', () => {
|
||||
syncService = {} as jest.Mocked<SyncServiceInterface>
|
||||
syncService.sync = jest.fn()
|
||||
|
||||
encryptor = {} as jest.Mocked<EncryptionProvider>
|
||||
encryptor = {} as jest.Mocked<EncryptionProviderInterface>
|
||||
|
||||
alertService = {} as jest.Mocked<AlertService>
|
||||
alertService.confirm = jest.fn().mockReturnValue(true)
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '@standardnotes/models'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { UuidGenerator } from '@standardnotes/utils'
|
||||
import { EncryptionProvider, SNItemsKey } from '@standardnotes/encryption'
|
||||
import { EncryptionProviderInterface, SNItemsKey } from '@standardnotes/encryption'
|
||||
import {
|
||||
DownloadAndDecryptFileOperation,
|
||||
EncryptAndUploadFileOperation,
|
||||
@@ -49,7 +49,7 @@ export class FileService extends AbstractService implements FilesClientInterface
|
||||
private api: FilesApiInterface,
|
||||
private itemManager: ItemManagerInterface,
|
||||
private syncService: SyncServiceInterface,
|
||||
private encryptor: EncryptionProvider,
|
||||
private encryptor: EncryptionProviderInterface,
|
||||
private challengor: ChallengeServiceInterface,
|
||||
private alertService: AlertService,
|
||||
private crypto: PureCryptoInterface,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface WorkspaceClientInterface {
|
||||
createWorkspace(dto: {
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
publicKey: string
|
||||
workspaceName?: string
|
||||
}): Promise<{ uuid: string } | null>
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { WorkspaceApiServiceInterface } from '@standardnotes/api'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { WorkspaceClientInterface } from './WorkspaceClientInterface'
|
||||
|
||||
export class WorkspaceManager extends AbstractService implements WorkspaceClientInterface {
|
||||
constructor(
|
||||
private workspaceApiService: WorkspaceApiServiceInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
}
|
||||
|
||||
async createWorkspace(dto: {
|
||||
encryptedWorkspaceKey: string
|
||||
encryptedPrivateKey: string
|
||||
publicKey: string
|
||||
workspaceName?: string
|
||||
}): Promise<{ uuid: string } | null> {
|
||||
try {
|
||||
const result = await this.workspaceApiService.createWorkspace(dto)
|
||||
|
||||
if (result.data.error !== undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result.data
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,3 +73,5 @@ export * from './Sync/SyncOptions'
|
||||
export * from './Sync/SyncQueueStrategy'
|
||||
export * from './Sync/SyncServiceInterface'
|
||||
export * from './Sync/SyncSource'
|
||||
export * from './Workspace/WorkspaceClientInterface'
|
||||
export * from './Workspace/WorkspaceManager'
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.13.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
* experimental 005 operator ([#1753](https://github.com/standardnotes/app/issues/1753)) ([cbbe913](https://github.com/standardnotes/app/commit/cbbe913cd6eb694dd27997927bd5c45e8a64cc09))
|
||||
|
||||
# [1.12.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-28)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/sncrypto-common",
|
||||
"version": "1.12.0",
|
||||
"version": "1.13.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PkcKeyPair } from '../Types'
|
||||
import { Base64String } from '../Types/Base64String'
|
||||
import { Base64URLSafeString } from '../Types/Base64URLSafeString'
|
||||
import { HexString } from '../Types/HexString'
|
||||
@@ -27,7 +28,7 @@ export interface PureCryptoInterface {
|
||||
* @param bits - Length of key in bits
|
||||
* @returns A string key in hex format
|
||||
*/
|
||||
generateRandomKey(bits: number): string
|
||||
generateRandomKey(bits: number): HexString
|
||||
|
||||
/**
|
||||
* @legacy
|
||||
@@ -98,7 +99,7 @@ export interface PureCryptoInterface {
|
||||
* @param assocData
|
||||
* @returns Base64 ciphertext string
|
||||
*/
|
||||
xchacha20Encrypt(plaintext: Utf8String, nonce: HexString, key: HexString, assocData: Utf8String): Base64String
|
||||
xchacha20Encrypt(plaintext: Utf8String, nonce: HexString, key: HexString, assocData?: Utf8String): Base64String
|
||||
|
||||
/**
|
||||
* Decrypt a message (and associated data) with XChaCha20-Poly1305
|
||||
@@ -112,7 +113,7 @@ export interface PureCryptoInterface {
|
||||
ciphertext: Base64String,
|
||||
nonce: HexString,
|
||||
key: HexString,
|
||||
assocData: Utf8String | Uint8Array,
|
||||
assocData?: Utf8String | Uint8Array,
|
||||
): Utf8String | null
|
||||
|
||||
xchacha20StreamInitEncryptor(key: HexString): StreamEncryptor
|
||||
@@ -132,6 +133,22 @@ export interface PureCryptoInterface {
|
||||
assocData: Utf8String,
|
||||
): { message: Uint8Array; tag: SodiumConstant } | false
|
||||
|
||||
sodiumCryptoBoxEasyEncrypt(
|
||||
message: Utf8String,
|
||||
nonce: HexString,
|
||||
senderSecretKey: HexString,
|
||||
recipientPublicKey: HexString,
|
||||
): Base64String
|
||||
|
||||
sodiumCryptoBoxEasyDecrypt(
|
||||
ciphertext: Base64String,
|
||||
nonce: HexString,
|
||||
senderPublicKey: HexString,
|
||||
recipientSecretKey: HexString,
|
||||
): Utf8String
|
||||
|
||||
sodiumCryptoBoxGenerateKeypair(): PkcKeyPair
|
||||
|
||||
/**
|
||||
* Converts a plain string into base64
|
||||
* @param text - A plain string
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.14.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
* experimental 005 operator ([#1753](https://github.com/standardnotes/app/issues/1753)) ([cbbe913](https://github.com/standardnotes/app/commit/cbbe913cd6eb694dd27997927bd5c45e8a64cc09))
|
||||
|
||||
# [1.13.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-09-28)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/sncrypto-web",
|
||||
"version": "1.13.0",
|
||||
"version": "1.14.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -93,7 +93,7 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
return this.webCryptoDeriveBits(key, salt, iterations, length)
|
||||
}
|
||||
|
||||
public generateRandomKey(bits: number): string {
|
||||
public generateRandomKey(bits: number): HexString {
|
||||
const bytes = bits / 8
|
||||
const arrayBuffer = Utils.getGlobalScope().crypto.getRandomValues(new Uint8Array(bytes))
|
||||
return Utils.arrayBufferToHexString(arrayBuffer)
|
||||
@@ -249,14 +249,14 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
plaintext: Utf8String,
|
||||
nonce: HexString,
|
||||
key: HexString,
|
||||
assocData: Utf8String,
|
||||
assocData?: Utf8String,
|
||||
): Base64String {
|
||||
if (nonce.length !== 48) {
|
||||
throw Error('Nonce must be 24 bytes')
|
||||
}
|
||||
const arrayBuffer = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||
plaintext,
|
||||
assocData,
|
||||
assocData || null,
|
||||
null,
|
||||
Utils.hexStringToArrayBuffer(nonce),
|
||||
Utils.hexStringToArrayBuffer(key),
|
||||
@@ -268,7 +268,7 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
ciphertext: Base64String,
|
||||
nonce: HexString,
|
||||
key: HexString,
|
||||
assocData: Utf8String | Uint8Array,
|
||||
assocData?: Utf8String | Uint8Array,
|
||||
): Utf8String | null {
|
||||
if (nonce.length !== 48) {
|
||||
throw Error('Nonce must be 24 bytes')
|
||||
@@ -277,7 +277,7 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||
null,
|
||||
Utils.base64ToArrayBuffer(ciphertext),
|
||||
assocData,
|
||||
assocData || null,
|
||||
Utils.hexStringToArrayBuffer(nonce),
|
||||
Utils.hexStringToArrayBuffer(key),
|
||||
'text',
|
||||
@@ -368,7 +368,7 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
nonce: HexString,
|
||||
senderPublicKey: HexString,
|
||||
recipientSecretKey: HexString,
|
||||
): Base64String {
|
||||
): Utf8String {
|
||||
const result = sodium.crypto_box_open_easy(
|
||||
Utils.base64ToArrayBuffer(ciphertext),
|
||||
Utils.hexStringToArrayBuffer(nonce),
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.136.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** add workspaces api ([#1765](https://github.com/standardnotes/app/issues/1765)) ([01ba715](https://github.com/standardnotes/app/commit/01ba715eba987a7da1ee062fec0b3593a7a453ed))
|
||||
|
||||
# [2.135.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
* experimental 005 operator ([#1753](https://github.com/standardnotes/app/issues/1753)) ([cbbe913](https://github.com/standardnotes/app/commit/cbbe913cd6eb694dd27997927bd5c45e8a64cc09))
|
||||
|
||||
## [2.134.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -14,6 +14,10 @@ import {
|
||||
WebSocketApiServiceInterface,
|
||||
WebSocketServer,
|
||||
WebSocketServerInterface,
|
||||
WorkspaceApiService,
|
||||
WorkspaceApiServiceInterface,
|
||||
WorkspaceServer,
|
||||
WorkspaceServerInterface,
|
||||
} from '@standardnotes/api'
|
||||
import * as Common from '@standardnotes/common'
|
||||
import * as ExternalServices from '@standardnotes/services'
|
||||
@@ -45,6 +49,8 @@ import {
|
||||
FileService,
|
||||
SubscriptionClientInterface,
|
||||
SubscriptionManager,
|
||||
WorkspaceClientInterface,
|
||||
WorkspaceManager,
|
||||
} from '@standardnotes/services'
|
||||
import { FilesClientInterface } from '@standardnotes/files'
|
||||
import { ComputePrivateWorkspaceIdentifier } from '@standardnotes/encryption'
|
||||
@@ -110,6 +116,9 @@ export class SNApplication
|
||||
private declare subscriptionApiService: SubscriptionApiServiceInterface
|
||||
private declare subscriptionServer: SubscriptionServerInterface
|
||||
private declare subscriptionManager: SubscriptionClientInterface
|
||||
private declare workspaceApiService: WorkspaceApiServiceInterface
|
||||
private declare workspaceServer: WorkspaceServerInterface
|
||||
private declare workspaceManager: WorkspaceClientInterface
|
||||
private declare webSocketApiService: WebSocketApiServiceInterface
|
||||
private declare webSocketServer: WebSocketServerInterface
|
||||
private sessionManager!: InternalServices.SNSessionManager
|
||||
@@ -211,6 +220,10 @@ export class SNApplication
|
||||
return this.subscriptionManager
|
||||
}
|
||||
|
||||
get workspaces(): ExternalServices.WorkspaceClientInterface {
|
||||
return this.workspaceManager
|
||||
}
|
||||
|
||||
public get files(): FilesClientInterface {
|
||||
return this.fileService
|
||||
}
|
||||
@@ -1047,6 +1060,9 @@ export class SNApplication
|
||||
this.createWebSocketServer()
|
||||
this.createWebSocketApiService()
|
||||
this.createSubscriptionManager()
|
||||
this.createWorkspaceServer()
|
||||
this.createWorkspaceApiService()
|
||||
this.createWorkspaceManager()
|
||||
this.createWebSocketsService()
|
||||
this.createSessionManager()
|
||||
this.createHistoryManager()
|
||||
@@ -1087,9 +1103,12 @@ export class SNApplication
|
||||
;(this.userServer as unknown) = undefined
|
||||
;(this.subscriptionApiService as unknown) = undefined
|
||||
;(this.subscriptionServer as unknown) = undefined
|
||||
;(this.subscriptionManager as unknown) = undefined
|
||||
;(this.workspaceApiService as unknown) = undefined
|
||||
;(this.workspaceServer as unknown) = undefined
|
||||
;(this.workspaceManager as unknown) = undefined
|
||||
;(this.webSocketApiService as unknown) = undefined
|
||||
;(this.webSocketServer as unknown) = undefined
|
||||
;(this.subscriptionManager as unknown) = undefined
|
||||
;(this.sessionManager as unknown) = undefined
|
||||
;(this.syncService as unknown) = undefined
|
||||
;(this.challengeService as unknown) = undefined
|
||||
@@ -1304,6 +1323,18 @@ export class SNApplication
|
||||
this.subscriptionManager = new SubscriptionManager(this.subscriptionApiService, this.internalEventBus)
|
||||
}
|
||||
|
||||
private createWorkspaceServer() {
|
||||
this.workspaceServer = new WorkspaceServer(this.httpService)
|
||||
}
|
||||
|
||||
private createWorkspaceApiService() {
|
||||
this.workspaceApiService = new WorkspaceApiService(this.workspaceServer)
|
||||
}
|
||||
|
||||
private createWorkspaceManager() {
|
||||
this.workspaceManager = new WorkspaceManager(this.workspaceApiService, this.internalEventBus)
|
||||
}
|
||||
|
||||
private createItemManager() {
|
||||
this.itemManager = new InternalServices.ItemManager(this.payloadManager, this.options, this.internalEventBus)
|
||||
this.services.push(this.itemManager)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ItemsKeyInterface } from '@standardnotes/models'
|
||||
import { dateSorted } from '@standardnotes/utils'
|
||||
import { SNRootKeyParams, EncryptionProvider } from '@standardnotes/encryption'
|
||||
import { SNRootKeyParams, EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { DecryptionQueueItem, KeyRecoveryOperationResult } from './Types'
|
||||
import { serverKeyParamsAreSafe } from './Utils'
|
||||
import { ChallengeServiceInterface, DecryptItemsKeyByPromptingUser } from '@standardnotes/services'
|
||||
@@ -11,7 +11,7 @@ export class KeyRecoveryOperation {
|
||||
constructor(
|
||||
private queueItem: DecryptionQueueItem,
|
||||
private itemManager: ItemManager,
|
||||
private protocolService: EncryptionProvider,
|
||||
private protocolService: EncryptionProviderInterface,
|
||||
private challengeService: ChallengeServiceInterface,
|
||||
private clientParams: SNRootKeyParams | undefined,
|
||||
private serverParams: SNRootKeyParams | undefined,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
ChallengeReason,
|
||||
MutatorClientInterface,
|
||||
} from '@standardnotes/services'
|
||||
import { EncryptionProvider } from '@standardnotes/encryption'
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { ContentType, ProtocolVersion, compareVersions } from '@standardnotes/common'
|
||||
import { ItemManager } from '../Items'
|
||||
@@ -49,7 +49,7 @@ export class MutatorService extends AbstractService implements MutatorClientInte
|
||||
private itemManager: ItemManager,
|
||||
private syncService: SNSyncService,
|
||||
private protectionService: SNProtectionService,
|
||||
private encryption: EncryptionProvider,
|
||||
private encryption: EncryptionProviderInterface,
|
||||
private payloadManager: PayloadManager,
|
||||
private challengeService: ChallengeService,
|
||||
private componentManager: SNComponentManager,
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
* key can decrypt wrapped storage.
|
||||
*/
|
||||
export class DiskStorageService extends Services.AbstractService implements Services.StorageServiceInterface {
|
||||
private encryptionProvider!: Encryption.EncryptionProvider
|
||||
private encryptionProvider!: Encryption.EncryptionProviderInterface
|
||||
private storagePersistable = false
|
||||
private persistencePolicy!: Services.StoragePersistencePolicies
|
||||
private encryptionPolicy!: Services.StorageEncryptionPolicy
|
||||
@@ -53,7 +53,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv
|
||||
void this.setEncryptionPolicy(Services.StorageEncryptionPolicy.Default, false)
|
||||
}
|
||||
|
||||
public provideEncryptionProvider(provider: Encryption.EncryptionProvider): void {
|
||||
public provideEncryptionProvider(provider: Encryption.EncryptionProviderInterface): void {
|
||||
this.encryptionProvider = provider
|
||||
}
|
||||
|
||||
|
||||
@@ -39,12 +39,14 @@ describe('basic auth', function () {
|
||||
let error = null
|
||||
try {
|
||||
await this.application.register(this.email, password)
|
||||
} catch(caughtError) {
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
|
||||
expect(error.message).to.equal('Your password must be at least 8 characters in length. '
|
||||
+ 'For your security, please choose a longer password or, ideally, a passphrase, and try again.')
|
||||
expect(error.message).to.equal(
|
||||
'Your password must be at least 8 characters in length. ' +
|
||||
'For your security, please choose a longer password or, ideally, a passphrase, and try again.',
|
||||
)
|
||||
|
||||
expect(await this.application.protocolService.getRootKey()).to.not.be.ok
|
||||
})
|
||||
|
||||
@@ -69,7 +69,8 @@ export default class FakeWebCrypto {
|
||||
}
|
||||
|
||||
generateRandomKey(bits) {
|
||||
const length = bits / 8
|
||||
const bitsPerHexChar = 4
|
||||
const length = bits / bitsPerHexChar
|
||||
return this.randomString(length)
|
||||
}
|
||||
|
||||
@@ -107,7 +108,13 @@ export default class FakeWebCrypto {
|
||||
}
|
||||
|
||||
argon2(password, salt, iterations, bytes, length) {
|
||||
return btoa(password)
|
||||
const bitsPerHexChar = 4
|
||||
const bitsInByte = 8
|
||||
const encoded = btoa(password)
|
||||
const desiredLength = length * (bitsInByte / bitsPerHexChar)
|
||||
const missingLength = desiredLength - encoded.length
|
||||
const result = `${encoded}${encoded.repeat(Math.ceil(missingLength / encoded.length))}`.slice(0, desiredLength)
|
||||
return result
|
||||
}
|
||||
|
||||
xchacha20Encrypt(plaintext, nonce, key, assocData) {
|
||||
@@ -128,6 +135,33 @@ export default class FakeWebCrypto {
|
||||
return data.plaintext
|
||||
}
|
||||
|
||||
sodiumCryptoBoxEasyEncrypt(message, nonce, senderSecretKey, recipientPublicKey) {
|
||||
const data = {
|
||||
message,
|
||||
nonce,
|
||||
senderSecretKey,
|
||||
recipientPublicKey,
|
||||
}
|
||||
return btoa(JSON.stringify(data))
|
||||
}
|
||||
|
||||
sodiumCryptoBoxEasyDecrypt(ciphertext, nonce, senderPublicKey, recipientSecretKey) {
|
||||
const data = JSON.parse(atob(ciphertext))
|
||||
if (
|
||||
data.senderPublicKey !== senderPublicKey ||
|
||||
data.recipientSecretKey !== recipientSecretKey ||
|
||||
data.nonce !== nonce ||
|
||||
data.assocData !== assocData
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return data.message
|
||||
}
|
||||
|
||||
sodiumCryptoBoxGenerateKeypair() {
|
||||
return { publicKey: this.randomString(64), privateKey: this.randomString(64), keyType: 'x25519' }
|
||||
}
|
||||
|
||||
generateOtpSecret() {
|
||||
return 'WQVV2GFBRQWU3UQZWQFZC37PSNRXKTA6'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.134.9",
|
||||
"version": "2.136.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.6.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.6.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dark mode toggling ([1377846](https://github.com/standardnotes/app/commit/1377846f3f3a23a405c96b8a733d439947abfefe))
|
||||
|
||||
# [1.6.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -233,7 +233,6 @@ export class ThemeManager extends AbstractService {
|
||||
if (theme && !theme.active) {
|
||||
this.application.mutator.toggleTheme(theme).catch(console.error)
|
||||
}
|
||||
void this.application.setPreference(PrefKey.DarkMode, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,6 +323,8 @@ export class ThemeManager extends AbstractService {
|
||||
}
|
||||
}
|
||||
document.getElementsByTagName('head')[0].appendChild(link)
|
||||
|
||||
void this.application.setPreference(PrefKey.DarkMode, false)
|
||||
}
|
||||
|
||||
private getBackgroundColor() {
|
||||
|
||||
@@ -3,6 +3,55 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.71.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* move viewport early return above ([6a4b5c5](https://github.com/standardnotes/app/commit/6a4b5c5cdc808c2947e4fe04c5a7e1c3ce7821d0))
|
||||
* potential fix for viewport height issue ([#1772](https://github.com/standardnotes/app/issues/1772)) ([cd1669f](https://github.com/standardnotes/app/commit/cd1669f56f510fe44c39f6c6d76ca6c93ee6e5ac))
|
||||
|
||||
## [3.71.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* open upgrade page in new tab if user has account ([e7ca12b](https://github.com/standardnotes/app/commit/e7ca12babefc4b8867c9dc2c535a9da0d4f62a76))
|
||||
|
||||
# [3.71.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Features
|
||||
|
||||
* change quick settings menu layout ([#1770](https://github.com/standardnotes/app/issues/1770)) ([59db63f](https://github.com/standardnotes/app/commit/59db63f05230168b20d6b2db2eaf1a4b99c02f50))
|
||||
* open purchase flow when clicking upgrade cta with no account ([#1771](https://github.com/standardnotes/app/issues/1771)) ([734b986](https://github.com/standardnotes/app/commit/734b986294380954c7eb3ceac42d76ef17b27ca1))
|
||||
|
||||
## [3.70.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add fallback for viewport height on mobile ([#1766](https://github.com/standardnotes/app/issues/1766)) ([1e3acd5](https://github.com/standardnotes/app/commit/1e3acd50e941051636ae2c92d143c649913b3c1a))
|
||||
|
||||
## [3.70.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.70.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dark mode toggling ([1377846](https://github.com/standardnotes/app/commit/1377846f3f3a23a405c96b8a733d439947abfefe))
|
||||
* **mobile:** reenable bouncing in lists ([c13dd88](https://github.com/standardnotes/app/commit/c13dd883a41d21d46350676f350ac95b5ae5d5a6))
|
||||
|
||||
## [3.70.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* increase default editor font size on mobile ([c944eb9](https://github.com/standardnotes/app/commit/c944eb9365e5835494724724d04c7dc3e20b8f61))
|
||||
|
||||
## [3.70.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add fallback for initial window size ([#1749](https://github.com/standardnotes/app/issues/1749)) ([05aff27](https://github.com/standardnotes/app/commit/05aff2776ba84f2988ebc7b3fca5ab9a18a58430))
|
||||
|
||||
# [3.70.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-10-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web",
|
||||
"version": "3.70.0",
|
||||
"version": "3.71.2",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/app.js",
|
||||
"author": "Standard Notes.",
|
||||
|
||||
@@ -35,17 +35,29 @@ import { WebOrDesktopDevice } from './Application/Device/WebOrDesktopDevice'
|
||||
import { WebApplication } from './Application/Application'
|
||||
import { createRoot, Root } from 'react-dom/client'
|
||||
import { ElementIds } from './Constants/ElementIDs'
|
||||
import { MediaQueryBreakpoints } from './Hooks/useMediaQuery'
|
||||
|
||||
let keyCount = 0
|
||||
const getKey = () => {
|
||||
return keyCount++
|
||||
}
|
||||
|
||||
const setViewportHeight = () => {
|
||||
document.documentElement.style.setProperty(
|
||||
'--viewport-height',
|
||||
`${visualViewport ? visualViewport.height : window.innerHeight}px`,
|
||||
)
|
||||
let initialCorrectViewportHeight: number | null = null
|
||||
|
||||
export const setViewportHeightWithFallback = (isOrientationChange = false) => {
|
||||
const newValue = visualViewport && visualViewport.height > 0 ? visualViewport.height : window.innerHeight
|
||||
|
||||
if (initialCorrectViewportHeight && newValue < initialCorrectViewportHeight && !isOrientationChange) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!newValue) {
|
||||
document.documentElement.style.setProperty('--viewport-height', '100vh')
|
||||
return
|
||||
}
|
||||
|
||||
initialCorrectViewportHeight = newValue
|
||||
document.documentElement.style.setProperty('--viewport-height', `${newValue}px`)
|
||||
}
|
||||
|
||||
const setDefaultMonospaceFont = (platform?: Platform) => {
|
||||
@@ -68,11 +80,35 @@ const startApplication: StartApplication = async function startApplication(
|
||||
SNLog.onError = console.error
|
||||
let root: Root
|
||||
|
||||
const onDestroy = () => {
|
||||
if (device.environment === Environment.Desktop) {
|
||||
window.removeEventListener('resize', setViewportHeight)
|
||||
const isDesktop =
|
||||
device.environment === Environment.Desktop ||
|
||||
(matchMedia(MediaQueryBreakpoints.md).matches && matchMedia(MediaQueryBreakpoints.pointerFine))
|
||||
|
||||
const orientationChangeHandler = () => {
|
||||
setViewportHeightWithFallback(true)
|
||||
}
|
||||
|
||||
const resizeHandler = () => {
|
||||
setViewportHeightWithFallback(false)
|
||||
}
|
||||
|
||||
const setupViewportHeightListeners = () => {
|
||||
if (!isDesktop) {
|
||||
setViewportHeightWithFallback()
|
||||
window.addEventListener('orientationchange', orientationChangeHandler)
|
||||
window.addEventListener('resize', resizeHandler)
|
||||
}
|
||||
window.removeEventListener('orientationchange', setViewportHeight)
|
||||
}
|
||||
|
||||
const removeViewportHeightListeners = () => {
|
||||
if (!isDesktop) {
|
||||
window.removeEventListener('orientationchange', orientationChangeHandler)
|
||||
window.removeEventListener('resize', resizeHandler)
|
||||
}
|
||||
}
|
||||
|
||||
const onDestroy = () => {
|
||||
removeViewportHeightListeners()
|
||||
const rootElement = document.getElementById(ElementIds.RootId) as HTMLElement
|
||||
root.unmount()
|
||||
rootElement.remove()
|
||||
@@ -87,11 +123,7 @@ const startApplication: StartApplication = async function startApplication(
|
||||
|
||||
disableIosTextFieldZoom()
|
||||
|
||||
setViewportHeight()
|
||||
window.addEventListener('orientationchange', setViewportHeight)
|
||||
if (device.environment === Environment.Desktop) {
|
||||
window.addEventListener('resize', setViewportHeight)
|
||||
}
|
||||
setupViewportHeightListeners()
|
||||
|
||||
setDefaultMonospaceFont(device.platform)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import { ArchiveManager, AutolockService, IOService, WebAlertService, ThemeManag
|
||||
import { MobileWebReceiver } from './MobileWebReceiver'
|
||||
import { AndroidBackHandler } from '@/NativeMobileWeb/AndroidBackHandler'
|
||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||
import { setViewportHeightWithFallback } from '@/App'
|
||||
|
||||
type WebServices = {
|
||||
viewControllerManager: ViewControllerManager
|
||||
@@ -233,8 +234,9 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
await this.lockApplicationAfterMobileEventIfApplicable()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async handleMobileGainingFocusEvent(): Promise<void> {}
|
||||
async handleMobileGainingFocusEvent(): Promise<void> {
|
||||
setViewportHeightWithFallback()
|
||||
}
|
||||
|
||||
async handleMobileLosingFocusEvent(): Promise<void> {
|
||||
if (this.protections.getMobileScreenshotPrivacyEnabled()) {
|
||||
@@ -248,6 +250,8 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
if (this.protections.getMobileScreenshotPrivacyEnabled()) {
|
||||
this.mobileDevice().hideMobileInterfaceFromScreenshots()
|
||||
}
|
||||
|
||||
setViewportHeightWithFallback()
|
||||
}
|
||||
|
||||
private async lockApplicationAfterMobileEventIfApplicable(): Promise<void> {
|
||||
|
||||
@@ -12,18 +12,6 @@ const UpgradeNow = ({ application, featuresController }: Props) => {
|
||||
const shouldShowCTA = !featuresController.hasFolders
|
||||
const hasAccount = application.hasAccount()
|
||||
|
||||
const openPlansPage = () => {
|
||||
if (!window.plansUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
if (application.isNativeMobileWeb()) {
|
||||
application.mobileDevice().openUrl(window.plansUrl)
|
||||
} else {
|
||||
window.location.assign(window.plansUrl)
|
||||
}
|
||||
}
|
||||
|
||||
return shouldShowCTA ? (
|
||||
<div className="flex h-full items-center px-2">
|
||||
<button
|
||||
@@ -34,7 +22,7 @@ const UpgradeNow = ({ application, featuresController }: Props) => {
|
||||
return
|
||||
}
|
||||
|
||||
openPlansPage()
|
||||
application.getViewControllerManager().purchaseFlowController.openPurchaseFlow()
|
||||
}}
|
||||
>
|
||||
Upgrade now
|
||||
|
||||
@@ -25,7 +25,8 @@ export const loadPurchaseFlowUrl = async (application: WebApplication): Promise<
|
||||
if (application.isNativeMobileWeb()) {
|
||||
application.mobileDevice().openUrl(finalUrl)
|
||||
} else {
|
||||
window.location.assign(finalUrl)
|
||||
const windowProxy = window.open('', '_blank')
|
||||
;(windowProxy as WindowProxy).location = finalUrl
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@@ -6,6 +6,7 @@ import { FunctionComponent } from 'react'
|
||||
import CreateAccount from './Panes/CreateAccount'
|
||||
import SignIn from './Panes/SignIn'
|
||||
import { SNLogoFull } from '@standardnotes/icons'
|
||||
import Icon from '../Icon/Icon'
|
||||
|
||||
type PaneSelectorProps = {
|
||||
currentPane: PurchaseFlowPane
|
||||
@@ -36,6 +37,14 @@ const PurchaseFlowView: FunctionComponent<PurchaseFlowViewProps> = ({ viewContro
|
||||
<div className="absolute top-0 left-0 z-purchase-flow flex h-full w-full items-center justify-center overflow-hidden bg-passive-super-light">
|
||||
<div className="relative w-fit">
|
||||
<div className="rounded-0 relative mb-4 w-full border border-solid border-border bg-default px-8 py-8 md:rounded md:p-12">
|
||||
<button
|
||||
className="absolute top-4 right-4 rounded-full p-1 hover:bg-info-backdrop"
|
||||
onClick={() => {
|
||||
viewControllerManager.purchaseFlowController.closePurchaseFlow()
|
||||
}}
|
||||
>
|
||||
<Icon type="close" className="text-neutral" />
|
||||
</button>
|
||||
<SNLogoFull className="mb-5" />
|
||||
<PurchaseFlowPaneSelector
|
||||
currentPane={currentPane}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { FeatureStatus, FeatureIdentifier } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, MouseEventHandler, useCallback } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||
import Switch from '@/Components/Switch/Switch'
|
||||
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
|
||||
import { isMobileScreen } from '@/Utils'
|
||||
|
||||
type Props = {
|
||||
@@ -15,21 +11,14 @@ type Props = {
|
||||
}
|
||||
|
||||
const FocusModeSwitch: FunctionComponent<Props> = ({ application, onToggle, onClose, isEnabled }) => {
|
||||
const premiumModal = usePremiumModal()
|
||||
const isEntitled = application.features.getFeatureStatus(FeatureIdentifier.FocusMode) === FeatureStatus.Entitled
|
||||
|
||||
const toggle: MouseEventHandler = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (isEntitled) {
|
||||
onToggle(!isEnabled)
|
||||
onClose()
|
||||
} else {
|
||||
premiumModal.activate('Focused Writing')
|
||||
}
|
||||
onToggle(!isEnabled)
|
||||
onClose()
|
||||
},
|
||||
[isEntitled, onToggle, isEnabled, onClose, premiumModal],
|
||||
[onToggle, isEnabled, onClose],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -39,17 +28,8 @@ const FocusModeSwitch: FunctionComponent<Props> = ({ application, onToggle, onCl
|
||||
onClick={toggle}
|
||||
disabled={application.isNativeMobileWeb() || isMobileScreen()}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="menu-close" className="mr-2 text-neutral group-disabled:text-passive-2" />
|
||||
Focused Writing
|
||||
</div>
|
||||
{isEntitled ? (
|
||||
<Switch className="px-0" checked={isEnabled} />
|
||||
) : (
|
||||
<div title="Premium feature">
|
||||
<Icon type={PremiumFeatureIconName} className={PremiumFeatureIconClass} />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center">Focused Writing</div>
|
||||
<Switch className="px-0" checked={isEnabled} />
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
|
||||
import MenuItem from '../Menu/MenuItem'
|
||||
import { MenuItemType } from '../Menu/MenuItemType'
|
||||
import { PANEL_NAME_NAVIGATION, PANEL_NAME_NOTES } from '@/Constants/Constants'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||
|
||||
type Props = {
|
||||
@@ -55,8 +54,6 @@ const PanelSettingsSection = ({ application }: Props) => {
|
||||
|
||||
return (
|
||||
<div className="hidden text-sm md:block pointer-coarse:md-only:hidden pointer-coarse:lg-only:hidden">
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
<div className="my-1 px-3 text-sm font-semibold uppercase text-text">Panel Settings</div>
|
||||
<MenuItem
|
||||
type={MenuItemType.SwitchButton}
|
||||
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
|
||||
@@ -71,7 +68,7 @@ const PanelSettingsSection = ({ application }: Props) => {
|
||||
checked={currentItemsPanelWidth > WidthForCollapsedPanel}
|
||||
onChange={toggleItemsListPanel}
|
||||
>
|
||||
Show items list panel
|
||||
Show list panel
|
||||
</MenuItem>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -48,24 +48,28 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSet
|
||||
const [themes, setThemes] = useState<ThemeItem[]>([])
|
||||
const [toggleableComponents, setToggleableComponents] = useState<SNComponent[]>([])
|
||||
|
||||
const [isDarkModeOn, setDarkModeOn] = useState(
|
||||
const [isDarkModeOn, setDarkModeOn] = useState(() =>
|
||||
application.getPreference(PrefKey.DarkMode, PrefDefaults[PrefKey.DarkMode]),
|
||||
)
|
||||
const defaultThemeOn =
|
||||
!themes.map((item) => item?.component).find((theme) => theme?.active && !theme.isLayerable()) && !isDarkModeOn
|
||||
|
||||
useEffect(() => {
|
||||
application.addSingleEventObserver(ApplicationEvent.PreferencesChanged, async () => {
|
||||
const removeObserver = application.addEventObserver(async (event) => {
|
||||
if (event !== ApplicationEvent.PreferencesChanged) {
|
||||
return
|
||||
}
|
||||
|
||||
const isDarkModeOn = application.getPreference(PrefKey.DarkMode, PrefDefaults[PrefKey.DarkMode])
|
||||
setDarkModeOn(isDarkModeOn)
|
||||
})
|
||||
|
||||
return removeObserver
|
||||
}, [application])
|
||||
|
||||
const prefsButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const defaultThemeButtonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const mainRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
toggleFocusMode(focusModeEnabled)
|
||||
}, [focusModeEnabled])
|
||||
@@ -170,8 +174,29 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSet
|
||||
}, [application, isDarkModeOn, deactivateAnyNonLayerableTheme])
|
||||
|
||||
return (
|
||||
<div ref={mainRef}>
|
||||
<div className="my-1 px-3 text-sm font-semibold uppercase text-text">Themes</div>
|
||||
<div>
|
||||
{toggleableComponents.length > 0 && (
|
||||
<>
|
||||
<div className="my-1 px-3 text-sm font-semibold uppercase text-text">Tools</div>
|
||||
{toggleableComponents.map((component) => (
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
|
||||
onClick={() => {
|
||||
toggleComponent(component)
|
||||
}}
|
||||
key={component.uuid}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="window" className="mr-2 text-neutral" />
|
||||
{component.displayName}
|
||||
</div>
|
||||
<Switch checked={component.active} className="px-0" />
|
||||
</button>
|
||||
))}
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
</>
|
||||
)}
|
||||
<div className="my-1 px-3 text-sm font-semibold uppercase text-text">Appearance</div>
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
|
||||
onClick={toggleDefaultTheme}
|
||||
@@ -190,23 +215,6 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSet
|
||||
{themes.map((theme) => (
|
||||
<ThemesMenuButton item={theme} application={application} key={theme.component?.uuid ?? theme.identifier} />
|
||||
))}
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
<div className="my-1 px-3 text-sm font-semibold uppercase text-text">Tools</div>
|
||||
{toggleableComponents.map((component) => (
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
|
||||
onClick={() => {
|
||||
toggleComponent(component)
|
||||
}}
|
||||
key={component.uuid}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Icon type="window" className="mr-2 text-neutral" />
|
||||
{component.displayName}
|
||||
</div>
|
||||
<Switch checked={component.active} className="px-0" />
|
||||
</button>
|
||||
))}
|
||||
<FocusModeSwitch
|
||||
application={application}
|
||||
onToggle={setFocusModeEnabled}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { FeatureIdentifier, FeatureStatus, PrefKey } from '@standardnotes/snjs'
|
||||
import { FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, MouseEventHandler, useCallback, useMemo } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||
@@ -37,10 +37,6 @@ const ThemesMenuButton: FunctionComponent<Props> = ({ application, item }) => {
|
||||
|
||||
if (themeIsLayerableOrNotActive) {
|
||||
application.mutator.toggleTheme(item.component).catch(console.error)
|
||||
|
||||
if (!isThemeLayerable) {
|
||||
application.setPreference(PrefKey.DarkMode, false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
premiumModal.activate(`${item.name} theme`)
|
||||
@@ -62,10 +58,10 @@ const ThemesMenuButton: FunctionComponent<Props> = ({ application, item }) => {
|
||||
{item.component?.isLayerable() ? (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<Switch className="mr-2 px-0" checked={item.component?.active} />
|
||||
{!canActivateTheme && <Icon type={PremiumFeatureIconName} className={PremiumFeatureIconClass} />}
|
||||
{item.name}
|
||||
</div>
|
||||
{!canActivateTheme && <Icon type={PremiumFeatureIconName} className={PremiumFeatureIconClass} />}
|
||||
<Switch className="px-0" checked={item.component?.active} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -7,6 +7,7 @@ export const MediaQueryBreakpoints = {
|
||||
lg: '(min-width: 1024px)',
|
||||
xl: '(min-width: 1280px)',
|
||||
'2xl': '(min-width: 1536px)',
|
||||
pointerFine: '(pointer: fine)',
|
||||
} as const
|
||||
|
||||
export const useMediaQuery = (mediaQuery: string) => {
|
||||
|
||||
@@ -30,6 +30,12 @@
|
||||
--safe-area-inset-right: env(safe-area-inset-right, 0);
|
||||
|
||||
--sn-stylekit-font-size-editor: 0.9375rem;
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
--sn-stylekit-font-size-editor: 1rem;
|
||||
}
|
||||
|
||||
--viewport-height: 100vh;
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -55,7 +61,6 @@ body {
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
.uppercase {
|
||||
|
||||
@@ -69,10 +69,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.text-editor {
|
||||
font-size: var(--sn-stylekit-font-size-editor);
|
||||
}
|
||||
|
||||
.break-word {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user