mirror of
https://github.com/standardnotes/app
synced 2026-09-14 21:46:03 -04:00
Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d8a05b805 | ||
|
|
10751ea2ed | ||
|
|
6b774db222 | ||
|
|
be028ff87b | ||
|
|
5f09fc74da | ||
|
|
340f4f7f09 | ||
|
|
371a47d838 | ||
|
|
b839ebb58a | ||
|
|
482c4d1ca4 | ||
|
|
5f96281948 | ||
|
|
2e79fc412c | ||
|
|
5bdbfe9fc7 | ||
|
|
dd8ccdeadc | ||
|
|
eea97362f8 | ||
|
|
01294868c9 | ||
|
|
77ec9ca335 | ||
|
|
3e6c752967 | ||
|
|
ba017d42f5 | ||
|
|
bd3df71dd4 | ||
|
|
22c6e2c56c | ||
|
|
496525ef61 | ||
|
|
6b2f8f3da2 | ||
|
|
e57b28fe29 | ||
|
|
ba4b6a580b | ||
|
|
59fc68296b | ||
|
|
69b2af7612 | ||
|
|
30dda73e90 | ||
|
|
81ea9e896c | ||
|
|
bc9247df41 | ||
|
|
71e8645ca4 | ||
|
|
72f384c215 | ||
|
|
b65e8562f2 |
@@ -1,96 +0,0 @@
|
||||
name: Mobile TestFlight
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/web*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
android:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/mobile
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run: |
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Setup react-native kernel and increase watchers
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Decode Dev Android keystore
|
||||
run: |
|
||||
echo "${{ secrets.DEV_KEYSTORE }}" > keystore.keystore.asc
|
||||
gpg -d --passphrase "${{ secrets.DEV_KEYSTORE_PASSPHRASE }}" --batch keystore.keystore.asc > android/app/keystore.keystore
|
||||
- name: Ruby Setup for Fastlane
|
||||
uses: ruby/setup-ruby@v1
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
- run: yarn build:mobile
|
||||
- name: fastlane
|
||||
uses: maierj/[email protected]
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
|
||||
BUILD_NUMBER: ${{ github.run_number }}
|
||||
ANDROID_KEYSTORE_ALIAS: ${{ secrets.DEV_ANDROID_KEYSTORE_ALIAS }}
|
||||
ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD: ${{ secrets.DEV_ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD }}
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.DEV_ANDROID_KEYSTORE_PASSWORD }}
|
||||
GOOGLE_PLAY_JSON_KEY_DATA: ${{ secrets.GOOGLE_PLAY_JSON_KEY_DATA }}
|
||||
with:
|
||||
lane: 'android dev'
|
||||
subdirectory: 'packages/mobile'
|
||||
ios:
|
||||
env:
|
||||
ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/mobile
|
||||
runs-on: macos-11
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Export version from package.json
|
||||
run: |
|
||||
echo "PACKAGE_VERSION=$(grep '"version"' ../web/package.json | cut -d '"' -f 4 | cut -d "-" -f 1)" >> $GITHUB_ENV
|
||||
- name: Ruby Setup for Fastlane
|
||||
uses: ruby/setup-ruby@v1
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable && yarn install:pods
|
||||
- run: yarn build:mobile
|
||||
- name: Set ssh connection to Github
|
||||
uses: webfactory/[email protected]
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
- name: Export Apple AppStore Connect API key
|
||||
run: echo "${{ secrets.APPSTORE_CONNECT_KEY }}" > ios/Authkey.p8
|
||||
- name: Setup fastlane
|
||||
uses: maierj/[email protected]
|
||||
env:
|
||||
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
with:
|
||||
lane: 'ios setup'
|
||||
subdirectory: 'packages/mobile'
|
||||
- name: fastlane
|
||||
uses: maierj/[email protected]
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ env.PACKAGE_VERSION }}
|
||||
APPLE_APP_ID: ${{ secrets.DEV_APP_APPLE_ID }}
|
||||
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
|
||||
APPSTORE_CONNECT_KEY_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ISSUER_ID }}
|
||||
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
|
||||
FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
MATCH_CERTIFICATES_URL: ${{ secrets.MATCH_CERTIFICATES_URL }}
|
||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
|
||||
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
|
||||
BUILD_NUMBER: ${{ github.run_number }}
|
||||
with:
|
||||
lane: 'ios dev'
|
||||
subdirectory: 'packages/mobile'
|
||||
BIN
Binary file not shown.
+1
-1
@@ -27,7 +27,7 @@
|
||||
"build:services": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/services run build",
|
||||
"build:api": "yarn workspaces foreach -pt --topological-dev --verbose -R --from @standardnotes/api run build",
|
||||
"start:server:web": "lerna run start --scope=@standardnotes/web",
|
||||
"start:server:e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
|
||||
"e2e": "lerna run start:test-server --scope=@standardnotes/snjs",
|
||||
"reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock && yarn install",
|
||||
"release:prod": "lerna version --conventional-commits --yes -m \"chore(release): publish\"",
|
||||
"publish:prod": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.22.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **snjs:** add sign in with recovery codes use case ([#2130](https://github.com/standardnotes/app/issues/2130)) ([be028ff](https://github.com/standardnotes/app/commit/be028ff87bb5306fc266b70567cbea8a13547e7e))
|
||||
|
||||
## [1.21.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.21.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.21.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.21.2",
|
||||
"version": "1.22.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum AuthApiOperations {
|
||||
GenerateRecoveryCodes,
|
||||
GetRecoveryKeyParams,
|
||||
SignInWithRecoveryCodes,
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { ApiVersion } from '../../Api'
|
||||
import { ApiCallError } from '../../Error/ApiCallError'
|
||||
import { ErrorMessage } from '../../Error/ErrorMessage'
|
||||
import {
|
||||
GenerateRecoveryCodesResponse,
|
||||
RecoveryKeyParamsResponse,
|
||||
SignInWithRecoveryCodesResponse,
|
||||
} from '../../Response'
|
||||
import { AuthServerInterface } from '../../Server'
|
||||
|
||||
import { AuthApiOperations } from './AuthApiOperations'
|
||||
import { AuthApiServiceInterface } from './AuthApiServiceInterface'
|
||||
|
||||
export class AuthApiService implements AuthApiServiceInterface {
|
||||
private operationsInProgress: Map<AuthApiOperations, boolean>
|
||||
|
||||
constructor(private authServer: AuthServerInterface) {
|
||||
this.operationsInProgress = new Map()
|
||||
}
|
||||
|
||||
async generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse> {
|
||||
if (this.operationsInProgress.get(AuthApiOperations.GenerateRecoveryCodes)) {
|
||||
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||
}
|
||||
|
||||
this.operationsInProgress.set(AuthApiOperations.GenerateRecoveryCodes, true)
|
||||
|
||||
try {
|
||||
const response = await this.authServer.generateRecoveryCodes()
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
} finally {
|
||||
this.operationsInProgress.set(AuthApiOperations.GenerateRecoveryCodes, false)
|
||||
}
|
||||
}
|
||||
|
||||
async recoveryKeyParams(dto: {
|
||||
username: string
|
||||
codeChallenge: string
|
||||
recoveryCodes: string
|
||||
}): Promise<RecoveryKeyParamsResponse> {
|
||||
if (this.operationsInProgress.get(AuthApiOperations.GetRecoveryKeyParams)) {
|
||||
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||
}
|
||||
|
||||
this.operationsInProgress.set(AuthApiOperations.GetRecoveryKeyParams, true)
|
||||
|
||||
try {
|
||||
const response = await this.authServer.recoveryKeyParams({
|
||||
apiVersion: ApiVersion.v0,
|
||||
...dto,
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
} finally {
|
||||
this.operationsInProgress.set(AuthApiOperations.GetRecoveryKeyParams, false)
|
||||
}
|
||||
}
|
||||
|
||||
async signInWithRecoveryCodes(dto: {
|
||||
username: string
|
||||
password: string
|
||||
codeVerifier: string
|
||||
recoveryCodes: string
|
||||
}): Promise<SignInWithRecoveryCodesResponse> {
|
||||
if (this.operationsInProgress.get(AuthApiOperations.SignInWithRecoveryCodes)) {
|
||||
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||
}
|
||||
|
||||
this.operationsInProgress.set(AuthApiOperations.SignInWithRecoveryCodes, true)
|
||||
|
||||
try {
|
||||
const response = await this.authServer.signInWithRecoveryCodes({
|
||||
apiVersion: ApiVersion.v0,
|
||||
...dto,
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
} finally {
|
||||
this.operationsInProgress.set(AuthApiOperations.SignInWithRecoveryCodes, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
GenerateRecoveryCodesResponse,
|
||||
RecoveryKeyParamsResponse,
|
||||
SignInWithRecoveryCodesResponse,
|
||||
} from '../../Response'
|
||||
|
||||
export interface AuthApiServiceInterface {
|
||||
generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse>
|
||||
recoveryKeyParams(dto: {
|
||||
username: string
|
||||
codeChallenge: string
|
||||
recoveryCodes: string
|
||||
}): Promise<RecoveryKeyParamsResponse>
|
||||
signInWithRecoveryCodes(dto: {
|
||||
username: string
|
||||
password: string
|
||||
codeVerifier: string
|
||||
recoveryCodes: string
|
||||
}): Promise<SignInWithRecoveryCodesResponse>
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
export * from './Auth/AuthApiOperations'
|
||||
export * from './Auth/AuthApiService'
|
||||
export * from './Auth/AuthApiServiceInterface'
|
||||
export * from './Authenticator/AuthenticatorApiOperations'
|
||||
export * from './Authenticator/AuthenticatorApiService'
|
||||
export * from './Authenticator/AuthenticatorApiServiceInterface'
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type HttpRequestParams = Record<string, unknown>
|
||||
export type HttpRequestParams = unknown
|
||||
|
||||
@@ -276,9 +276,9 @@ export class HttpService implements HttpServiceInterface {
|
||||
}
|
||||
|
||||
private urlForUrlAndParams(url: string, params: HttpRequestParams) {
|
||||
const keyValueString = Object.keys(params)
|
||||
const keyValueString = Object.keys(params as Record<string, unknown>)
|
||||
.map((key) => {
|
||||
return key + '=' + encodeURIComponent(params[key] as string)
|
||||
return key + '=' + encodeURIComponent((params as Record<string, unknown>)[key] as string)
|
||||
})
|
||||
.join('&')
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface RecoveryKeyParamsRequestParams {
|
||||
apiVersion: string
|
||||
username: string
|
||||
codeChallenge: string
|
||||
recoveryCodes: string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface SignInWithRecoveryCodesRequestParams {
|
||||
apiVersion: string
|
||||
username: string
|
||||
password: string
|
||||
codeVerifier: string
|
||||
recoveryCodes: string
|
||||
}
|
||||
@@ -5,6 +5,8 @@ export * from './Authenticator/GenerateAuthenticatorRegistrationOptionsRequestPa
|
||||
export * from './Authenticator/ListAuthenticatorsRequestParams'
|
||||
export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseRequestParams'
|
||||
export * from './Authenticator/VerifyAuthenticatorRegistrationResponseRequestParams'
|
||||
export * from './Recovery/RecoveryKeyParamsRequestParams'
|
||||
export * from './Recovery/SignInWithRecoveryCodesRequestParams'
|
||||
export * from './Subscription/AppleIAPConfirmRequestParams'
|
||||
export * from './Subscription/SubscriptionInviteAcceptRequestParams'
|
||||
export * from './Subscription/SubscriptionInviteCancelRequestParams'
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
|
||||
import { GenerateRecoveryCodesResponseBody } from './GenerateRecoveryCodesResponseBody'
|
||||
|
||||
export interface GenerateRecoveryCodesResponse extends HttpResponse {
|
||||
data: Either<GenerateRecoveryCodesResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GenerateRecoveryCodesResponseBody {
|
||||
recoveryCodes: string
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
|
||||
import { RecoveryKeyParamsResponseBody } from './RecoveryKeyParamsResponseBody'
|
||||
|
||||
export interface RecoveryKeyParamsResponse extends HttpResponse {
|
||||
data: Either<RecoveryKeyParamsResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { KeyParamsData } from '@standardnotes/responses'
|
||||
|
||||
export interface RecoveryKeyParamsResponseBody {
|
||||
keyParams: KeyParamsData
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
|
||||
import { SignInWithRecoveryCodesResponseBody } from './SignInWithRecoveryCodesResponseBody'
|
||||
|
||||
export interface SignInWithRecoveryCodesResponse extends HttpResponse {
|
||||
data: Either<SignInWithRecoveryCodesResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { KeyParamsData, SessionBody } from '@standardnotes/responses'
|
||||
|
||||
export interface SignInWithRecoveryCodesResponseBody {
|
||||
session: SessionBody
|
||||
key_params: KeyParamsData
|
||||
user: {
|
||||
uuid: string
|
||||
email: string
|
||||
protocolVersion: string
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,12 @@ export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseResponse
|
||||
export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseResponseBody'
|
||||
export * from './Authenticator/VerifyAuthenticatorRegistrationResponseResponse'
|
||||
export * from './Authenticator/VerifyAuthenticatorRegistrationResponseResponseBody'
|
||||
export * from './Recovery/GenerateRecoveryCodesResponse'
|
||||
export * from './Recovery/GenerateRecoveryCodesResponseBody'
|
||||
export * from './Recovery/RecoveryKeyParamsResponse'
|
||||
export * from './Recovery/RecoveryKeyParamsResponseBody'
|
||||
export * from './Recovery/SignInWithRecoveryCodesResponse'
|
||||
export * from './Recovery/SignInWithRecoveryCodesResponseBody'
|
||||
export * from './Subscription/AppleIAPConfirmResponse'
|
||||
export * from './Subscription/AppleIAPConfirmResponseBody'
|
||||
export * from './Subscription/SubscriptionInviteAcceptResponse'
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { HttpServiceInterface } from '../../Http/HttpServiceInterface'
|
||||
import { RecoveryKeyParamsRequestParams, SignInWithRecoveryCodesRequestParams } from '../../Request'
|
||||
import {
|
||||
GenerateRecoveryCodesResponse,
|
||||
RecoveryKeyParamsResponse,
|
||||
SignInWithRecoveryCodesResponse,
|
||||
} from '../../Response'
|
||||
import { AuthServerInterface } from './AuthServerInterface'
|
||||
import { Paths } from './Paths'
|
||||
|
||||
export class AuthServer implements AuthServerInterface {
|
||||
constructor(private httpService: HttpServiceInterface) {}
|
||||
|
||||
async generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.generateRecoveryCodes)
|
||||
|
||||
return response as GenerateRecoveryCodesResponse
|
||||
}
|
||||
|
||||
async recoveryKeyParams(params: RecoveryKeyParamsRequestParams): Promise<RecoveryKeyParamsResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.recoveryKeyParams, params)
|
||||
|
||||
return response as RecoveryKeyParamsResponse
|
||||
}
|
||||
|
||||
async signInWithRecoveryCodes(
|
||||
params: SignInWithRecoveryCodesRequestParams,
|
||||
): Promise<SignInWithRecoveryCodesResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.signInWithRecoveryCodes, params)
|
||||
|
||||
return response as SignInWithRecoveryCodesResponse
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { RecoveryKeyParamsRequestParams, SignInWithRecoveryCodesRequestParams } from '../../Request'
|
||||
import {
|
||||
GenerateRecoveryCodesResponse,
|
||||
RecoveryKeyParamsResponse,
|
||||
SignInWithRecoveryCodesResponse,
|
||||
} from '../../Response'
|
||||
|
||||
export interface AuthServerInterface {
|
||||
generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse>
|
||||
recoveryKeyParams(params: RecoveryKeyParamsRequestParams): Promise<RecoveryKeyParamsResponse>
|
||||
signInWithRecoveryCodes(params: SignInWithRecoveryCodesRequestParams): Promise<SignInWithRecoveryCodesResponse>
|
||||
}
|
||||
@@ -2,8 +2,15 @@ const SessionPaths = {
|
||||
refreshSession: '/v1/sessions/refresh',
|
||||
}
|
||||
|
||||
const RecoveryPaths = {
|
||||
generateRecoveryCodes: '/v1/auth/recovery/codes',
|
||||
recoveryKeyParams: '/v1/auth/recovery/login-params',
|
||||
signInWithRecoveryCodes: '/v1/auth/recovery/login',
|
||||
}
|
||||
|
||||
export const Paths = {
|
||||
v1: {
|
||||
...SessionPaths,
|
||||
...RecoveryPaths,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './Auth/AuthServer'
|
||||
export * from './Auth/AuthServerInterface'
|
||||
export * from './Authenticator/AuthenticatorServer'
|
||||
export * from './Authenticator/AuthenticatorServerInterface'
|
||||
export * from './Subscription/SubscriptionServer'
|
||||
|
||||
@@ -3,6 +3,54 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.104.77](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.76](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.75](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.74](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.73](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.72](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.71](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.70](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.69](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.68](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.67](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.66](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.104.65](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.104.65",
|
||||
"version": "3.104.77",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.20.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **snjs:** add sign in with recovery codes use case ([#2130](https://github.com/standardnotes/app/issues/2130)) ([be028ff](https://github.com/standardnotes/app/commit/be028ff87bb5306fc266b70567cbea8a13547e7e))
|
||||
|
||||
## [1.19.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.19.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.19.33",
|
||||
"version": "1.20.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -32,6 +32,9 @@ export interface EncryptionProviderInterface {
|
||||
getKeyEmbeddedKeyParams(key: EncryptedPayloadInterface): SNRootKeyParams | undefined
|
||||
computeRootKey(password: string, keyParams: SNRootKeyParams): Promise<RootKeyInterface>
|
||||
supportedVersions(): ProtocolVersion[]
|
||||
isVersionNewerThanLibraryVersion(version: ProtocolVersion): boolean
|
||||
platformSupportsKeyDerivation(keyParams: SNRootKeyParams): boolean
|
||||
computeWrappingKey(passcode: string): Promise<RootKeyInterface>
|
||||
getUserVersion(): ProtocolVersion | undefined
|
||||
decryptBackupFile(
|
||||
file: BackupFile,
|
||||
|
||||
@@ -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.58.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
### Features
|
||||
|
||||
* Added per-tag preference to use table layout and removed "Files Table View" from Labs ([dd8ccde](https://github.com/standardnotes/app/commit/dd8ccdeadc6c2fd7f7ccd66f2dc4b0081616fb2b))
|
||||
|
||||
## [1.57.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/features",
|
||||
"version": "1.57.1",
|
||||
"version": "1.58.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -38,8 +38,6 @@ export enum FeatureIdentifier {
|
||||
TaskEditor = 'org.standardnotes.simple-task-editor',
|
||||
TokenVaultEditor = 'org.standardnotes.token-vault',
|
||||
|
||||
FilesTableView = 'org.standardnotes.files-table-view',
|
||||
|
||||
DeprecatedBoldEditor = 'org.standardnotes.bold-editor',
|
||||
DeprecatedMarkdownBasicEditor = 'org.standardnotes.simple-markdown-editor',
|
||||
DeprecatedMarkdownMathEditor = 'org.standardnotes.fancy-markdown-editor',
|
||||
@@ -53,4 +51,4 @@ export enum FeatureIdentifier {
|
||||
*/
|
||||
export const LegacyFileSafeIdentifier = 'org.standardnotes.legacy.file-safe'
|
||||
|
||||
export const ExperimentalFeatures = [FeatureIdentifier.FilesTableView]
|
||||
export const ExperimentalFeatures = []
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||
import { FeatureDescription } from '../Feature/FeatureDescription'
|
||||
import { FeatureIdentifier } from '../Feature/FeatureIdentifier'
|
||||
import { PermissionName } from '../Permission/PermissionName'
|
||||
|
||||
export function experimentalFeatures(): FeatureDescription[] {
|
||||
const filesTableView: FeatureDescription = {
|
||||
identifier: FeatureIdentifier.FilesTableView,
|
||||
name: 'Files Table View',
|
||||
description:
|
||||
'Replaces the current Files view with a table view, with name, size, and date sort options. Requires reload to take effect.',
|
||||
availableInSubscriptions: [SubscriptionName.PlusPlan, SubscriptionName.ProPlan],
|
||||
availableInRoles: [RoleName.PlusUser, RoleName.ProUser],
|
||||
permission_name: PermissionName.FilesTableView,
|
||||
}
|
||||
|
||||
return [filesTableView]
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -40,5 +40,4 @@ export enum PermissionName {
|
||||
TwoFactorAuth = 'server:two-factor-auth',
|
||||
SubscriptionSharing = 'server:subscription-sharing',
|
||||
SuperEditor = 'editor:super-editor',
|
||||
FilesTableView = 'app:files-table-view',
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.28.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
# [1.28.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.28.0",
|
||||
"version": "1.28.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.14.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.14.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.14.10",
|
||||
"version": "1.14.13",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,58 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.50.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.50.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
# [3.50.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
for_platform :ios do
|
||||
for_lane :dev do
|
||||
app_identifier "com.standardnotes.standardnotes.dev"
|
||||
end
|
||||
for_lane :prod do
|
||||
app_identifier "com.standardnotes.standardnotes"
|
||||
end
|
||||
end
|
||||
|
||||
for_platform :android do
|
||||
for_lane :dev do
|
||||
package_name 'com.standardnotes.dev'
|
||||
end
|
||||
for_lane :prod do
|
||||
package_name 'com.standardnotes'
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ storage_mode("git")
|
||||
|
||||
# type("development") # The default type, can be: appstore, adhoc, enterprise or development
|
||||
|
||||
app_identifier(["com.standardnotes.standardnotes", "com.standardnotes.standardnotes.dev"])
|
||||
app_identifier(["com.standardnotes.standardnotes"])
|
||||
|
||||
# For all available options run `fastlane match --help`
|
||||
# Remove the # in the beginning of the line to enable the other options
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.50.0",
|
||||
"version": "3.50.13",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -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.42.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
### Features
|
||||
|
||||
* Added per-tag preference to use table layout and removed "Files Table View" from Labs ([dd8ccde](https://github.com/standardnotes/app/commit/dd8ccdeadc6c2fd7f7ccd66f2dc4b0081616fb2b))
|
||||
|
||||
## [1.41.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
# [1.41.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.41.0",
|
||||
"version": "1.42.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -18,4 +18,5 @@ export interface TagPreferences {
|
||||
editorIdentifier?: EditorIdentifier
|
||||
entryMode?: 'normal' | 'daily'
|
||||
panelWidth?: number
|
||||
useTableView?: boolean
|
||||
}
|
||||
|
||||
@@ -3,6 +3,58 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.100](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.99](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.98](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.97](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.96](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.95](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.94](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.93](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.92](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.91](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.90](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.89](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.88](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.87](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.87",
|
||||
"version": "1.4.100",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.12.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
## [1.12.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/responses
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/responses",
|
||||
"version": "1.12.12",
|
||||
"version": "1.12.13",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.53.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **snjs:** add sign in with recovery codes use case ([#2130](https://github.com/standardnotes/app/issues/2130)) ([be028ff](https://github.com/standardnotes/app/commit/be028ff87bb5306fc266b70567cbea8a13547e7e))
|
||||
|
||||
## [1.52.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.52.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
# [1.52.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.52.0",
|
||||
"version": "1.53.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -19,6 +19,7 @@
|
||||
"@standardnotes/api": "workspace:^",
|
||||
"@standardnotes/auth": "^3.19.4",
|
||||
"@standardnotes/common": "^1.45.0",
|
||||
"@standardnotes/domain-core": "^1.11.0",
|
||||
"@standardnotes/encryption": "workspace:^",
|
||||
"@standardnotes/files": "workspace:^",
|
||||
"@standardnotes/models": "workspace:^",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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 { ComponentManagerInterface } from '../Component/ComponentManagerInterface'
|
||||
import { ApplicationEvent } from '../Event/ApplicationEvent'
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { AnyKeyParamsContent } from '@standardnotes/common'
|
||||
import { SessionBody } from '@standardnotes/responses'
|
||||
|
||||
export interface AuthClientInterface {
|
||||
generateRecoveryCodes(): Promise<string | false>
|
||||
recoveryKeyParams(dto: {
|
||||
username: string
|
||||
codeChallenge: string
|
||||
recoveryCodes: string
|
||||
}): Promise<AnyKeyParamsContent | false>
|
||||
signInWithRecoveryCodes(dto: {
|
||||
username: string
|
||||
password: string
|
||||
codeVerifier: string
|
||||
recoveryCodes: string
|
||||
}): Promise<
|
||||
| {
|
||||
keyParams: AnyKeyParamsContent
|
||||
session: SessionBody
|
||||
user: {
|
||||
uuid: string
|
||||
email: string
|
||||
protocolVersion: string
|
||||
}
|
||||
}
|
||||
| false
|
||||
>
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { AuthApiServiceInterface } from '@standardnotes/api'
|
||||
import { AnyKeyParamsContent } from '@standardnotes/common'
|
||||
import { SessionBody } from '@standardnotes/responses'
|
||||
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { AuthClientInterface } from './AuthClientInterface'
|
||||
|
||||
export class AuthManager extends AbstractService implements AuthClientInterface {
|
||||
constructor(
|
||||
private authApiService: AuthApiServiceInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
}
|
||||
|
||||
async generateRecoveryCodes(): Promise<string | false> {
|
||||
try {
|
||||
const result = await this.authApiService.generateRecoveryCodes()
|
||||
|
||||
if (result.data.error) {
|
||||
return false
|
||||
}
|
||||
|
||||
return result.data.recoveryCodes
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async recoveryKeyParams(dto: {
|
||||
username: string
|
||||
codeChallenge: string
|
||||
recoveryCodes: string
|
||||
}): Promise<AnyKeyParamsContent | false> {
|
||||
try {
|
||||
const result = await this.authApiService.recoveryKeyParams(dto)
|
||||
|
||||
if (result.data.error) {
|
||||
return false
|
||||
}
|
||||
|
||||
return result.data.keyParams as AnyKeyParamsContent
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async signInWithRecoveryCodes(dto: {
|
||||
username: string
|
||||
password: string
|
||||
codeVerifier: string
|
||||
recoveryCodes: string
|
||||
}): Promise<
|
||||
| {
|
||||
keyParams: AnyKeyParamsContent
|
||||
session: SessionBody
|
||||
user: {
|
||||
uuid: string
|
||||
email: string
|
||||
protocolVersion: string
|
||||
}
|
||||
}
|
||||
| false
|
||||
> {
|
||||
try {
|
||||
const result = await this.authApiService.signInWithRecoveryCodes(dto)
|
||||
|
||||
if (result.data.error) {
|
||||
return false
|
||||
}
|
||||
|
||||
return {
|
||||
keyParams: result.data.key_params as AnyKeyParamsContent,
|
||||
session: result.data.session,
|
||||
user: result.data.user,
|
||||
}
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { UserRegistrationResponseBody } from '@standardnotes/api'
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { SNRootKey } from '@standardnotes/encryption'
|
||||
import { RootKeyInterface } from '@standardnotes/models'
|
||||
import { ClientDisplayableError, HttpResponse, SignInResponse, User } from '@standardnotes/responses'
|
||||
import { ClientDisplayableError, HttpResponse, SessionBody, SignInResponse, User } from '@standardnotes/responses'
|
||||
import { Base64String } from '@standardnotes/sncrypto-common'
|
||||
|
||||
import { SessionManagerResponse } from './SessionManagerResponse'
|
||||
@@ -31,4 +32,13 @@ export interface SessionsClientInterface {
|
||||
wrappingKey?: RootKeyInterface
|
||||
newEmail?: string
|
||||
}): Promise<SessionManagerResponse>
|
||||
handleAuthentication(dto: {
|
||||
session: SessionBody
|
||||
user: {
|
||||
uuid: string
|
||||
email: string
|
||||
}
|
||||
rootKey: SNRootKey
|
||||
wrappingKey?: SNRootKey
|
||||
}): Promise<void>
|
||||
}
|
||||
|
||||
@@ -182,5 +182,6 @@ export const ErrorAlertStrings = {
|
||||
|
||||
export const KeychainRecoveryStrings = {
|
||||
Title: 'Restore Keychain',
|
||||
Text: "We've detected that your keychain has been wiped. This can happen when restoring your device from a backup. Please enter your account password to restore your account keys.",
|
||||
Text: (email: string) =>
|
||||
`We've detected that your keychain has been wiped. This can happen when restoring your device from a backup. Please enter your account password for "${email}" to restore your account keys.`,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Base64String } from '@standardnotes/sncrypto-common'
|
||||
import { UserRequestType } from '@standardnotes/common'
|
||||
import { DeinitSource } from '../Application/DeinitSource'
|
||||
|
||||
@@ -8,4 +9,5 @@ export interface UserClientInterface {
|
||||
}>
|
||||
signOut(force?: boolean, source?: DeinitSource): Promise<void>
|
||||
submitUserRequest(requestType: UserRequestType): Promise<boolean>
|
||||
populateSessionFromDemoShareToken(token: Base64String): Promise<void>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Base64String } from '@standardnotes/sncrypto-common'
|
||||
import { EncryptionProviderInterface, SNRootKey, SNRootKeyParams } from '@standardnotes/encryption'
|
||||
import { HttpResponse, SignInResponse, User } from '@standardnotes/responses'
|
||||
import { KeyParamsOrigination, UserRequestType } from '@standardnotes/common'
|
||||
import { Either, KeyParamsOrigination, UserRequestType } from '@standardnotes/common'
|
||||
import { UuidGenerator } from '@standardnotes/utils'
|
||||
import { UserApiServiceInterface, UserRegistrationResponseBody } from '@standardnotes/api'
|
||||
|
||||
@@ -24,6 +25,8 @@ import { DeinitSource } from '../Application/DeinitSource'
|
||||
import { StoragePersistencePolicies } from '../Storage/StorageTypes'
|
||||
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
|
||||
import { ProtectionsClientInterface } from '../Protection/ProtectionClientInterface'
|
||||
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
|
||||
import { InternalEventInterface } from '../Internal/InternalEventInterface'
|
||||
|
||||
export type CredentialsChangeFunctionResponse = { error?: { message: string } }
|
||||
export type AccountServiceResponse = HttpResponse
|
||||
@@ -33,11 +36,25 @@ export enum AccountEvent {
|
||||
SignedOut = 'SignedOut',
|
||||
}
|
||||
|
||||
type AccountEventData = {
|
||||
export interface SignedInOrRegisteredEventPayload {
|
||||
ephemeral: boolean
|
||||
mergeLocal: boolean
|
||||
awaitSync: boolean
|
||||
checkIntegrity: boolean
|
||||
}
|
||||
|
||||
export interface SignedOutEventPayload {
|
||||
source: DeinitSource
|
||||
}
|
||||
|
||||
export class UserService extends AbstractService<AccountEvent, AccountEventData> implements UserClientInterface {
|
||||
export interface AccountEventData {
|
||||
payload: Either<SignedInOrRegisteredEventPayload, SignedOutEventPayload>
|
||||
}
|
||||
|
||||
export class UserService
|
||||
extends AbstractService<AccountEvent, AccountEventData>
|
||||
implements UserClientInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private signingIn = false
|
||||
private registering = false
|
||||
|
||||
@@ -59,6 +76,43 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
|
||||
super(internalEventBus)
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === AccountEvent.SignedInOrRegistered) {
|
||||
const payload = (event.payload as AccountEventData).payload as SignedInOrRegisteredEventPayload
|
||||
this.syncService.resetSyncState()
|
||||
|
||||
await this.storageService.setPersistencePolicy(
|
||||
payload.ephemeral ? StoragePersistencePolicies.Ephemeral : StoragePersistencePolicies.Default,
|
||||
)
|
||||
|
||||
if (payload.mergeLocal) {
|
||||
await this.syncService.markAllItemsAsNeedingSyncAndPersist()
|
||||
} else {
|
||||
void this.itemManager.removeAllItemsFromMemory()
|
||||
await this.clearDatabase()
|
||||
}
|
||||
|
||||
this.unlockSyncing()
|
||||
|
||||
const syncPromise = this.syncService
|
||||
.downloadFirstSync(1_000, {
|
||||
checkIntegrity: payload.checkIntegrity,
|
||||
awaitAll: payload.awaitSync,
|
||||
})
|
||||
.then(() => {
|
||||
if (!payload.awaitSync) {
|
||||
void this.protocolService.decryptErroredPayloads()
|
||||
}
|
||||
})
|
||||
|
||||
if (payload.awaitSync) {
|
||||
await syncPromise
|
||||
|
||||
await this.protocolService.decryptErroredPayloads()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override deinit(): void {
|
||||
super.deinit()
|
||||
;(this.sessionManager as unknown) = undefined
|
||||
@@ -96,27 +150,17 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
|
||||
this.lockSyncing()
|
||||
const response = await this.sessionManager.register(email, password, ephemeral)
|
||||
|
||||
this.syncService.resetSyncState()
|
||||
await this.notifyEventSync(AccountEvent.SignedInOrRegistered, {
|
||||
payload: {
|
||||
ephemeral,
|
||||
mergeLocal,
|
||||
awaitSync: true,
|
||||
checkIntegrity: false,
|
||||
},
|
||||
})
|
||||
|
||||
await this.storageService.setPersistencePolicy(
|
||||
ephemeral ? StoragePersistencePolicies.Ephemeral : StoragePersistencePolicies.Default,
|
||||
)
|
||||
|
||||
if (mergeLocal) {
|
||||
await this.syncService.markAllItemsAsNeedingSyncAndPersist()
|
||||
} else {
|
||||
await this.itemManager.removeAllItemsFromMemory()
|
||||
await this.clearDatabase()
|
||||
}
|
||||
|
||||
await this.notifyEvent(AccountEvent.SignedInOrRegistered)
|
||||
|
||||
this.unlockSyncing()
|
||||
this.registering = false
|
||||
|
||||
await this.syncService.downloadFirstSync(300)
|
||||
void this.protocolService.decryptErroredPayloads()
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
this.unlockSyncing()
|
||||
@@ -155,39 +199,15 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
|
||||
const result = await this.sessionManager.signIn(email, password, strict, ephemeral)
|
||||
|
||||
if (!result.response.error) {
|
||||
this.syncService.resetSyncState()
|
||||
|
||||
await this.storageService.setPersistencePolicy(
|
||||
ephemeral ? StoragePersistencePolicies.Ephemeral : StoragePersistencePolicies.Default,
|
||||
)
|
||||
|
||||
if (mergeLocal) {
|
||||
await this.syncService.markAllItemsAsNeedingSyncAndPersist()
|
||||
} else {
|
||||
void this.itemManager.removeAllItemsFromMemory()
|
||||
await this.clearDatabase()
|
||||
}
|
||||
|
||||
await this.notifyEvent(AccountEvent.SignedInOrRegistered)
|
||||
|
||||
this.unlockSyncing()
|
||||
|
||||
const syncPromise = this.syncService
|
||||
.downloadFirstSync(1_000, {
|
||||
const notifyingFunction = awaitSync ? this.notifyEventSync.bind(this) : this.notifyEvent.bind(this)
|
||||
await notifyingFunction(AccountEvent.SignedInOrRegistered, {
|
||||
payload: {
|
||||
mergeLocal,
|
||||
awaitSync,
|
||||
ephemeral,
|
||||
checkIntegrity: true,
|
||||
awaitAll: awaitSync,
|
||||
})
|
||||
.then(() => {
|
||||
if (!awaitSync) {
|
||||
void this.protocolService.decryptErroredPayloads()
|
||||
}
|
||||
})
|
||||
|
||||
if (awaitSync) {
|
||||
await syncPromise
|
||||
|
||||
await this.protocolService.decryptErroredPayloads()
|
||||
}
|
||||
},
|
||||
})
|
||||
} else {
|
||||
this.unlockSyncing()
|
||||
}
|
||||
@@ -266,15 +286,14 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
|
||||
)
|
||||
|
||||
if (!response.error) {
|
||||
await this.notifyEvent(AccountEvent.SignedInOrRegistered)
|
||||
|
||||
this.unlockSyncing()
|
||||
|
||||
void this.syncService.downloadFirstSync(1_000, {
|
||||
checkIntegrity: true,
|
||||
await this.notifyEvent(AccountEvent.SignedInOrRegistered, {
|
||||
payload: {
|
||||
mergeLocal: true,
|
||||
awaitSync: true,
|
||||
ephemeral: false,
|
||||
checkIntegrity: true,
|
||||
},
|
||||
})
|
||||
|
||||
void this.protocolService.decryptErroredPayloads()
|
||||
}
|
||||
|
||||
this.unlockSyncing()
|
||||
@@ -309,7 +328,7 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
|
||||
await this.sessionManager.signOut()
|
||||
await this.protocolService.deleteWorkspaceSpecificKeyStateFromDevice()
|
||||
await this.storageService.clearAllData()
|
||||
await this.notifyEvent(AccountEvent.SignedOut, { source })
|
||||
await this.notifyEvent(AccountEvent.SignedOut, { payload: { source } })
|
||||
}
|
||||
|
||||
if (force) {
|
||||
@@ -470,6 +489,18 @@ export class UserService extends AbstractService<AccountEvent, AccountEventData>
|
||||
}
|
||||
}
|
||||
|
||||
public async populateSessionFromDemoShareToken(token: Base64String): Promise<void> {
|
||||
await this.sessionManager.populateSessionFromDemoShareToken(token)
|
||||
await this.notifyEvent(AccountEvent.SignedInOrRegistered, {
|
||||
payload: {
|
||||
ephemeral: false,
|
||||
mergeLocal: false,
|
||||
checkIntegrity: false,
|
||||
awaitSync: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private async setPasscodeWithoutWarning(passcode: string, origination: KeyParamsOrigination) {
|
||||
const identifier = UuidGenerator.GenerateUuid()
|
||||
const key = await this.protocolService.createRootKey(identifier, passcode, origination)
|
||||
|
||||
@@ -6,6 +6,8 @@ export * from './Application/ApplicationStage'
|
||||
export * from './Application/DeinitCallback'
|
||||
export * from './Application/DeinitSource'
|
||||
export * from './Application/DeinitMode'
|
||||
export * from './Auth/AuthClientInterface'
|
||||
export * from './Auth/AuthManager'
|
||||
export * from './Authenticator/AuthenticatorClientInterface'
|
||||
export * from './Authenticator/AuthenticatorManager'
|
||||
export * from './User/UserClientInterface'
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.160.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **snjs:** add getting recovery codes ([#2132](https://github.com/standardnotes/app/issues/2132)) ([10751ea](https://github.com/standardnotes/app/commit/10751ea2edb2ca48e4493b87053383f27460ef75))
|
||||
|
||||
# [2.159.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **snjs:** add sign in with recovery codes use case ([#2130](https://github.com/standardnotes/app/issues/2130)) ([be028ff](https://github.com/standardnotes/app/commit/be028ff87bb5306fc266b70567cbea8a13547e7e))
|
||||
|
||||
## [2.158.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.158.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
# [2.158.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -53,7 +53,7 @@ Once the server infrastructure is ready, and you've built all packages, you can
|
||||
|
||||
In app repo:
|
||||
```
|
||||
yarn start:server:e2e
|
||||
yarn e2e
|
||||
```
|
||||
|
||||
Once you are finished you can close the running local server on E2E repo by typing:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
AuthApiService,
|
||||
AuthenticatorApiService,
|
||||
AuthenticatorApiServiceInterface,
|
||||
AuthenticatorServer,
|
||||
AuthenticatorServerInterface,
|
||||
AuthServer,
|
||||
HttpService,
|
||||
HttpServiceInterface,
|
||||
SubscriptionApiService,
|
||||
@@ -69,6 +71,8 @@ import {
|
||||
AccountEvent,
|
||||
AuthenticatorClientInterface,
|
||||
AuthenticatorManager,
|
||||
AuthClientInterface,
|
||||
AuthManager,
|
||||
} from '@standardnotes/services'
|
||||
import { BackupServiceInterface, FilesClientInterface } from '@standardnotes/files'
|
||||
import { ComputePrivateUsername } from '@standardnotes/encryption'
|
||||
@@ -88,9 +92,12 @@ import { SNLog } from '../Log'
|
||||
import { ChallengeResponse, ListedClientInterface } from '../Services'
|
||||
import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions'
|
||||
import { ApplicationOptionsDefaults } from './Options/Defaults'
|
||||
import { LegacySession, MapperInterface, Session } from '@standardnotes/domain-core'
|
||||
import { LegacySession, MapperInterface, Session, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { SessionStorageMapper } from '@Lib/Services/Mapping/SessionStorageMapper'
|
||||
import { LegacySessionStorageMapper } from '@Lib/Services/Mapping/LegacySessionStorageMapper'
|
||||
import { SignInWithRecoveryCodes } from '@Lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes'
|
||||
import { UseCaseContainerInterface } from '@Lib/Domain/UseCase/UseCaseContainerInterface'
|
||||
import { GetRecoveryCodes } from '@Lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes'
|
||||
|
||||
/** How often to automatically sync, in milliseconds */
|
||||
const DEFAULT_AUTO_SYNC_INTERVAL = 30_000
|
||||
@@ -106,7 +113,7 @@ type ApplicationObserver = {
|
||||
|
||||
type ObserverRemover = () => void
|
||||
|
||||
export class SNApplication implements ApplicationInterface, AppGroupManagedApplication {
|
||||
export class SNApplication implements ApplicationInterface, AppGroupManagedApplication, UseCaseContainerInterface {
|
||||
onDeinit!: ExternalServices.DeinitCallback
|
||||
|
||||
/**
|
||||
@@ -168,6 +175,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
private declare authenticatorApiService: AuthenticatorApiServiceInterface
|
||||
private declare authenticatorServer: AuthenticatorServerInterface
|
||||
private declare authenticatorManager: AuthenticatorClientInterface
|
||||
private declare authManager: AuthClientInterface
|
||||
|
||||
private declare _signInWithRecoveryCodes: SignInWithRecoveryCodes
|
||||
private declare _getRecoveryCodes: GetRecoveryCodes
|
||||
|
||||
private internalEventBus!: ExternalServices.InternalEventBusInterface
|
||||
|
||||
@@ -250,6 +261,14 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return this.workspaceManager
|
||||
}
|
||||
|
||||
get signInWithRecoveryCodes(): UseCaseInterface<void> {
|
||||
return this._signInWithRecoveryCodes
|
||||
}
|
||||
|
||||
get getRecoveryCodes(): UseCaseInterface<string> {
|
||||
return this._getRecoveryCodes
|
||||
}
|
||||
|
||||
public get files(): FilesClientInterface {
|
||||
return this.fileService
|
||||
}
|
||||
@@ -1150,6 +1169,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.createAuthenticatorServer()
|
||||
this.createAuthenticatorApiService()
|
||||
this.createAuthenticatorManager()
|
||||
this.createAuthManager()
|
||||
|
||||
this.createUseCases()
|
||||
}
|
||||
|
||||
private clearServices() {
|
||||
@@ -1200,6 +1222,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
;(this.authenticatorApiService as unknown) = undefined
|
||||
;(this.authenticatorServer as unknown) = undefined
|
||||
;(this.authenticatorManager as unknown) = undefined
|
||||
;(this.authManager as unknown) = undefined
|
||||
;(this._signInWithRecoveryCodes as unknown) = undefined
|
||||
;(this._getRecoveryCodes as unknown) = undefined
|
||||
|
||||
this.services = []
|
||||
}
|
||||
@@ -1212,6 +1237,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.internalEventBus.addEventHandler(this.featuresService, ExternalServices.ApiServiceEvent.MetaReceived)
|
||||
this.internalEventBus.addEventHandler(this.integrityService, ExternalServices.SyncEvent.SyncRequestsIntegrityCheck)
|
||||
this.internalEventBus.addEventHandler(this.syncService, ExternalServices.IntegrityEvent.IntegrityCheckCompleted)
|
||||
this.internalEventBus.addEventHandler(this.userService, AccountEvent.SignedInOrRegistered)
|
||||
}
|
||||
|
||||
private clearInternalEventBus(): void {
|
||||
@@ -1348,7 +1374,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
case AccountEvent.SignedOut: {
|
||||
await this.notifyEvent(ApplicationEvent.SignedOut)
|
||||
await this.prepareForDeinit()
|
||||
this.deinit(this.getDeinitMode(), data?.source || DeinitSource.SignOut)
|
||||
this.deinit(this.getDeinitMode(), data?.payload.source || DeinitSource.SignOut)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
@@ -1581,6 +1607,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.identifier,
|
||||
{
|
||||
loadBatchSize: this.options.loadBatchSize,
|
||||
sleepBetweenBatches: this.options.sleepBetweenBatches,
|
||||
},
|
||||
this.internalEventBus,
|
||||
)
|
||||
@@ -1738,4 +1765,25 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
private createAuthenticatorManager() {
|
||||
this.authenticatorManager = new AuthenticatorManager(this.authenticatorApiService, this.internalEventBus)
|
||||
}
|
||||
|
||||
private createAuthManager() {
|
||||
const authServer = new AuthServer(this.httpService)
|
||||
|
||||
const authApiService = new AuthApiService(authServer)
|
||||
|
||||
this.authManager = new AuthManager(authApiService, this.internalEventBus)
|
||||
}
|
||||
|
||||
private createUseCases() {
|
||||
this._signInWithRecoveryCodes = new SignInWithRecoveryCodes(
|
||||
this.authManager,
|
||||
this.protocolService,
|
||||
this.inMemoryStore,
|
||||
this.options.crypto,
|
||||
this.sessionManager,
|
||||
this.internalEventBus,
|
||||
)
|
||||
|
||||
this._getRecoveryCodes = new GetRecoveryCodes(this.authManager, this.settingsService)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { ApplicationSyncOptions } from './OptionalOptions'
|
||||
import { ApplicationDisplayOptions, ApplicationSyncOptions } from './OptionalOptions'
|
||||
|
||||
export interface ApplicationOptionsWhichHaveDefaults {
|
||||
loadBatchSize: ApplicationSyncOptions['loadBatchSize']
|
||||
sleepBetweenBatches: ApplicationSyncOptions['sleepBetweenBatches']
|
||||
allowNoteSelectionStatePersistence: ApplicationDisplayOptions['allowNoteSelectionStatePersistence']
|
||||
allowMultipleSelection: ApplicationDisplayOptions['allowMultipleSelection']
|
||||
}
|
||||
|
||||
export const ApplicationOptionsDefaults: ApplicationOptionsWhichHaveDefaults = {
|
||||
loadBatchSize: 700,
|
||||
sleepBetweenBatches: 10,
|
||||
allowMultipleSelection: true,
|
||||
allowNoteSelectionStatePersistence: true,
|
||||
}
|
||||
|
||||
@@ -3,10 +3,14 @@ export interface ApplicationSyncOptions {
|
||||
* The size of the item batch to decrypt and render upon application load.
|
||||
*/
|
||||
loadBatchSize: number
|
||||
|
||||
sleepBetweenBatches: number
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ApplicationDisplayOptions {}
|
||||
export interface ApplicationDisplayOptions {
|
||||
allowNoteSelectionStatePersistence: boolean
|
||||
allowMultipleSelection: boolean
|
||||
}
|
||||
|
||||
export interface ApplicationOptionalConfiguratioOptions {
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { AuthClientInterface } from '@standardnotes/services'
|
||||
import { SettingsClientInterface } from '@Lib/Services/Settings/SettingsClientInterface'
|
||||
|
||||
import { GetRecoveryCodes } from './GetRecoveryCodes'
|
||||
|
||||
describe('GetRecoveryCodes', () => {
|
||||
let authClient: AuthClientInterface
|
||||
let settingsClient: SettingsClientInterface
|
||||
|
||||
const createUseCase = () => new GetRecoveryCodes(authClient, settingsClient)
|
||||
|
||||
beforeEach(() => {
|
||||
authClient = {} as jest.Mocked<AuthClientInterface>
|
||||
authClient.generateRecoveryCodes = jest.fn().mockResolvedValue('recovery-codes')
|
||||
|
||||
settingsClient = {} as jest.Mocked<SettingsClientInterface>
|
||||
settingsClient.getSetting = jest.fn().mockResolvedValue('existing-recovery-codes')
|
||||
})
|
||||
|
||||
it('should return existing recovery codes if they exist', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute()
|
||||
|
||||
expect(result.getValue()).toBe('existing-recovery-codes')
|
||||
})
|
||||
|
||||
it('should generate recovery codes if they do not exist', async () => {
|
||||
settingsClient.getSetting = jest.fn().mockResolvedValue(undefined)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute()
|
||||
|
||||
expect(result.getValue()).toBe('recovery-codes')
|
||||
})
|
||||
|
||||
it('should return error if recovery codes could not be generated', async () => {
|
||||
settingsClient.getSetting = jest.fn().mockResolvedValue(undefined)
|
||||
authClient.generateRecoveryCodes = jest.fn().mockResolvedValue(false)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute()
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
import { AuthClientInterface } from '@standardnotes/services'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
|
||||
import { SettingsClientInterface } from '@Lib/Services/Settings/SettingsClientInterface'
|
||||
|
||||
export class GetRecoveryCodes implements UseCaseInterface<string> {
|
||||
constructor(private authClient: AuthClientInterface, private settingsClient: SettingsClientInterface) {}
|
||||
|
||||
async execute(): Promise<Result<string>> {
|
||||
const existingRecoveryCodes = await this.settingsClient.getSetting(SettingName.RecoveryCodes)
|
||||
if (existingRecoveryCodes !== undefined) {
|
||||
return Result.ok(existingRecoveryCodes)
|
||||
}
|
||||
|
||||
const generatedRecoveryCodes = await this.authClient.generateRecoveryCodes()
|
||||
if (generatedRecoveryCodes === false) {
|
||||
return Result.fail('Could not generate recovery codes')
|
||||
}
|
||||
|
||||
return Result.ok(generatedRecoveryCodes)
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
import {
|
||||
AuthClientInterface,
|
||||
InternalEventBusInterface,
|
||||
KeyValueStoreInterface,
|
||||
SessionsClientInterface,
|
||||
} from '@standardnotes/services'
|
||||
import { EncryptionProviderInterface } from '@standardnotes/encryption'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { AnyKeyParamsContent } from '@standardnotes/common'
|
||||
import { DecryptedPayloadInterface, RootKeyContent, RootKeyInterface } from '@standardnotes/models'
|
||||
import { SessionBody } from '@standardnotes/responses'
|
||||
|
||||
import { SignInWithRecoveryCodes } from './SignInWithRecoveryCodes'
|
||||
|
||||
describe('SignInWithRecoveryCodes', () => {
|
||||
let authManager: AuthClientInterface
|
||||
let protocolService: EncryptionProviderInterface
|
||||
let inMemoryStore: KeyValueStoreInterface<string>
|
||||
let crypto: PureCryptoInterface
|
||||
let sessionManager: SessionsClientInterface
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
|
||||
const createUseCase = () => new SignInWithRecoveryCodes(
|
||||
authManager,
|
||||
protocolService,
|
||||
inMemoryStore,
|
||||
crypto,
|
||||
sessionManager,
|
||||
internalEventBus,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
authManager = {} as jest.Mocked<AuthClientInterface>
|
||||
authManager.recoveryKeyParams = jest.fn().mockReturnValue({
|
||||
identifier: '[email protected]',
|
||||
pw_nonce: 'pw_nonce',
|
||||
created: new Date().toISOString(),
|
||||
/** The event that lead to the creation of these params */
|
||||
origination: 'register',
|
||||
version: '004',
|
||||
})
|
||||
authManager.signInWithRecoveryCodes = jest.fn()
|
||||
|
||||
const rootKey = {
|
||||
serverPassword: 'foobar',
|
||||
} as jest.Mocked<RootKeyInterface>
|
||||
const payload = {} as jest.Mocked<DecryptedPayloadInterface<RootKeyContent>>
|
||||
payload.ejected = jest.fn().mockReturnValue({
|
||||
uuid: 'uuid',
|
||||
})
|
||||
rootKey.payload = payload
|
||||
|
||||
protocolService = {} as jest.Mocked<EncryptionProviderInterface>
|
||||
protocolService.hasAccount = jest.fn()
|
||||
protocolService.computeRootKey = jest.fn().mockReturnValue(rootKey)
|
||||
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(true)
|
||||
protocolService.supportedVersions = jest.fn().mockReturnValue([
|
||||
'001',
|
||||
'002',
|
||||
'003',
|
||||
'004',
|
||||
])
|
||||
protocolService.isVersionNewerThanLibraryVersion = jest.fn()
|
||||
|
||||
inMemoryStore = {} as jest.Mocked<KeyValueStoreInterface<string>>
|
||||
inMemoryStore.setValue = jest.fn()
|
||||
inMemoryStore.removeValue = jest.fn()
|
||||
|
||||
crypto = {} as jest.Mocked<PureCryptoInterface>
|
||||
crypto.generateRandomKey = jest.fn()
|
||||
crypto.base64URLEncode = jest.fn()
|
||||
crypto.sha256 = jest.fn()
|
||||
|
||||
sessionManager = {} as jest.Mocked<SessionsClientInterface>
|
||||
sessionManager.handleAuthentication = jest.fn()
|
||||
|
||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
internalEventBus.publishSync = jest.fn()
|
||||
})
|
||||
|
||||
it('should fail if an account already exists', async () => {
|
||||
protocolService.hasAccount = jest.fn().mockReturnValue(true)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toEqual('Tried to sign in when an account already exists.')
|
||||
})
|
||||
|
||||
it('should fail if recovery key params could not be retrieved', async () => {
|
||||
authManager.recoveryKeyParams = jest.fn().mockReturnValue(false)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toEqual('Could not retrieve recovery key params')
|
||||
})
|
||||
|
||||
it('should fail if key params has unsupported deriviation', async () => {
|
||||
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toEqual('Your account was created on a platform with higher security capabilities than this browser supports. If we attempted to generate your login keys here, it would take hours. Please use a browser with more up to date security capabilities, like Google Chrome or Firefox, to log in.')
|
||||
})
|
||||
|
||||
it('should fail if key params has unsupported version', async () => {
|
||||
protocolService.isVersionNewerThanLibraryVersion = jest.fn().mockReturnValue(true)
|
||||
|
||||
authManager.recoveryKeyParams = jest.fn().mockReturnValue({
|
||||
identifier: '[email protected]',
|
||||
pw_nonce: 'pw_nonce',
|
||||
created: new Date().toISOString(),
|
||||
/** The event that lead to the creation of these params */
|
||||
origination: 'register',
|
||||
version: '006',
|
||||
})
|
||||
|
||||
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toEqual('This version of the application does not support your newer account type. Please upgrade to the latest version of Standard Notes to sign in.')
|
||||
})
|
||||
|
||||
it('should fail if key params has expired version', async () => {
|
||||
protocolService.isVersionNewerThanLibraryVersion = jest.fn().mockReturnValue(false)
|
||||
|
||||
authManager.recoveryKeyParams = jest.fn().mockReturnValue({
|
||||
identifier: '[email protected]',
|
||||
pw_nonce: 'pw_nonce',
|
||||
created: new Date().toISOString(),
|
||||
/** The event that lead to the creation of these params */
|
||||
origination: 'register',
|
||||
version: '006',
|
||||
})
|
||||
|
||||
protocolService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toEqual('The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.com/help/security for more information.')
|
||||
})
|
||||
|
||||
it('should fail if the sign in with recovery codes fails', async () => {
|
||||
authManager.signInWithRecoveryCodes = jest.fn().mockReturnValue(false)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toEqual('Could not sign in with recovery codes')
|
||||
})
|
||||
|
||||
it('should sign in with recovery codes', async () => {
|
||||
authManager.signInWithRecoveryCodes = jest.fn().mockReturnValue({
|
||||
keyParams: {} as AnyKeyParamsContent,
|
||||
session: {} as SessionBody,
|
||||
user: {
|
||||
uuid: '1-2-3',
|
||||
email: '[email protected]',
|
||||
protocolVersion: '004',
|
||||
}
|
||||
})
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: '[email protected]' })
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { CopyPayloadWithContentOverride } from '@standardnotes/models'
|
||||
import {
|
||||
AccountEvent,
|
||||
AuthClientInterface,
|
||||
EXPIRED_PROTOCOL_VERSION,
|
||||
InternalEventBusInterface,
|
||||
InternalEventPublishStrategy,
|
||||
KeyValueStoreInterface,
|
||||
SessionsClientInterface,
|
||||
StorageKey,
|
||||
UNSUPPORTED_KEY_DERIVATION,
|
||||
UNSUPPORTED_PROTOCOL_VERSION,
|
||||
} from '@standardnotes/services'
|
||||
import { CreateAnyKeyParams, EncryptionProviderInterface, SNRootKey } from '@standardnotes/encryption'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
|
||||
import { SignInWithRecoveryCodesDTO } from './SignInWithRecoveryCodesDTO'
|
||||
|
||||
export class SignInWithRecoveryCodes implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private authManager: AuthClientInterface,
|
||||
private protocolService: EncryptionProviderInterface,
|
||||
private inMemoryStore: KeyValueStoreInterface<string>,
|
||||
private crypto: PureCryptoInterface,
|
||||
private sessionManager: SessionsClientInterface,
|
||||
private internalEventBus: InternalEventBusInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: SignInWithRecoveryCodesDTO): Promise<Result<void>> {
|
||||
if (this.protocolService.hasAccount()) {
|
||||
return Result.fail('Tried to sign in when an account already exists.')
|
||||
}
|
||||
|
||||
const codeVerifier = this.crypto.generateRandomKey(256)
|
||||
this.inMemoryStore.setValue(StorageKey.CodeVerifier, codeVerifier)
|
||||
|
||||
const codeChallenge = this.crypto.base64URLEncode(await this.crypto.sha256(codeVerifier))
|
||||
|
||||
const recoveryKeyParams = await this.authManager.recoveryKeyParams({
|
||||
codeChallenge,
|
||||
...dto,
|
||||
})
|
||||
|
||||
if (recoveryKeyParams === false) {
|
||||
return Result.fail('Could not retrieve recovery key params')
|
||||
}
|
||||
|
||||
const rootKeyParams = CreateAnyKeyParams(recoveryKeyParams)
|
||||
|
||||
if (!this.protocolService.supportedVersions().includes(rootKeyParams.version)) {
|
||||
if (this.protocolService.isVersionNewerThanLibraryVersion(rootKeyParams.version)) {
|
||||
return Result.fail(UNSUPPORTED_PROTOCOL_VERSION)
|
||||
}
|
||||
|
||||
return Result.fail(EXPIRED_PROTOCOL_VERSION)
|
||||
}
|
||||
|
||||
if (!this.protocolService.platformSupportsKeyDerivation(rootKeyParams)) {
|
||||
return Result.fail(UNSUPPORTED_KEY_DERIVATION)
|
||||
}
|
||||
|
||||
const rootKey = await this.protocolService.computeRootKey(dto.password, rootKeyParams)
|
||||
|
||||
const signInResult = await this.authManager.signInWithRecoveryCodes({
|
||||
codeVerifier,
|
||||
recoveryCodes: dto.recoveryCodes,
|
||||
username: dto.username,
|
||||
password: rootKey.serverPassword as string,
|
||||
})
|
||||
|
||||
if (signInResult === false) {
|
||||
return Result.fail('Could not sign in with recovery codes')
|
||||
}
|
||||
|
||||
this.inMemoryStore.removeValue(StorageKey.CodeVerifier)
|
||||
|
||||
const expandedRootKey = new SNRootKey(
|
||||
CopyPayloadWithContentOverride(rootKey.payload, {
|
||||
keyParams: signInResult.keyParams,
|
||||
}),
|
||||
)
|
||||
|
||||
await this.sessionManager.handleAuthentication({
|
||||
session: signInResult.session,
|
||||
user: signInResult.user,
|
||||
rootKey: expandedRootKey,
|
||||
})
|
||||
|
||||
await this.internalEventBus.publishSync(
|
||||
{
|
||||
type: AccountEvent.SignedInOrRegistered,
|
||||
payload: {
|
||||
payload: {
|
||||
ephemeral: false,
|
||||
mergeLocal: false,
|
||||
awaitSync: true,
|
||||
checkIntegrity: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
InternalEventPublishStrategy.SEQUENCE,
|
||||
)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface SignInWithRecoveryCodesDTO {
|
||||
recoveryCodes: string
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export interface UseCaseContainerInterface {
|
||||
get signInWithRecoveryCodes(): UseCaseInterface<void>
|
||||
get getRecoveryCodes(): UseCaseInterface<string>
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AnyKeyParamsContent } from '@standardnotes/common'
|
||||
import { AnyKeyParamsContent, KeyParamsContent004 } from '@standardnotes/common'
|
||||
import { EncryptedPayload, EncryptedTransferPayload, isErrorDecryptingPayload } from '@standardnotes/models'
|
||||
import { PreviousSnjsVersion1_0_0, PreviousSnjsVersion2_0_0, SnjsVersion } from '../Version'
|
||||
import { Migration } from '@Lib/Migrations/Migration'
|
||||
@@ -165,7 +165,7 @@ export class BaseMigration extends Migration {
|
||||
}
|
||||
|
||||
private async repairMissingKeychain() {
|
||||
const rawAccountParams = await this.reader.getAccountKeyParams()
|
||||
const rawAccountParams = (await this.reader.getAccountKeyParams()) as AnyKeyParamsContent
|
||||
|
||||
/** Choose an item to decrypt against */
|
||||
const allItems = (
|
||||
@@ -196,14 +196,14 @@ export class BaseMigration extends Migration {
|
||||
ChallengeReason.Custom,
|
||||
false,
|
||||
KeychainRecoveryStrings.Title,
|
||||
KeychainRecoveryStrings.Text,
|
||||
KeychainRecoveryStrings.Text((rawAccountParams as KeyParamsContent004).identifier),
|
||||
)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.services.challengeService.addChallengeObserver(challenge, {
|
||||
onNonvalidatedSubmit: async (challengeResponse) => {
|
||||
const password = challengeResponse.values[0].value as string
|
||||
const accountParams = this.services.protocolService.createKeyParams(rawAccountParams as AnyKeyParamsContent)
|
||||
const accountParams = this.services.protocolService.createKeyParams(rawAccountParams)
|
||||
const rootKey = await this.services.protocolService.computeRootKey(password, accountParams)
|
||||
|
||||
/** TS can't detect we returned early above if itemToDecrypt is null */
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
Challenge,
|
||||
} from '@standardnotes/services'
|
||||
import { Base64String } from '@standardnotes/sncrypto-common'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { ClientDisplayableError, SessionBody } from '@standardnotes/responses'
|
||||
import { CopyPayloadWithContentOverride } from '@standardnotes/models'
|
||||
import { isNullOrUndefined } from '@standardnotes/utils'
|
||||
import { LegacySession, MapperInterface, Session, SessionToken } from '@standardnotes/domain-core'
|
||||
@@ -306,7 +306,12 @@ export class SNSessionManager extends AbstractService<SessionEvent> implements S
|
||||
throw new ApiCallError((registerResponse.data as HttpErrorResponseBody).error.message)
|
||||
}
|
||||
|
||||
await this.handleAuthResponse(registerResponse.data, rootKey, wrappingKey)
|
||||
await this.handleAuthentication({
|
||||
rootKey,
|
||||
wrappingKey,
|
||||
session: registerResponse.data.session,
|
||||
user: registerResponse.data.user,
|
||||
})
|
||||
|
||||
return registerResponse.data
|
||||
}
|
||||
@@ -640,22 +645,30 @@ export class SNSessionManager extends AbstractService<SessionEvent> implements S
|
||||
this.setSession(session)
|
||||
}
|
||||
|
||||
private async handleAuthResponse(body: UserRegistrationResponseBody, rootKey: SNRootKey, wrappingKey?: SNRootKey) {
|
||||
async handleAuthentication(dto: {
|
||||
session: SessionBody
|
||||
user: {
|
||||
uuid: string
|
||||
email: string
|
||||
}
|
||||
rootKey: SNRootKey
|
||||
wrappingKey?: SNRootKey
|
||||
}): Promise<void> {
|
||||
const session = this.createSession(
|
||||
body.session.access_token,
|
||||
body.session.access_expiration,
|
||||
body.session.refresh_token,
|
||||
body.session.refresh_expiration,
|
||||
body.session.readonly_access,
|
||||
dto.session.access_token,
|
||||
dto.session.access_expiration,
|
||||
dto.session.refresh_token,
|
||||
dto.session.refresh_expiration,
|
||||
dto.session.readonly_access,
|
||||
)
|
||||
|
||||
if (session !== null) {
|
||||
await this.populateSession(rootKey, body.user, session, this.apiService.getHost(), wrappingKey)
|
||||
await this.populateSession(dto.rootKey, dto.user, session, this.apiService.getHost(), dto.wrappingKey)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use handleAuthResponse instead
|
||||
* @deprecated use handleAuthentication instead
|
||||
*/
|
||||
private async handleSuccessAuthResponse(
|
||||
response: Responses.SignInResponse | Responses.ChangeCredentialsResponse,
|
||||
|
||||
@@ -298,6 +298,9 @@ export class SNSyncService
|
||||
? chunks.fullEntries.remainingChunks
|
||||
: chunks.keys.remainingChunks
|
||||
|
||||
let chunkIndex = 0
|
||||
const ChunkIndexOfContentTypePriorityItems = 0
|
||||
|
||||
for (const chunk of remainingChunks) {
|
||||
const dbEntries = isChunkFullEntry(chunk)
|
||||
? chunk.entries
|
||||
@@ -314,7 +317,14 @@ export class SNSyncService
|
||||
.filter(isNotUndefined)
|
||||
|
||||
await this.processPayloadBatch(payloads, totalProcessedCount, payloadCount)
|
||||
|
||||
const shouldSleepOnlyAfterFirstRegularBatch = chunkIndex > ChunkIndexOfContentTypePriorityItems
|
||||
if (shouldSleepOnlyAfterFirstRegularBatch) {
|
||||
await sleep(this.options.sleepBetweenBatches, false, 'Sleeping to allow interface to update')
|
||||
}
|
||||
|
||||
totalProcessedCount += payloads.length
|
||||
chunkIndex++
|
||||
}
|
||||
|
||||
this.databaseLoaded = true
|
||||
@@ -353,8 +363,6 @@ export class SNSyncService
|
||||
if (currentPosition != undefined && payloadCount != undefined) {
|
||||
this.opStatus.setDatabaseLoadStatus(currentPosition, payloadCount, false)
|
||||
}
|
||||
|
||||
await sleep(1, false)
|
||||
}
|
||||
|
||||
private setLastSyncToken(token: string) {
|
||||
|
||||
@@ -20,16 +20,19 @@ describe('application instances', () => {
|
||||
})
|
||||
|
||||
it('two distinct applications should not share model manager state', async () => {
|
||||
const app1 = await Factory.createAndInitializeApplication('app1')
|
||||
const app2 = await Factory.createAndInitializeApplication('app2')
|
||||
expect(app1.payloadManager).to.equal(app1.payloadManager)
|
||||
expect(app1.payloadManager).to.not.equal(app2.payloadManager)
|
||||
const context1 = await Factory.createAppContext({ identifier: 'app1' })
|
||||
const context2 = await Factory.createAppContext({ identifier: 'app2' })
|
||||
await Promise.all([context1.launch(), context2.launch()])
|
||||
|
||||
await Factory.createMappedNote(app1)
|
||||
expect(app1.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1)
|
||||
expect(app2.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
await Factory.safeDeinit(app1)
|
||||
await Factory.safeDeinit(app2)
|
||||
expect(context1.application.payloadManager).to.equal(context1.application.payloadManager)
|
||||
expect(context1.application.payloadManager).to.not.equal(context2.application.payloadManager)
|
||||
|
||||
await Factory.createMappedNote(context1.application)
|
||||
expect(context1.application.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1)
|
||||
expect(context2.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
|
||||
await context1.deinit()
|
||||
await context2.deinit()
|
||||
})
|
||||
|
||||
it('two distinct applications should not share storage manager state', async () => {
|
||||
|
||||
@@ -16,7 +16,9 @@ describe('basic auth', function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
})
|
||||
@@ -402,7 +404,7 @@ describe('basic auth', function () {
|
||||
await this.application.syncService.markAllItemsAsNeedingSyncAndPersist()
|
||||
await this.application.syncService.sync(syncOptions)
|
||||
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
this.application = await this.context.signout()
|
||||
|
||||
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
expect(this.application.payloadManager.invalidPayloads.length).to.equal(0)
|
||||
|
||||
@@ -163,6 +163,13 @@ export class AppContext {
|
||||
return newApplication
|
||||
}
|
||||
|
||||
async signout() {
|
||||
await this.application.user.signOut()
|
||||
await this.initialize()
|
||||
await this.launch()
|
||||
return this.application
|
||||
}
|
||||
|
||||
syncWithIntegrityCheck() {
|
||||
return this.application.sync.sync({ checkIntegrity: true, awaitAll: true })
|
||||
}
|
||||
@@ -189,11 +196,46 @@ export class AppContext {
|
||||
})
|
||||
}
|
||||
|
||||
awaitUserPrefsSingletonCreation() {
|
||||
const preferences = this.application.preferencesService.preferences
|
||||
if (preferences) {
|
||||
return
|
||||
}
|
||||
|
||||
let didCompleteRelevantSync = false
|
||||
return new Promise((resolve) => {
|
||||
this.application.syncService.addEventObserver((eventName, data) => {
|
||||
if (!didCompleteRelevantSync) {
|
||||
if (data?.savedPayloads) {
|
||||
const matching = data.savedPayloads.find((p) => {
|
||||
return p.content_type === ContentType.UserPrefs
|
||||
})
|
||||
if (matching) {
|
||||
didCompleteRelevantSync = true
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
awaitUserPrefsSingletonResolution() {
|
||||
return new Promise((resolve) => {
|
||||
this.application.preferencesService.addEventObserver((eventName) => {
|
||||
if (eventName === PreferencesServiceEvent.PreferencesChanged) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async launch({ awaitDatabaseLoad = true, receiveChallenge } = { awaitDatabaseLoad: true }) {
|
||||
await this.application.prepareForLaunch({
|
||||
receiveChallenge: receiveChallenge || this.handleChallenge,
|
||||
})
|
||||
await this.application.launch(awaitDatabaseLoad)
|
||||
await this.awaitUserPrefsSingletonCreation()
|
||||
}
|
||||
|
||||
async deinit() {
|
||||
|
||||
@@ -21,7 +21,9 @@ describe('app models', () => {
|
||||
|
||||
beforeEach(async function () {
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
this.application = this.context.application
|
||||
await this.context.launch()
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -13,6 +13,7 @@ describe('importing', function () {
|
||||
let application
|
||||
let email
|
||||
let password
|
||||
let context
|
||||
|
||||
beforeEach(function () {
|
||||
localStorage.clear()
|
||||
@@ -20,11 +21,16 @@ describe('importing', function () {
|
||||
|
||||
const setup = async ({ fakeCrypto }) => {
|
||||
expectedItemCount = BaseItemCounts.DefaultItems
|
||||
|
||||
if (fakeCrypto) {
|
||||
application = await Factory.createInitAppWithFakeCrypto()
|
||||
context = await Factory.createAppContext()
|
||||
} else {
|
||||
application = await Factory.createInitAppWithRealCrypto()
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
}
|
||||
|
||||
await context.launch()
|
||||
application = context.application
|
||||
|
||||
email = UuidGenerator.GenerateUuid()
|
||||
password = UuidGenerator.GenerateUuid()
|
||||
Factory.handlePasswordChallenges(application, password)
|
||||
|
||||
@@ -9,7 +9,9 @@ const expect = chai.expect
|
||||
describe('model manager mapping', () => {
|
||||
beforeEach(async function () {
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -14,7 +14,9 @@ describe('notes and tags', () => {
|
||||
|
||||
beforeEach(async function () {
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -7,7 +7,9 @@ const expect = chai.expect
|
||||
|
||||
describe('tags as folders', () => {
|
||||
beforeEach(async function () {
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -7,7 +7,11 @@ const expect = chai.expect
|
||||
describe('preferences', function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
})
|
||||
@@ -70,10 +74,16 @@ describe('preferences', function () {
|
||||
await register.call(this)
|
||||
await this.application.setPreference('editorLeft', 300)
|
||||
await this.application.sync.sync()
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
|
||||
this.application = await this.context.signout()
|
||||
|
||||
await this.application.setPreference('editorLeft', 200)
|
||||
await this.application.signIn(this.email, this.password)
|
||||
|
||||
const promise = this.context.awaitUserPrefsSingletonResolution()
|
||||
await this.application.sync.sync({ awaitAll: true })
|
||||
await promise
|
||||
|
||||
const editorLeft = this.application.getPreference('editorLeft')
|
||||
expect(editorLeft).to.equal(300)
|
||||
})
|
||||
|
||||
@@ -31,7 +31,11 @@ describe('singletons', function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
this.registerUser = async () => {
|
||||
|
||||
@@ -16,7 +16,9 @@ describe('storage manager', function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.expectedKeyCount = BASE_KEY_COUNT
|
||||
this.application = await Factory.createInitAppWithFakeCrypto(Environment.Mobile)
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
})
|
||||
@@ -221,7 +223,8 @@ describe('storage manager', function () {
|
||||
expect(decrypted.content).to.be.an.instanceof(Object)
|
||||
})
|
||||
|
||||
it('disabling storage encryption should store items without encryption', async function () {
|
||||
/** @TODO: Storage encryption disable is no longer available, remove tests and associated functionality */
|
||||
it.skip('disabling storage encryption should store items without encryption', async function () {
|
||||
await Factory.registerUserToApplication({
|
||||
application: this.application,
|
||||
email: this.email,
|
||||
|
||||
@@ -13,7 +13,9 @@ describe('offline syncing', () => {
|
||||
|
||||
beforeEach(async function () {
|
||||
this.expectedItemCount = BaseItemCounts.DefaultItems
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
this.application = this.context.application
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('online syncing', function () {
|
||||
|
||||
await this.application.sync.sync(syncOptions)
|
||||
|
||||
this.application = await Factory.signOutApplicationAndReturnNew(this.application)
|
||||
this.application = await this.context.signout()
|
||||
|
||||
expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems)
|
||||
|
||||
|
||||
@@ -7,7 +7,11 @@ const expect = chai.expect
|
||||
describe('upgrading', () => {
|
||||
beforeEach(async function () {
|
||||
localStorage.clear()
|
||||
this.application = await Factory.createInitAppWithFakeCrypto()
|
||||
|
||||
this.context = await Factory.createAppContext()
|
||||
await this.context.launch()
|
||||
|
||||
this.application = this.context.application
|
||||
this.email = UuidGenerator.GenerateUuid()
|
||||
this.password = UuidGenerator.GenerateUuid()
|
||||
this.passcode = '1234'
|
||||
@@ -170,7 +174,7 @@ describe('upgrading', () => {
|
||||
/** Delete default items key that is created on launch */
|
||||
const itemsKey = await this.application.protocolService.getSureDefaultItemsKey()
|
||||
await this.application.itemManager.setItemToBeDeleted(itemsKey)
|
||||
expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(0)
|
||||
expect(Uuids(this.application.itemManager.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false)
|
||||
|
||||
Factory.createMappedNote(this.application)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.158.0",
|
||||
"version": "2.160.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -45,7 +45,7 @@
|
||||
"@standardnotes/responses": "workspace:*",
|
||||
"@standardnotes/security": "^1.7.0",
|
||||
"@standardnotes/services": "workspace:*",
|
||||
"@standardnotes/settings": "^1.18.4",
|
||||
"@standardnotes/settings": "^1.19.0",
|
||||
"@standardnotes/sncrypto-common": "workspace:*",
|
||||
"@standardnotes/sncrypto-web": "workspace:*",
|
||||
"@standardnotes/utils": "workspace:*",
|
||||
|
||||
@@ -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.3.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/toast
|
||||
|
||||
## [1.3.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where file upload toast would close before completion if you switched windows ([30dda73](https://github.com/standardnotes/app/commit/30dda73e9078bbccf91c3f43307250c84a17e8bd))
|
||||
|
||||
## [1.3.15](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2022-12-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/toast
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/toast",
|
||||
"version": "1.3.15",
|
||||
"version": "1.3.17",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -27,8 +27,12 @@ export const ToastTimer: FunctionComponent<Props> = ({ toast, index }) => {
|
||||
const remainingTimeRef = useRef(duration)
|
||||
|
||||
const dismissToastOnEnd = useCallback(() => {
|
||||
if (!shouldAutoClose) {
|
||||
return
|
||||
}
|
||||
|
||||
dismissToast(toast.id)
|
||||
}, [toast.id])
|
||||
}, [shouldAutoClose, toast.id])
|
||||
|
||||
const clearTimer = useCallback(() => {
|
||||
if (toastTimerIdRef.current) {
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.23.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.23.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where the importer would not correctly parse Google Keep JSON notes ([2e79fc4](https://github.com/standardnotes/app/commit/2e79fc412c8b57b175381c4f59593a03906d89a1))
|
||||
|
||||
## [1.23.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.23.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
# [1.23.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
### Features
|
||||
|
||||
* Added keyboard navigation to Files table view ([#2131](https://github.com/standardnotes/app/issues/2131)) ([ba017d4](https://github.com/standardnotes/app/commit/ba017d42f5145a3f9fb4a4610d7582bd8571b29c))
|
||||
|
||||
## [1.22.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.22.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.22.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.22.2",
|
||||
"version": "1.23.4",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -98,13 +98,13 @@ export class GoogleKeepConverter {
|
||||
|
||||
static isValidGoogleKeepJson(json: any): boolean {
|
||||
return (
|
||||
json.title &&
|
||||
json.textContent &&
|
||||
json.userEditedTimestampUsec &&
|
||||
typeof json.title === 'string' &&
|
||||
typeof json.textContent === 'string' &&
|
||||
typeof json.userEditedTimestampUsec === 'number' &&
|
||||
typeof json.isArchived === 'boolean' &&
|
||||
typeof json.isTrashed === 'boolean' &&
|
||||
typeof json.isPinned === 'boolean' &&
|
||||
json.color
|
||||
typeof json.color === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,4 +10,6 @@ export enum KeyboardKey {
|
||||
Home = 'Home',
|
||||
End = 'End',
|
||||
Space = ' ',
|
||||
PageUp = 'PageUp',
|
||||
PageDown = 'PageDown',
|
||||
}
|
||||
|
||||
@@ -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.15.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/utils
|
||||
|
||||
## [1.15.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/utils
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/utils",
|
||||
"version": "1.15.1",
|
||||
"version": "1.15.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -519,9 +519,9 @@ export function truncateHexString(string: string, desiredBits: number) {
|
||||
* When awaited, this function allows code execution to pause for a set time.
|
||||
* Should be used primarily for testing.
|
||||
*/
|
||||
export async function sleep(milliseconds: number, warn = true): Promise<void> {
|
||||
export async function sleep(milliseconds: number, warn = true, desc = ''): Promise<void> {
|
||||
if (warn) {
|
||||
console.warn(`Sleeping for ${milliseconds}ms`)
|
||||
console.warn(`Sleeping for ${milliseconds}ms ${desc}`)
|
||||
}
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(function () {
|
||||
|
||||
@@ -3,6 +3,67 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.136.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.136.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.136.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where search button would not show up in Files view ([340f4f7](https://github.com/standardnotes/app/commit/340f4f7f09c28f7818cb4a7f1ccaa387ef0283c9))
|
||||
|
||||
## [3.136.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed issue where the "Attached to" in table view would report an extra number of links ([482c4d1](https://github.com/standardnotes/app/commit/482c4d1ca4499ae4e80f592176648ee2c03f16b8))
|
||||
|
||||
## [3.136.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
# [3.136.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-06)
|
||||
|
||||
### Features
|
||||
|
||||
* Added per-tag preference to use table layout and removed "Files Table View" from Labs ([dd8ccde](https://github.com/standardnotes/app/commit/dd8ccdeadc6c2fd7f7ccd66f2dc4b0081616fb2b))
|
||||
|
||||
## [3.135.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
# [3.135.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
### Features
|
||||
|
||||
* Added keyboard navigation to Files table view ([#2131](https://github.com/standardnotes/app/issues/2131)) ([ba017d4](https://github.com/standardnotes/app/commit/ba017d42f5145a3f9fb4a4610d7582bd8571b29c))
|
||||
|
||||
# [3.134.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-05)
|
||||
|
||||
### Features
|
||||
|
||||
* Added linked items container & autocomplete input to file preview modal ([22c6e2c](https://github.com/standardnotes/app/commit/22c6e2c56c656cdbbc9788e5e9dc9112b22a9127))
|
||||
|
||||
# [3.133.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
### Features
|
||||
|
||||
* Added "Add tag" option to file context menu ([ba4b6a5](https://github.com/standardnotes/app/commit/ba4b6a580b2b18d2c383475c8723fb66fa9fecdf))
|
||||
* Added file options menu to file preview modal ([e57b28f](https://github.com/standardnotes/app/commit/e57b28fe29f230754c247bf2a769355a7fbd30cb))
|
||||
|
||||
## [3.132.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.132.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
# [3.132.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-01-03)
|
||||
|
||||
### Features
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user