Compare commits

...
Author SHA1 Message Date
StandardNotes CI a7f266bb68 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-04 13:30:52 +00:00
Karol Sójko c42a559955 chore: remove e2e tests that are invalid due to session invalidation upon password change (#2384)
* chore: remove e2e tests that are invalid due to session invalidation upon password change

* chore: move invalid session e2e tests to a skipped suite for revisiting
2023-08-04 15:02:31 +02:00
StandardNotes CI afc32121ee chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-04 11:26:54 +00:00
Aman Harwara 1abacff890 chore: fix blur issues on alert dialogs on chrome [skip e2e] 2023-08-04 16:37:28 +05:30
Aman Harwara fc500b08d2 chore: fix issue where popovers & modals are sometimes blurry on chrome 2023-08-04 16:22:14 +05:30
StandardNotes CI 0642b589ff chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-03 19:48:59 +00:00
Aman Harwara f7a1c6ea65 fix: Fixed issue where moving the floating keyboard on iPad would lead to blank space on screen 2023-08-04 00:30:12 +05:30
StandardNotes CI e8c7c10358 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected]
2023-08-03 14:42:10 +00:00
Aman Harwara b3eab998f9 chore: fix colors 2023-08-03 19:17:47 +05:30
Aman Harwara c76ffc764b clipper: handle clipped note sync in background (#2367) 2023-08-03 18:43:04 +05:30
Aman Harwara 5602a4014e chore: improve super formatting toolbar contrast 2023-08-03 18:24:50 +05:30
58 changed files with 664 additions and 302 deletions
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/api
## [1.26.39](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api",
"version": "1.26.39",
"version": "1.26.40",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+1 -1
View File
@@ -181,7 +181,7 @@ export class HttpService implements HttpServiceInterface {
return response
}
private async refreshSession(): Promise<boolean> {
async refreshSession(): Promise<boolean> {
if (!this.session) {
return false
}
@@ -14,6 +14,7 @@ export interface HttpServiceInterface {
runHttp<T>(httpRequest: HttpRequest): Promise<HttpResponse<T>>
setSession(session: Session | LegacySession): void
refreshSession(): Promise<boolean>
setCallbacks(
updateMetaCallback: (meta: HttpResponseMeta) => void,
refreshSessionCallback: (session: Session) => void,
+1
View File
@@ -1,3 +1,4 @@
export * from './HttpService'
export * from './FetchRequestHandler'
export * from './HttpServiceInterface'
export * from './XMLHttpRequestState'
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.137](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.136](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.135](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.134](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/clipper
## [1.1.133](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/clipper
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/clipper",
"description": "Web clipper browser extension for Standard Notes",
"version": "1.1.133",
"version": "1.1.137",
"private": true,
"scripts": {
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
@@ -1,5 +1,7 @@
import { runtime, action, browserAction, windows, storage, tabs } from 'webextension-polyfill'
import { ClipPayload, RuntimeMessage, RuntimeMessageTypes } from '../types/message'
import { Environment, FetchRequestHandler, Logger, SnjsVersion } from '@standardnotes/snjs'
import packageInfo from '../../package.json'
const isFirefox = navigator.userAgent.indexOf('Firefox/') !== -1
@@ -22,6 +24,9 @@ const openPopupAndClipSelection = async (payload: ClipPayload) => {
void openPopup()
}
const logger = new Logger('clipper')
const requestHandler = new FetchRequestHandler(SnjsVersion, packageInfo.version, Environment.Clipper, logger)
runtime.onMessage.addListener(async (message: RuntimeMessage) => {
if (message.type === RuntimeMessageTypes.OpenPopupWithSelection) {
if (!message.payload) {
@@ -32,5 +37,7 @@ runtime.onMessage.addListener(async (message: RuntimeMessage) => {
return await tabs.captureVisibleTab(undefined, {
format: 'png',
})
} else if (message.type === RuntimeMessageTypes.RunHttpRequest) {
requestHandler.handleRequest(message.payload).catch(console.error)
}
})
+14 -1
View File
@@ -1,3 +1,5 @@
import { HttpRequest } from '@standardnotes/snjs'
export const RuntimeMessageTypes = {
GetArticle: 'get-article',
GetSelection: 'get-selection',
@@ -7,6 +9,7 @@ export const RuntimeMessageTypes = {
StartNodeSelection: 'start-node-selection',
ToggleScreenshotMode: 'toggle-screenshot-mode',
CaptureVisibleTab: 'capture-visible-tab',
RunHttpRequest: 'run-http-request',
} as const
export type RuntimeMessageType = (typeof RuntimeMessageTypes)[keyof typeof RuntimeMessageTypes]
@@ -29,6 +32,7 @@ export type RuntimeMessageReturnTypes = {
[RuntimeMessageTypes.OpenPopupWithSelection]: void
[RuntimeMessageTypes.StartNodeSelection]: void
[RuntimeMessageTypes.ToggleScreenshotMode]: void
[RuntimeMessageTypes.RunHttpRequest]: void
}
export type RuntimeMessage =
@@ -36,10 +40,19 @@ export type RuntimeMessage =
type: MessagesWithClipPayload
payload: ClipPayload
}
| {
type: typeof RuntimeMessageTypes.RunHttpRequest
payload: HttpRequest
}
| {
type: typeof RuntimeMessageTypes.ToggleScreenshotMode
enabled: boolean
}
| {
type: Exclude<RuntimeMessageType, MessagesWithClipPayload | typeof RuntimeMessageTypes.ToggleScreenshotMode>
type: Exclude<
RuntimeMessageType,
| MessagesWithClipPayload
| typeof RuntimeMessageTypes.ToggleScreenshotMode
| typeof RuntimeMessageTypes.RunHttpRequest
>
}
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.108.68](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.67](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.66](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.65](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/desktop
## [3.108.64](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/desktop
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.108.64",
"version": "3.108.68",
"license": "CC BY-NC-SA 4.0",
"author": "Standard Notes.",
"private": true,
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.62](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/encryption
## [1.21.61](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
**Note:** Version bump only for package @standardnotes/encryption
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/encryption",
"version": "1.21.61",
"version": "1.21.62",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.28.72](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/filepicker
## [1.28.71](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
**Note:** Version bump only for package @standardnotes/filepicker
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/filepicker",
"version": "1.28.71",
"version": "1.28.72",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.16.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/files
## [1.16.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
**Note:** Version bump only for package @standardnotes/files
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files",
"version": "1.16.17",
"version": "1.16.18",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.56.48](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.47](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.46](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
### Bug Fixes
* Fixed issue where moving the floating keyboard on iPad would lead to blank space on screen ([f7a1c6e](https://github.com/standardnotes/app/commit/f7a1c6ea65201af80ca6859c0e83c5b49c94e11d))
## [3.56.45](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/mobile
## [3.56.44](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/mobile
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.56.44",
"version": "3.56.48",
"author": "Standard Notes.",
"private": true,
"license": "CC BY-NC-SA 4.0",
@@ -2,7 +2,7 @@
import { ApplicationEvent, ReactNativeToWebEvent } from '@standardnotes/snjs'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Button, Keyboard, Platform, Text, View } from 'react-native'
import { Button, Dimensions, Keyboard, Platform, Text, View } from 'react-native'
import VersionInfo from 'react-native-version-info'
import { WebView, WebViewMessageEvent } from 'react-native-webview'
import { OnShouldStartLoadWithRequest } from 'react-native-webview/lib/WebViewTypes'
@@ -84,7 +84,11 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
JSON.stringify({
reactNativeEvent: ReactNativeToWebEvent.KeyboardFrameWillChange,
messageType: 'event',
messageData: { height: e.endCoordinates.height, contentHeight: e.endCoordinates.screenY },
messageData: {
height: e.endCoordinates.height,
contentHeight: e.endCoordinates.screenY,
isFloatingKeyboard: e.endCoordinates.width !== Dimensions.get('window').width,
},
}),
)
})
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.46.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/models
## [1.46.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-02)
**Note:** Version bump only for package @standardnotes/models
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/models",
"version": "1.46.20",
"version": "1.46.21",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -2,4 +2,5 @@ export enum Environment {
Web = 1,
Desktop = 2,
Mobile = 3,
Clipper = 4,
}
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.420](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.419](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.418](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.417](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.416](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.4.416",
"version": "1.4.420",
"license": "CC BY-NC-SA 4.0",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.63.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/services
## [1.63.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/services",
"version": "1.63.21",
"version": "1.63.22",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -2,8 +2,8 @@ import { FilesApiInterface } from '@standardnotes/files'
import { AbstractService } from '../Service/AbstractService'
import { ApiServiceEvent } from './ApiServiceEvent'
import { ApiServiceEventData } from './ApiServiceEventData'
import { SNFeatureRepo } from '@standardnotes/models'
import { ClientDisplayableError, HttpResponse } from '@standardnotes/responses'
import { SNFeatureRepo, ServerSyncPushContextualPayload } from '@standardnotes/models'
import { ClientDisplayableError, HttpRequest, HttpResponse } from '@standardnotes/responses'
import { AnyFeatureDescription } from '@standardnotes/features'
export interface LegacyApiServiceInterface
@@ -16,4 +16,12 @@ export interface LegacyApiServiceInterface
): Promise<{ features: AnyFeatureDescription[]; roles: string[] } | ClientDisplayableError>
downloadFeatureUrl(url: string): Promise<HttpResponse>
getSyncHttpRequest(
payloads: ServerSyncPushContextualPayload[],
lastSyncToken: string | undefined,
paginationToken: string | undefined,
limit: number,
sharedVaultUuids?: string[],
): HttpRequest
}
@@ -25,6 +25,7 @@ export interface SessionsClientInterface {
isSignedIntoFirstPartyServer(): boolean
getSessionsList(): Promise<HttpResponse<SessionListEntry[]>>
refreshSessionIfExpiringSoon(): Promise<boolean>
revokeSession(sessionId: string): Promise<HttpResponse<SessionListResponse>>
revokeAllOtherSessions(): Promise<void>
@@ -1,13 +1,18 @@
/* istanbul ignore file */
import { FullyFormedPayloadInterface } from '@standardnotes/models'
import { DecryptedItemInterface, DeletedItemInterface, FullyFormedPayloadInterface } from '@standardnotes/models'
import { SyncOptions } from './SyncOptions'
import { AbstractService } from '../Service/AbstractService'
import { SyncEvent } from '../Event/SyncEvent'
import { SyncOpStatus } from './SyncOpStatus'
import { HttpRequest } from '@standardnotes/responses'
export interface SyncServiceInterface extends AbstractService<SyncEvent> {
sync(options?: Partial<SyncOptions>): Promise<unknown>
getRawSyncRequestForExternalUse(
items: (DecryptedItemInterface | DeletedItemInterface)[],
): Promise<HttpRequest | undefined>
isDatabaseLoaded(): boolean
onNewDatabaseCreated(): Promise<void>
loadDatabasePayloads(): Promise<void>
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.202.29](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/snjs
## [2.202.28](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/snjs
## [2.202.27](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
### Bug Fixes
@@ -33,6 +33,7 @@ export function environmentToString(environment: Environment) {
[Environment.Web]: 'web',
[Environment.Desktop]: 'desktop',
[Environment.Mobile]: 'native-mobile-web',
[Environment.Clipper]: 'clipper',
}
return map[environment]
}
+25 -9
View File
@@ -374,15 +374,8 @@ export class LegacyApiService
if (preprocessingError) {
return preprocessingError
}
const path = Paths.v1.sync
const params = this.params({
[ApiEndpointParam.SyncPayloads]: payloads,
[ApiEndpointParam.LastSyncToken]: lastSyncToken,
[ApiEndpointParam.PaginationToken]: paginationToken,
[ApiEndpointParam.SyncDlLimit]: limit,
[ApiEndpointParam.SharedVaultUuids]: sharedVaultUuids,
})
const response = await this.httpService.post<RawSyncResponse>(path, params, this.getSessionAccessToken())
const request = this.getSyncHttpRequest(payloads, lastSyncToken, paginationToken, limit, sharedVaultUuids)
const response = await this.httpService.runHttp<RawSyncResponse>(request)
if (isErrorResponse(response)) {
this.preprocessAuthenticatedErrorResponse(response)
@@ -394,6 +387,29 @@ export class LegacyApiService
return response
}
getSyncHttpRequest(
payloads: ServerSyncPushContextualPayload[],
lastSyncToken: string | undefined,
paginationToken: string | undefined,
limit: number,
sharedVaultUuids?: string[] | undefined,
): HttpRequest {
const path = Paths.v1.sync
const params = this.params({
[ApiEndpointParam.SyncPayloads]: payloads,
[ApiEndpointParam.LastSyncToken]: lastSyncToken,
[ApiEndpointParam.PaginationToken]: paginationToken,
[ApiEndpointParam.SyncDlLimit]: limit,
[ApiEndpointParam.SharedVaultUuids]: sharedVaultUuids,
})
return {
url: joinPaths(this.host, path),
params,
verb: HttpVerb.Post,
authentication: this.getSessionAccessToken(),
}
}
async refreshSession(): Promise<HttpResponse<SessionRenewalResponse>> {
const preprocessingError = this.preprocessingError()
if (preprocessingError) {
@@ -71,6 +71,7 @@ import {
export const MINIMUM_PASSWORD_LENGTH = 8
export const MissingAccountParams = 'missing-params'
const ThirtyMinutes = 30 * 60 * 1000
const cleanedEmailString = (email: string) => {
return email.trim().toLowerCase()
@@ -837,4 +838,27 @@ export class SessionManager
return Result.ok(sessionOrError.getValue())
}
async refreshSessionIfExpiringSoon(): Promise<boolean> {
const session = this.getSession()
if (!session) {
return false
}
if (session instanceof LegacySession) {
return false
}
const accessTokenExpiration = new Date(session.accessToken.expiresAt)
const refreshTokenExpiration = new Date(session.refreshToken.expiresAt)
const willAccessTokenExpireSoon = accessTokenExpiration.getTime() - Date.now() < ThirtyMinutes
const willRefreshTokenExpireSoon = refreshTokenExpiration.getTime() - Date.now() < ThirtyMinutes
if (willAccessTokenExpireSoon || willRefreshTokenExpireSoon) {
return this.httpService.refreshSession()
}
return false
}
}
+21 -1
View File
@@ -1,4 +1,4 @@
import { ConflictParams, ConflictType } from '@standardnotes/responses'
import { ConflictParams, ConflictType, HttpRequest } from '@standardnotes/responses'
import { AccountSyncOperation } from '@Lib/Services/Sync/Account/Operation'
import {
LoggerInterface,
@@ -922,6 +922,26 @@ export class SyncService
return undefined
}
async getRawSyncRequestForExternalUse(
items: (DecryptedItemInterface | DeletedItemInterface)[],
): Promise<HttpRequest | undefined> {
if (this.dealloced) {
return
}
const online = this.sessionManager.online()
if (!online) {
return
}
const payloads = await this.payloadsByPreparingForServer(items.map((i) => i.payloadRepresentation()))
const syncToken = await this.getLastSyncToken()
const paginationToken = await this.getPaginationToken()
return this.apiService.getSyncHttpRequest(payloads, syncToken, paginationToken, 150)
}
private async handleOfflineResponse(response: OfflineSyncResponse) {
this.logger.debug('Offline Sync Response', response)
@@ -53,6 +53,7 @@ export const BaseTests = [
'preferences.test.js',
'files.test.js',
'session.test.js',
'session-invalidation.test.js',
'subscriptions.test.js',
'recovery.test.js',
]
@@ -132,63 +132,6 @@ describe('key recovery service', function () {
await context.deinit()
})
it('recovered keys with key params not matching servers should not be synced if local root key does not match server', async function () {
/**
* Assume Application A has been through these states:
* 1. Registration + Items Key A + Root Key A
* 2. Password change + Items Key B + Root Key B
* 3. Password change + Items Key C + Root Key C + Failure to correctly re-encrypt Items Key A and B with Root Key C
*
* Application B is not correctly in sync, and is only at State 1 (Registration + Items Key A)
*
* Application B receives Items Key B of Root Key B but for whatever reason ignores Items Key C of Root Key C.
*
* When it recovers Items Key B, it should not re-upload it to the server, because Application B's Root Key is not
* the current account's root key.
*/
const contextA = await Factory.createAppContextWithFakeCrypto()
await contextA.launch()
await contextA.register()
contextA.preventKeyRecoveryOfKeys()
const contextB = await Factory.createAppContextWithFakeCrypto('app-b', contextA.email, contextA.password)
await contextB.launch()
await contextB.signIn()
await contextA.changePassword('new-password-1')
const itemsKeyARootKeyB = contextA.itemsKeys[0]
const itemsKeyBRootKeyB = contextA.itemsKeys[1]
contextA.disableSyncingOfItems([itemsKeyARootKeyB.uuid, itemsKeyBRootKeyB.uuid])
await contextA.changePassword('new-password-2')
const itemsKeyCRootKeyC = contextA.itemsKeys[2]
contextB.disableKeyRecoveryServerSignIn()
contextB.preventKeyRecoveryOfKeys([itemsKeyCRootKeyC.uuid])
contextB.respondToAccountPasswordChallengeWith('new-password-1')
const recoveryPromise = Promise.all([
contextB.resolveWhenKeyRecovered(itemsKeyARootKeyB.uuid),
contextB.resolveWhenKeyRecovered(itemsKeyBRootKeyB.uuid),
])
const observedDirtyItemUuids = []
contextB.spyOnChangedItems((changed) => {
const dirty = changed.filter((i) => i.dirty)
extendArray(observedDirtyItemUuids, Uuids(dirty))
})
await contextB.sync()
await recoveryPromise
expect(observedDirtyItemUuids.includes(itemsKeyARootKeyB.uuid)).to.equal(false)
expect(observedDirtyItemUuids.includes(itemsKeyBRootKeyB.uuid)).to.equal(false)
await contextA.deinit()
await contextB.deinit()
})
it('when encountering many undecryptable items key with same key params, should only prompt once', async function () {
const namespace = Factory.randomString()
const unassociatedPassword = 'randfoo'
@@ -251,111 +194,6 @@ describe('key recovery service', function () {
await context.deinit()
})
it('when changing password on client B, client A should perform recovery flow', async function () {
const contextA = await Factory.createAppContextWithFakeCrypto()
await contextA.launch()
await contextA.register()
const originalItemsKey = contextA.application.items.getDisplayableItemsKeys()[0]
const contextB = await Factory.createAppContextWithFakeCrypto(
'another-namespace',
contextA.email,
contextA.password,
)
contextB.ignoreChallenges()
await contextB.launch()
await contextB.signIn()
const newPassword = `${Math.random()}`
const result = await contextB.application.changePassword(contextA.password, newPassword)
expect(result.error).to.not.be.ok
expect(contextB.application.items.getAnyItems(ContentType.TYPES.ItemsKey).length).to.equal(2)
const newItemsKey = contextB.application.items
.getDisplayableItemsKeys()
.find((k) => k.uuid !== originalItemsKey.uuid)
const note = await Factory.createSyncedNote(contextB.application)
const recoveryPromise = contextA.resolveWhenKeyRecovered(newItemsKey.uuid)
contextA.password = newPassword
await contextA.sync(syncOptions)
await recoveryPromise
/** Same previously errored key should now no longer be errored, */
expect(contextA.application.items.getAnyItems(ContentType.TYPES.ItemsKey).length).to.equal(2)
for (const key of contextA.application.items.getDisplayableItemsKeys()) {
expect(key.errorDecrypting).to.not.be.ok
}
const aKey = await contextA.application.encryption.getRootKey()
const bKey = await contextB.application.encryption.getRootKey()
expect(aKey.compare(bKey)).to.equal(true)
expect(contextA.application.items.findItem(note.uuid).errorDecrypting).to.not.be.ok
expect(contextB.application.items.findItem(note.uuid).errorDecrypting).to.not.be.ok
expect(contextA.application.sync.isOutOfSync()).to.equal(false)
expect(contextB.application.sync.isOutOfSync()).to.equal(false)
await contextA.deinit()
await contextB.deinit()
}).timeout(80000)
it('when items key associated with item is errored, item should be marked waiting for key', async function () {
const namespace = Factory.randomString()
const newPassword = `${Math.random()}`
const contextA = await Factory.createAppContextWithFakeCrypto(namespace)
const appA = contextA.application
await appA.prepareForLaunch({ receiveChallenge: () => {} })
await appA.launch(true)
await Factory.registerUserToApplication({
application: appA,
email: contextA.email,
password: contextA.password,
})
expect(appA.items.getItems(ContentType.TYPES.ItemsKey).length).to.equal(1)
/** Create simultaneous appB signed into same account */
const appB = await Factory.createApplicationWithFakeCrypto('another-namespace')
await appB.prepareForLaunch({ receiveChallenge: () => {} })
await appB.launch(true)
await Factory.loginToApplication({
application: appB,
email: contextA.email,
password: contextA.password,
})
/** Change password on appB */
await appB.changePassword(contextA.password, newPassword)
const note = await Factory.createSyncedNote(appB)
await appB.sync.sync()
/** We expect the item in appA to be errored at this point, but we do not want it to recover */
await appA.sync.sync()
expect(appA.payloads.findOne(note.uuid).waitingForKey).to.equal(true)
console.warn('Expecting exceptions below as we destroy app during key recovery')
await Factory.safeDeinit(appA)
await Factory.safeDeinit(appB)
const recreatedAppA = await Factory.createApplicationWithFakeCrypto(namespace)
await recreatedAppA.prepareForLaunch({ receiveChallenge: () => {} })
await recreatedAppA.launch(true)
expect(recreatedAppA.payloads.findOne(note.uuid).errorDecrypting).to.equal(true)
expect(recreatedAppA.payloads.findOne(note.uuid).waitingForKey).to.equal(true)
await Factory.safeDeinit(recreatedAppA)
})
it('when client key params differ from server, and no matching items key exists to compare against, should perform sign in flow', async function () {
/**
* When a user changes password/email on client A, client B must update their root key to the new one.
-44
View File
@@ -599,50 +599,6 @@ describe('keys', function () {
await Factory.safeDeinit(recreatedApp)
})
it('errored second client should not upload its items keys', async function () {
/**
* The original source of this issue was that when changing password on client A and syncing with B,
* the newly encrypted items key retrieved on B would be included as "ignored", so its timestamps
* would not be emitted, and thus the application would be in sync. The app would then download
* the items key independently, and make duplicates erroneously.
*/
const contextA = this.context
const email = Utils.generateUuid()
const password = Utils.generateUuid()
await Factory.registerUserToApplication({
application: contextA.application,
email,
password: password,
})
const contextB = await Factory.createAppContext({ email, password })
await contextB.launch()
await contextB.signIn()
contextA.ignoreChallenges()
contextB.ignoreChallenges()
const newPassword = Utils.generateUuid()
await contextA.application.user.changeCredentials({
currentPassword: password,
newPassword: newPassword,
origination: KeyParamsOrigination.PasswordChange,
})
await contextB.syncWithIntegrityCheck()
await contextA.syncWithIntegrityCheck()
const clientAUndecryptables = contextA.keyRecovery.getUndecryptables()
const clientBUndecryptables = contextB.keyRecovery.getUndecryptables()
expect(Object.keys(clientBUndecryptables).length).to.equal(1)
expect(Object.keys(clientAUndecryptables).length).to.equal(0)
await contextB.deinit()
})
describe('changing password on 003 account while signed into 004 client', function () {
/**
* When an 004 client signs into 003 account, it creates a root key based items key.
@@ -0,0 +1,267 @@
describe.skip('session invalidation tests to revisit', function () {
this.timeout(Factory.TwentySecondTimeout)
const syncOptions = {
checkIntegrity: true,
awaitAll: true,
}
beforeEach(function () {
localStorage.clear()
})
afterEach(function () {
localStorage.clear()
})
it('recovered keys with key params not matching servers should not be synced if local root key does not match server', async function () {
/**
* Assume Application A has been through these states:
* 1. Registration + Items Key A + Root Key A
* 2. Password change + Items Key B + Root Key B
* 3. Password change + Items Key C + Root Key C + Failure to correctly re-encrypt Items Key A and B with Root Key C
*
* Application B is not correctly in sync, and is only at State 1 (Registration + Items Key A)
*
* Application B receives Items Key B of Root Key B but for whatever reason ignores Items Key C of Root Key C.
*
* When it recovers Items Key B, it should not re-upload it to the server, because Application B's Root Key is not
* the current account's root key.
*/
const contextA = await Factory.createAppContextWithFakeCrypto()
await contextA.launch()
await contextA.register()
contextA.preventKeyRecoveryOfKeys()
const contextB = await Factory.createAppContextWithFakeCrypto('app-b', contextA.email, contextA.password)
await contextB.launch()
await contextB.signIn()
await contextA.changePassword('new-password-1')
const itemsKeyARootKeyB = contextA.itemsKeys[0]
const itemsKeyBRootKeyB = contextA.itemsKeys[1]
contextA.disableSyncingOfItems([itemsKeyARootKeyB.uuid, itemsKeyBRootKeyB.uuid])
await contextA.changePassword('new-password-2')
const itemsKeyCRootKeyC = contextA.itemsKeys[2]
contextB.disableKeyRecoveryServerSignIn()
contextB.preventKeyRecoveryOfKeys([itemsKeyCRootKeyC.uuid])
contextB.respondToAccountPasswordChallengeWith('new-password-1')
const recoveryPromise = Promise.all([
contextB.resolveWhenKeyRecovered(itemsKeyARootKeyB.uuid),
contextB.resolveWhenKeyRecovered(itemsKeyBRootKeyB.uuid),
])
const observedDirtyItemUuids = []
contextB.spyOnChangedItems((changed) => {
const dirty = changed.filter((i) => i.dirty)
extendArray(observedDirtyItemUuids, Uuids(dirty))
})
await contextB.sync()
await recoveryPromise
expect(observedDirtyItemUuids.includes(itemsKeyARootKeyB.uuid)).to.equal(false)
expect(observedDirtyItemUuids.includes(itemsKeyBRootKeyB.uuid)).to.equal(false)
await contextA.deinit()
await contextB.deinit()
})
it('when changing password on client B, client A should perform recovery flow', async function () {
const contextA = await Factory.createAppContextWithFakeCrypto()
await contextA.launch()
await contextA.register()
const originalItemsKey = contextA.application.items.getDisplayableItemsKeys()[0]
const contextB = await Factory.createAppContextWithFakeCrypto(
'another-namespace',
contextA.email,
contextA.password,
)
contextB.ignoreChallenges()
await contextB.launch()
await contextB.signIn()
const newPassword = `${Math.random()}`
const result = await contextB.application.changePassword(contextA.password, newPassword)
expect(result.error).to.not.be.ok
expect(contextB.application.items.getAnyItems(ContentType.TYPES.ItemsKey).length).to.equal(2)
const newItemsKey = contextB.application.items
.getDisplayableItemsKeys()
.find((k) => k.uuid !== originalItemsKey.uuid)
const note = await Factory.createSyncedNote(contextB.application)
const recoveryPromise = contextA.resolveWhenKeyRecovered(newItemsKey.uuid)
contextA.password = newPassword
await contextA.sync(syncOptions)
await recoveryPromise
/** Same previously errored key should now no longer be errored, */
expect(contextA.application.items.getAnyItems(ContentType.TYPES.ItemsKey).length).to.equal(2)
for (const key of contextA.application.items.getDisplayableItemsKeys()) {
expect(key.errorDecrypting).to.not.be.ok
}
const aKey = await contextA.application.encryption.getRootKey()
const bKey = await contextB.application.encryption.getRootKey()
expect(aKey.compare(bKey)).to.equal(true)
expect(contextA.application.items.findItem(note.uuid).errorDecrypting).to.not.be.ok
expect(contextB.application.items.findItem(note.uuid).errorDecrypting).to.not.be.ok
expect(contextA.application.sync.isOutOfSync()).to.equal(false)
expect(contextB.application.sync.isOutOfSync()).to.equal(false)
await contextA.deinit()
await contextB.deinit()
}).timeout(80000)
it('when items key associated with item is errored, item should be marked waiting for key', async function () {
const namespace = Factory.randomString()
const newPassword = `${Math.random()}`
const contextA = await Factory.createAppContextWithFakeCrypto(namespace)
const appA = contextA.application
await appA.prepareForLaunch({ receiveChallenge: () => {} })
await appA.launch(true)
await Factory.registerUserToApplication({
application: appA,
email: contextA.email,
password: contextA.password,
})
expect(appA.items.getItems(ContentType.TYPES.ItemsKey).length).to.equal(1)
/** Create simultaneous appB signed into same account */
const appB = await Factory.createApplicationWithFakeCrypto('another-namespace')
await appB.prepareForLaunch({ receiveChallenge: () => {} })
await appB.launch(true)
await Factory.loginToApplication({
application: appB,
email: contextA.email,
password: contextA.password,
})
/** Change password on appB */
await appB.changePassword(contextA.password, newPassword)
const note = await Factory.createSyncedNote(appB)
await appB.sync.sync()
/** We expect the item in appA to be errored at this point, but we do not want it to recover */
await appA.sync.sync()
expect(appA.payloads.findOne(note.uuid).waitingForKey).to.equal(true)
console.warn('Expecting exceptions below as we destroy app during key recovery')
await Factory.safeDeinit(appA)
await Factory.safeDeinit(appB)
const recreatedAppA = await Factory.createApplicationWithFakeCrypto(namespace)
await recreatedAppA.prepareForLaunch({ receiveChallenge: () => {} })
await recreatedAppA.launch(true)
expect(recreatedAppA.payloads.findOne(note.uuid).errorDecrypting).to.equal(true)
expect(recreatedAppA.payloads.findOne(note.uuid).waitingForKey).to.equal(true)
await Factory.safeDeinit(recreatedAppA)
})
it('errored second client should not upload its items keys', async function () {
/**
* The original source of this issue was that when changing password on client A and syncing with B,
* the newly encrypted items key retrieved on B would be included as "ignored", so its timestamps
* would not be emitted, and thus the application would be in sync. The app would then download
* the items key independently, and make duplicates erroneously.
*/
const contextA = this.context
const email = Utils.generateUuid()
const password = Utils.generateUuid()
await Factory.registerUserToApplication({
application: contextA.application,
email,
password: password,
})
const contextB = await Factory.createAppContext({ email, password })
await contextB.launch()
await contextB.signIn()
contextA.ignoreChallenges()
contextB.ignoreChallenges()
const newPassword = Utils.generateUuid()
await contextA.application.user.changeCredentials({
currentPassword: password,
newPassword: newPassword,
origination: KeyParamsOrigination.PasswordChange,
})
await contextB.syncWithIntegrityCheck()
await contextA.syncWithIntegrityCheck()
const clientAUndecryptables = contextA.keyRecovery.getUndecryptables()
const clientBUndecryptables = contextB.keyRecovery.getUndecryptables()
expect(Object.keys(clientBUndecryptables).length).to.equal(1)
expect(Object.keys(clientAUndecryptables).length).to.equal(0)
await contextB.deinit()
})
it('changing password on one client should not invalidate other sessions', async function () {
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
password: this.password,
})
const appA = await Factory.createApplicationWithFakeCrypto(Factory.randomString())
await appA.prepareForLaunch({})
await appA.launch(true)
const email = `${Math.random()}`
const password = `${Math.random()}`
await Factory.registerUserToApplication({
application: appA,
email: email,
password: password,
})
/** Create simultaneous appB signed into same account */
const appB = await Factory.createApplicationWithFakeCrypto('another-namespace')
await appB.prepareForLaunch({})
await appB.launch(true)
await Factory.loginToApplication({
application: appB,
email: email,
password: password,
})
/** Change password on appB */
const newPassword = 'random'
await appB.changePassword(password, newPassword)
/** Create an item and sync it */
const note = await Factory.createSyncedNote(appB)
/** Expect appA session to still be valid */
await appA.sync.sync()
expect(appA.items.findItem(note.uuid)).to.be.ok
await Factory.safeDeinit(appA)
await Factory.safeDeinit(appB)
})
})
-45
View File
@@ -483,51 +483,6 @@ describe('server session', function () {
}
}).timeout(Factory.TwentySecondTimeout)
it('changing password on one client should not invalidate other sessions', async function () {
await Factory.registerUserToApplication({
application: this.application,
email: this.email,
password: this.password,
})
const appA = await Factory.createApplicationWithFakeCrypto(Factory.randomString())
await appA.prepareForLaunch({})
await appA.launch(true)
const email = `${Math.random()}`
const password = `${Math.random()}`
await Factory.registerUserToApplication({
application: appA,
email: email,
password: password,
})
/** Create simultaneous appB signed into same account */
const appB = await Factory.createApplicationWithFakeCrypto('another-namespace')
await appB.prepareForLaunch({})
await appB.launch(true)
await Factory.loginToApplication({
application: appB,
email: email,
password: password,
})
/** Change password on appB */
const newPassword = 'random'
await appB.changePassword(password, newPassword)
/** Create an item and sync it */
const note = await Factory.createSyncedNote(appB)
/** Expect appA session to still be valid */
await appA.sync.sync()
expect(appA.items.findItem(note.uuid)).to.be.ok
await Factory.safeDeinit(appA)
await Factory.safeDeinit(appB)
})
it('should prompt user for account password and sign back in on invalid session', async function () {
await Factory.registerUserToApplication({
application: this.application,
@@ -1047,4 +1047,25 @@ describe('online syncing', function () {
await contextB.deinit()
})
it('should sync note when running raw sync request for external use', async function () {
const contextA = this.context
const contextB = await Factory.createAppContextWithFakeCrypto('AppB', contextA.email, contextA.password)
await contextB.launch()
await contextB.signIn()
const notePayload = Factory.createNote()
const rawSyncRequest = await this.application.sync.getRawSyncRequestForExternalUse([notePayload])
expect(rawSyncRequest).to.be.ok
const response = await this.application.http.runHttp(rawSyncRequest)
expect(response.status).to.equal(200)
await contextB.sync()
const note = contextB.application.items.findItem(notePayload.uuid)
expect(note).to.be.ok
})
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.202.27",
"version": "2.202.29",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.28.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
### Bug Fixes
* Fixed issue where moving the floating keyboard on iPad would lead to blank space on screen ([f7a1c6e](https://github.com/standardnotes/app/commit/f7a1c6ea65201af80ca6859c0e83c5b49c94e11d))
## [1.28.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/ui-services
## [1.28.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/ui-services
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/ui-services",
"version": "1.28.24",
"version": "1.28.26",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -14,7 +14,11 @@ export interface WebApplicationInterface extends ApplicationInterface {
handleMobileLosingFocusEvent(): Promise<void>
handleMobileResumingFromBackgroundEvent(): Promise<void>
handleMobileColorSchemeChangeEvent(): void
handleMobileKeyboardWillChangeFrameEvent(frame: { height: number; contentHeight: number }): void
handleMobileKeyboardWillChangeFrameEvent(frame: {
height: number
contentHeight: number
isFloatingKeyboard: boolean
}): void
handleMobileKeyboardDidChangeFrameEvent(frame: { height: number; contentHeight: number }): void
handleReceivedFileEvent(file: { name: string; mimeType: string; data: string }): void
handleReceivedTextEvent(item: { text: string; title?: string }): Promise<void>
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.167.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/web
## [3.167.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)
**Note:** Version bump only for package @standardnotes/web
## [3.167.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
### Bug Fixes
* Fixed issue where moving the floating keyboard on iPad would lead to blank space on screen ([f7a1c6e](https://github.com/standardnotes/app/commit/f7a1c6ea65201af80ca6859c0e83c5b49c94e11d))
## [3.167.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/web
## [3.167.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)
**Note:** Version bump only for package @standardnotes/web
+47
View File
@@ -1,5 +1,52 @@
{
"versions": [
{
"version": "3.167.35",
"title": "[3.167.35](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.167.34",
"title": "[3.167.34](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-04)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.167.33",
"title": "[3.167.33](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)",
"date": null,
"body": "### Bug Fixes\n\n* Fixed issue where moving the floating keyboard on iPad would lead to blank space on screen ([f7a1c6e](https://github.com/standardnotes/app/commit/f7a1c6ea65201af80ca6859c0e83c5b49c94e11d))",
"parsed": {
"_": [
"Fixed issue where moving the floating keyboard on iPad would lead to blank space on screen (f7a1c6e)"
],
"Bug Fixes": [
"Fixed issue where moving the floating keyboard on iPad would lead to blank space on screen (f7a1c6e)"
]
}
},
{
"version": "3.167.32",
"title": "[3.167.32](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.167.31",
"title": "[3.167.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-08-03)",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/web",
"version": "3.167.31",
"version": "3.167.35",
"license": "CC BY-NC-SA 4.0",
"main": "dist/app.js",
"author": "Standard Notes.",
@@ -373,10 +373,17 @@ export class WebApplication extends SNApplication implements WebApplicationInter
void this.getThemeService().handleMobileColorSchemeChangeEvent()
}
handleMobileKeyboardWillChangeFrameEvent(frame: { height: number; contentHeight: number }): void {
handleMobileKeyboardWillChangeFrameEvent(frame: {
height: number
contentHeight: number
isFloatingKeyboard: boolean
}): void {
if (frame.contentHeight > 0) {
setCustomViewportHeight(frame.contentHeight, 'px', true)
}
if (frame.isFloatingKeyboard) {
setCustomViewportHeight(100, 'vh', true)
}
this.notifyWebEvent(WebAppEvent.MobileKeyboardWillChangeFrame, frame)
}
@@ -27,7 +27,7 @@ const AlertDialog = ({
<Dialog
store={dialog}
role="alertdialog"
className="fixed left-0 top-0 z-modal h-full w-full"
className="fixed left-0 top-0 z-modal h-full w-full flex items-center justify-center"
modal={false}
portal={true}
preventBodyScroll={true}
@@ -40,7 +40,7 @@ const AlertDialog = ({
/>
<div
className={classNames(
'absolute left-1/2 top-1/2 z-[1] w-[95vw] -translate-x-1/2 -translate-y-1/2 rounded border border-border bg-default px-6 py-5 shadow-xl md:w-auto',
'w-[95vw] rounded border border-border bg-default px-6 py-5 shadow-xl md:w-auto',
!className?.includes('max-w-') && 'max-w-[600px]',
className,
)}
@@ -7,7 +7,7 @@ import { useApplication } from '../ApplicationProvider'
import Icon from '../Icon/Icon'
import Menu from '../Menu/Menu'
import MenuItem from '../Menu/MenuItem'
import { storage as extensionStorage, windows } from 'webextension-polyfill'
import { storage as extensionStorage, runtime, windows } from 'webextension-polyfill'
import sendMessageToActiveTab from '@standardnotes/clipper/src/utils/sendMessageToActiveTab'
import { ClipPayload, RuntimeMessageTypes } from '@standardnotes/clipper/src/types/message'
import { confirmDialog } from '@standardnotes/ui-services'
@@ -22,6 +22,7 @@ import {
PrefKey,
SNNote,
SNTag,
classNames,
} from '@standardnotes/snjs'
import { addToast, ToastType } from '@standardnotes/toast'
import { getSuperJSONFromClipPayload } from './getSuperJSONFromClipHTML'
@@ -36,6 +37,7 @@ import ItemSelectionDropdown from '../ItemSelectionDropdown/ItemSelectionDropdow
import LinkedItemBubble from '../LinkedItems/LinkedItemBubble'
import StyledTooltip from '../StyledTooltip/StyledTooltip'
import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
import Spinner from '../Spinner/Spinner'
const ClipperView = ({
viewControllerManager,
@@ -60,6 +62,11 @@ const ClipperView = ({
const isFirefoxPopup = !!currentWindow && currentWindow.type === 'popup' && currentWindow.incognito === false
const [user, setUser] = useState(() => application.getUser())
const [isSyncing, setIsSyncing] = useState(false)
const [hasSyncError, setHasSyncError] = useState(false)
useEffect(() => {
application.sessions.refreshSessionIfExpiringSoon().catch(console.error)
}, [application.sessions])
const [isEntitledToExtension, setIsEntitled] = useState(
() =>
application.features.getFeatureStatus(
@@ -88,6 +95,13 @@ const ClipperView = ({
) === FeatureStatus.Entitled,
)
break
case ApplicationEvent.SyncStatusChanged:
case ApplicationEvent.FailedSync: {
const status = application.sync.getSyncStatus()
setIsSyncing(status.syncInProgress)
setHasSyncError(status.hasError())
break
}
}
})
}, [application])
@@ -241,10 +255,19 @@ const ClipperView = ({
message: 'Note clipped successfully',
})
void application.sync.sync()
const syncRequest = await application.sync.getRawSyncRequestForExternalUse([insertedNote])
if (syncRequest) {
runtime
.sendMessage({
type: RuntimeMessageTypes.RunHttpRequest,
payload: syncRequest,
})
.catch(console.error)
}
}
void createNoteFromClip()
createNoteFromClip().catch(console.error)
}, [
application.items,
application.linkingController,
@@ -431,6 +454,22 @@ const ClipperView = ({
<Icon type="signOut" className="text-neutral" />
</button>
</div>
{isSyncing || hasSyncError ? (
<div className={classNames('flex items-center border-t border-border', hasSyncError && 'text-danger')}>
{isSyncing && (
<>
<Spinner className="w-4 h-4 mx-2.5" />
<div className="flex-grow py-2 text-sm font-semibold text-info">Syncing...</div>
</>
)}
{hasSyncError && (
<>
<Icon type="warning" className="mx-2.5" />
<div className="flex-grow py-2 text-sm font-semibold">Unable to sync</div>
</>
)}
</div>
) : null}
</Menu>
</div>
)
@@ -104,8 +104,7 @@ const Modal = ({
/>
<div
className={classNames(
'absolute z-[1] m-0 flex h-full w-full flex-col border-solid border-border bg-default p-0 md:h-auto md:max-h-[85vh] md:w-160 md:rounded md:border md:shadow-main',
'md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:transform',
'z-[1] m-0 flex h-full w-full flex-col border-solid border-border bg-default p-0 md:h-auto md:max-h-[85vh] md:w-160 md:rounded md:border md:shadow-main',
className?.content,
)}
>
@@ -42,7 +42,7 @@ const ModalOverlay = forwardRef(
return (
<Dialog
tabIndex={0}
className="fixed left-0 top-0 z-modal h-full w-full"
className="fixed flex items-center justify-center left-0 top-0 z-modal h-full w-full"
ref={mergeRefs([setElement, addCloseMethod, ref])}
store={dialog}
modal={false}
@@ -29,8 +29,8 @@ const getStylesFromRect = (
return {
willChange: 'transform',
'--translate-x': `${shouldApplyMobileWidth ? marginForMobile / 2 : rect.x}px`,
'--translate-y': `${rect.y}px`,
'--translate-x': `${shouldApplyMobileWidth ? marginForMobile / 2 : Math.floor(rect.x)}px`,
'--translate-y': `${Math.floor(rect.y)}px`,
'--offset': `${options.offset}px`,
transform: 'translate(var(--translate-x), var(--translate-y))',
visibility: 'visible',
@@ -19,8 +19,8 @@ export const getAdjustedStylesForNonPortalPopover = (
const parentRect = absoluteParent.getBoundingClientRect()
const adjustedTranslateX = parsedTranslateX - parentRect.left
const adjustedTranslateY = parsedTranslateY - parentRect.top
const adjustedTranslateX = Math.floor(parsedTranslateX - parentRect.left)
const adjustedTranslateY = Math.floor(parsedTranslateY - parentRect.top)
return {
...styles,
@@ -8,18 +8,18 @@ export const movePopoverToFitInsideRect = (popoverElement: HTMLElement, rect: DO
const overflows = getOverflows(popoverRect, rect)
if (overflows['top'] > 0) {
popoverElement.style.setProperty('--translate-y', `${y + overflows['top'] + offset}px`)
popoverElement.style.setProperty('--translate-y', `${Math.floor(y + overflows['top'] + offset)}px`)
}
if (overflows['bottom'] > 0) {
popoverElement.style.setProperty('--translate-y', `${y - overflows['bottom'] - offset}px`)
popoverElement.style.setProperty('--translate-y', `${Math.floor(y - overflows['bottom'] - offset)}px`)
}
if (overflows['left'] > 0) {
popoverElement.style.setProperty('--translate-x', `${x + overflows['left'] + offset}px`)
popoverElement.style.setProperty('--translate-x', `${Math.floor(x + overflows['left'] + offset)}px`)
}
if (overflows['right'] > 0) {
popoverElement.style.setProperty('--translate-x', `${x - overflows['right'] - offset}px`)
popoverElement.style.setProperty('--translate-x', `${Math.floor(x - overflows['right'] - offset)}px`)
}
}
@@ -78,7 +78,7 @@ const ToolbarButton = ({ active, ...props }: { active?: boolean } & ComponentPro
return (
<button
className={classNames(
'flex rounded-lg p-3 hover:bg-contrast hover:text-text disabled:cursor-not-allowed',
'flex rounded-lg p-3 hover:bg-default hover:text-text disabled:cursor-not-allowed',
active && 'bg-info text-info-contrast',
)}
{...props}
@@ -277,7 +277,7 @@ function TextFormatFloatingToolbar({
return (
<div
ref={toolbarRef}
className="absolute left-0 top-0 rounded-lg border border-border bg-default px-2 py-1 shadow shadow-contrast"
className="absolute left-0 top-0 rounded-lg border border-border bg-contrast px-2 py-1 shadow-sm shadow-contrast"
>
{isLink && (
<LinkEditor
@@ -75,7 +75,7 @@ export class MobileWebReceiver {
break
case ReactNativeToWebEvent.KeyboardFrameWillChange:
void this.application.handleMobileKeyboardWillChangeFrameEvent(
messageData as { height: number; contentHeight: number },
messageData as { height: number; contentHeight: number; isFloatingKeyboard: boolean },
)
break
case ReactNativeToWebEvent.KeyboardFrameDidChange: