Compare commits

...
Author SHA1 Message Date
StandardNotes CI 8863ee82bb chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2022-10-09 15:47:24 +00:00
Aman Harwara 6a4b5c5cdc fix: move viewport early return above 2022-10-09 20:39:48 +05:30
Aman Harwara cd1669f56f fix: potential fix for viewport height issue (#1772) 2022-10-09 20:37:44 +05:30
StandardNotes CI 037007f0ec chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-10-08 08:12:13 +00:00
Aman Harwara e7ca12babe fix: open upgrade page in new tab if user has account 2022-10-08 13:12:32 +05:30
StandardNotes CI c4c33db1a4 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
2022-10-07 20:41:32 +00:00
Aman Harwara 734b986294 feat: open purchase flow when clicking upgrade cta with no account (#1771) 2022-10-08 01:39:40 +05:30
Aman Harwara 59db63f052 feat: change quick settings menu layout (#1770) 2022-10-08 01:08:54 +05:30
Mo 20226c3269 fix(desktop): use different method to send messages across main and render without node integration in preload (#1769) 2022-10-07 14:30:07 -05:00
StandardNotes CI a1352d9f65 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
2022-10-07 13:57:12 +00:00
Mo 7d21046b51 feat(mobile): delete account option in settings (#1768) 2022-10-07 08:23:18 -05:00
Mo a365c17c46 fix(mobile): disable landscape for iPhone 2022-10-07 07:27:37 -05:00
StandardNotes CI c1981334e1 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].5
2022-10-07 10:51:03 +00:00
Aman Harwara 1e3acd50e9 fix: add fallback for viewport height on mobile (#1766) 2022-10-07 15:41:45 +05:30
StandardNotes CI 8de6023848 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
2022-10-07 09:13:30 +00:00
Karol Sójko 01ba715eba feat(api): add workspaces api (#1765)
* feat(api): add workspaces api

* fix(api): lint issues
2022-10-07 10:36:30 +02:00
StandardNotes CI 3733707bf1 chore(release): publish
- @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected].0
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2022-10-06 21:43:54 +00:00
Mo cbbe913cd6 feat: experimental 005 operator (#1753) 2022-10-06 11:03:43 -05:00
Mo c13dd883a4 fix(mobile): reenable bouncing in lists 2022-10-06 09:56:56 -05:00
Aman Harwara 1377846f3f fix: dark mode toggling 2022-10-06 18:53:27 +05:30
StandardNotes CI f9ee197f04 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2022-10-06 12:35:47 +00:00
Aman Harwara c944eb9365 fix: increase default editor font size on mobile 2022-10-06 17:35:53 +05:30
StandardNotes CI 4609053bd0 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
2022-10-06 11:25:46 +00:00
Aman Harwara 1c10214104 chore: remove unused dev build gh release (#1750) 2022-10-06 16:23:59 +05:30
Aman Harwara 1602d8157f fix: add android platform check for status bar color sync 2022-10-06 16:22:25 +05:30
StandardNotes CI ed1d583476 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2022-10-06 08:53:37 +00:00
Aman Harwara 05aff2776b fix: add fallback for initial window size (#1749) 2022-10-06 13:49:06 +05:30
94 changed files with 1070 additions and 244 deletions
-10
View File
@@ -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:
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.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
+4 -4
View File
@@ -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 -1
View File
@@ -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>
}
+2
View File
@@ -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
}
+1
View File
@@ -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>
}
+2
View File
@@ -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'
+34
View File
@@ -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 -1
View File
@@ -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,
+6
View File
@@ -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 -1
View File
@@ -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)
}
}
@@ -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[]>
+2 -1
View File
@@ -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'
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.23.11",
"version": "1.23.12",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.10.11",
"version": "1.10.12",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+50
View File
@@ -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 -1
View File
@@ -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",
+1 -1
View File
@@ -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>
)
}
+6
View File
@@ -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 -1
View File
@@ -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)
+40
View File
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.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 -1
View File
@@ -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",
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.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
+1 -1
View File
@@ -12,7 +12,7 @@ module.exports = {
global: {
branches: 9,
functions: 10,
lines: 17,
lines: 16,
statements: 16
}
}
+1 -1
View File
@@ -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
}
}
}
+2
View File
@@ -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'
+6
View File
@@ -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 -1
View File
@@ -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
+6
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/sncrypto-web",
"version": "1.13.0",
"version": "1.14.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+6 -6
View File
@@ -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),
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
+32 -1
View File
@@ -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
}
+5 -3
View File
@@ -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
})
+36 -2
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.134.9",
"version": "2.136.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+10
View File
@@ -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 -1
View File
@@ -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() {
+49
View File
@@ -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 -1
View File
@@ -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.",
+46 -14
View File
@@ -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) => {
+6 -1
View File
@@ -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 {
-4
View File
@@ -69,10 +69,6 @@
}
}
.text-editor {
font-size: var(--sn-stylekit-font-size-editor);
}
.break-word {
word-break: break-word;
}