mirror of
https://github.com/standardnotes/app
synced 2026-09-13 09:46:02 -04:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac4231d8eb | ||
|
|
3281ac9d37 | ||
|
|
f2d089ab24 | ||
|
|
a91ab4abbf | ||
|
|
0af41cbcad | ||
|
|
1adb21a06e | ||
|
|
412b192b85 | ||
|
|
8ad9b8ae2a | ||
|
|
c52d8e7309 | ||
|
|
406abb4929 | ||
|
|
814a156e1b |
@@ -6,6 +6,3 @@ DEFAULT_SYNC_SERVER=https://api.standardnotes.com
|
||||
DASHBOARD_URL=http://standardnotes.com/dashboard
|
||||
PLANS_URL=https://standardnotes.com/plans
|
||||
PURCHASE_URL=https://standardnotes.com/purchase
|
||||
|
||||
# Used by Rails internals and not Standard Notes related
|
||||
SECRET_KEY_BASE=<enter a secret string here>
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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.110](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.109](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.108](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.107](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.106](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/clipper",
|
||||
"description": "Web clipper browser extension for Standard Notes",
|
||||
"version": "1.1.106",
|
||||
"version": "1.1.110",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
|
||||
|
||||
@@ -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.41](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.39](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.38](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.37](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.108.37",
|
||||
"version": "3.108.41",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -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.56.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.56.16",
|
||||
"version": "3.56.20",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -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.392](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.391](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.390](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.389](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.388](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.388",
|
||||
"version": "1.4.392",
|
||||
"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.63.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.63.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.63.6",
|
||||
"version": "1.63.7",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { ReuploadAllInvites } from '../../SharedVaults/UseCase/ReuploadAllInvites'
|
||||
import { ReuploadAllInvites } from '../../VaultInvite/UseCase/ReuploadAllInvites'
|
||||
import { ResendAllMessages } from '../../AsymmetricMessage/UseCase/ResendAllMessages'
|
||||
|
||||
export class HandleKeyPairChange implements UseCaseInterface<void> {
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
import { IsVaultAdmin } from './../VaultUser/UseCase/IsVaultAdmin'
|
||||
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
|
||||
import { GetVaultUsers } from './UseCase/GetVaultUsers'
|
||||
import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember'
|
||||
import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
|
||||
import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault'
|
||||
import { ShareContactWithVault } from './UseCase/ShareContactWithVault'
|
||||
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault'
|
||||
import { LeaveVault } from './UseCase/LeaveSharedVault'
|
||||
import { InviteToVault } from './UseCase/InviteToVault'
|
||||
import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite'
|
||||
import { GetVaultContacts } from './UseCase/GetVaultContacts'
|
||||
import { GetAllContacts } from './../Contacts/UseCase/GetAllContacts'
|
||||
import { FindContact } from './../Contacts/UseCase/FindContact'
|
||||
import { GetUntrustedPayload } from './../AsymmetricMessage/UseCase/GetUntrustedPayload'
|
||||
import { GetTrustedPayload } from './../AsymmetricMessage/UseCase/GetTrustedPayload'
|
||||
import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage'
|
||||
import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation'
|
||||
import { HandleKeyPairChange } from './../Contacts/UseCase/HandleKeyPairChange'
|
||||
import { CreateSharedVault } from './UseCase/CreateSharedVault'
|
||||
import { GetVault } from './../Vaults/UseCase/GetVault'
|
||||
import { SharedVaultInvitesServer } from '@standardnotes/api'
|
||||
import { SharedVaultService } from './SharedVaultService'
|
||||
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
@@ -40,56 +31,37 @@ describe('SharedVaultService', () => {
|
||||
const encryption = {} as jest.Mocked<EncryptionProviderInterface>
|
||||
const session = {} as jest.Mocked<SessionsClientInterface>
|
||||
const vaults = {} as jest.Mocked<VaultServiceInterface>
|
||||
const invitesServer = {} as jest.Mocked<SharedVaultInvitesServer>
|
||||
const getVault = {} as jest.Mocked<GetVault>
|
||||
const createSharedVaultUseCase = {} as jest.Mocked<CreateSharedVault>
|
||||
const handleKeyPairChange = {} as jest.Mocked<HandleKeyPairChange>
|
||||
const notifyVaultUsersOfKeyRotation = {} as jest.Mocked<NotifyVaultUsersOfKeyRotation>
|
||||
const sendVaultDataChangeMessage = {} as jest.Mocked<SendVaultDataChangedMessage>
|
||||
const getTrustedPayload = {} as jest.Mocked<GetTrustedPayload>
|
||||
const getUntrustedPayload = {} as jest.Mocked<GetUntrustedPayload>
|
||||
const findContact = {} as jest.Mocked<FindContact>
|
||||
const getAllContacts = {} as jest.Mocked<GetAllContacts>
|
||||
const getVaultContacts = {} as jest.Mocked<GetVaultContacts>
|
||||
const acceptVaultInvite = {} as jest.Mocked<AcceptVaultInvite>
|
||||
const inviteToVault = {} as jest.Mocked<InviteToVault>
|
||||
const leaveVault = {} as jest.Mocked<LeaveVault>
|
||||
const deleteThirdPartyVault = {} as jest.Mocked<DeleteThirdPartyVault>
|
||||
const shareContactWithVault = {} as jest.Mocked<ShareContactWithVault>
|
||||
const convertToSharedVault = {} as jest.Mocked<ConvertToSharedVault>
|
||||
const deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
|
||||
const removeVaultMember = {} as jest.Mocked<RemoveVaultMember>
|
||||
const getSharedVaultUsersUseCase = {} as jest.Mocked<GetVaultUsers>
|
||||
const isVaultAdmin = {} as jest.Mocked<IsVaultAdmin>
|
||||
|
||||
const eventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
eventBus.addEventHandler = jest.fn()
|
||||
|
||||
service = new SharedVaultService(
|
||||
sync,
|
||||
items,
|
||||
encryption,
|
||||
session,
|
||||
vaults,
|
||||
invitesServer,
|
||||
getVault,
|
||||
createSharedVaultUseCase,
|
||||
handleKeyPairChange,
|
||||
notifyVaultUsersOfKeyRotation,
|
||||
sendVaultDataChangeMessage,
|
||||
getTrustedPayload,
|
||||
getUntrustedPayload,
|
||||
findContact,
|
||||
getAllContacts,
|
||||
getVaultContacts,
|
||||
acceptVaultInvite,
|
||||
inviteToVault,
|
||||
leaveVault,
|
||||
deleteThirdPartyVault,
|
||||
shareContactWithVault,
|
||||
convertToSharedVault,
|
||||
deleteSharedVaultUseCase,
|
||||
removeVaultMember,
|
||||
getSharedVaultUsersUseCase,
|
||||
isVaultAdmin,
|
||||
eventBus,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,114 +1,70 @@
|
||||
import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData'
|
||||
import { InviteToVault } from './UseCase/InviteToVault'
|
||||
import {
|
||||
ClientDisplayableError,
|
||||
SharedVaultInviteServerHash,
|
||||
isErrorResponse,
|
||||
SharedVaultUserServerHash,
|
||||
isClientDisplayableError,
|
||||
SharedVaultPermission,
|
||||
UserEventType,
|
||||
} from '@standardnotes/responses'
|
||||
import { SharedVaultInvitesServer } from '@standardnotes/api'
|
||||
import { ClientDisplayableError, UserEventType } from '@standardnotes/responses'
|
||||
import {
|
||||
DecryptedItemInterface,
|
||||
PayloadEmitSource,
|
||||
TrustedContactInterface,
|
||||
SharedVaultListingInterface,
|
||||
VaultListingInterface,
|
||||
AsymmetricMessageSharedVaultInvite,
|
||||
KeySystemRootKeyStorageMode,
|
||||
} from '@standardnotes/models'
|
||||
import { SharedVaultServiceInterface } from './SharedVaultServiceInterface'
|
||||
import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent'
|
||||
import { GetVaultUsers } from './UseCase/GetVaultUsers'
|
||||
import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
|
||||
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { SyncEvent, SyncEventReceivedSharedVaultInvitesData } from '../Event/SyncEvent'
|
||||
import { SyncEvent } from '../Event/SyncEvent'
|
||||
import { SessionEvent } from '../Session/SessionEvent'
|
||||
import { InternalEventInterface } from '../Internal/InternalEventInterface'
|
||||
import { LeaveVault } from './UseCase/LeaveSharedVault'
|
||||
import { VaultServiceInterface } from '../Vaults/VaultServiceInterface'
|
||||
import { UserEventServiceEvent, UserEventServiceEventPayload } from '../UserEvent/UserEventServiceEvent'
|
||||
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault'
|
||||
import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
|
||||
import { VaultServiceEvent, VaultServiceEventPayload } from '../Vaults/VaultServiceEvent'
|
||||
import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite'
|
||||
import { GetTrustedPayload } from '../AsymmetricMessage/UseCase/GetTrustedPayload'
|
||||
import { PendingSharedVaultInviteRecord } from './PendingSharedVaultInviteRecord'
|
||||
import { GetUntrustedPayload } from '../AsymmetricMessage/UseCase/GetUntrustedPayload'
|
||||
import { ShareContactWithVault } from './UseCase/ShareContactWithVault'
|
||||
import { GetVaultContacts } from './UseCase/GetVaultContacts'
|
||||
import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation'
|
||||
import { CreateSharedVault } from './UseCase/CreateSharedVault'
|
||||
import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage'
|
||||
import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault'
|
||||
import { GetVault } from '../Vaults/UseCase/GetVault'
|
||||
import { ContentType, Result } from '@standardnotes/domain-core'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange'
|
||||
import { FindContact } from '../Contacts/UseCase/FindContact'
|
||||
import { GetAllContacts } from '../Contacts/UseCase/GetAllContacts'
|
||||
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
|
||||
import { IsVaultAdmin } from '../VaultUser/UseCase/IsVaultAdmin'
|
||||
|
||||
export class SharedVaultService
|
||||
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload>
|
||||
implements SharedVaultServiceInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private pendingInvites: Record<string, PendingSharedVaultInviteRecord> = {}
|
||||
|
||||
constructor(
|
||||
private sync: SyncServiceInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private encryption: EncryptionProviderInterface,
|
||||
private session: SessionsClientInterface,
|
||||
private vaults: VaultServiceInterface,
|
||||
private invitesServer: SharedVaultInvitesServer,
|
||||
private getVault: GetVault,
|
||||
private createSharedVaultUseCase: CreateSharedVault,
|
||||
private handleKeyPairChange: HandleKeyPairChange,
|
||||
private notifyVaultUsersOfKeyRotation: NotifyVaultUsersOfKeyRotation,
|
||||
private sendVaultDataChangeMessage: SendVaultDataChangedMessage,
|
||||
private getTrustedPayload: GetTrustedPayload,
|
||||
private getUntrustedPayload: GetUntrustedPayload,
|
||||
private findContact: FindContact,
|
||||
private getAllContacts: GetAllContacts,
|
||||
private getVaultContacts: GetVaultContacts,
|
||||
private acceptVaultInvite: AcceptVaultInvite,
|
||||
private inviteToVault: InviteToVault,
|
||||
private leaveVault: LeaveVault,
|
||||
private deleteThirdPartyVault: DeleteThirdPartyVault,
|
||||
private shareContactWithVault: ShareContactWithVault,
|
||||
private convertToSharedVault: ConvertToSharedVault,
|
||||
private deleteSharedVaultUseCase: DeleteSharedVault,
|
||||
private removeVaultMember: RemoveVaultMember,
|
||||
private getSharedVaultUsersUseCase: GetVaultUsers,
|
||||
private _getVault: GetVault,
|
||||
private _createSharedVaultUseCase: CreateSharedVault,
|
||||
private _handleKeyPairChange: HandleKeyPairChange,
|
||||
private _notifyVaultUsersOfKeyRotation: NotifyVaultUsersOfKeyRotation,
|
||||
private _sendVaultDataChangeMessage: SendVaultDataChangedMessage,
|
||||
private _findContact: FindContact,
|
||||
private _deleteThirdPartyVault: DeleteThirdPartyVault,
|
||||
private _shareContactWithVault: ShareContactWithVault,
|
||||
private _convertToSharedVault: ConvertToSharedVault,
|
||||
private _deleteSharedVaultUseCase: DeleteSharedVault,
|
||||
private _isVaultAdmin: IsVaultAdmin,
|
||||
eventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(eventBus)
|
||||
|
||||
eventBus.addEventHandler(this, SessionEvent.UserKeyPairChanged)
|
||||
eventBus.addEventHandler(this, UserEventServiceEvent.UserEventReceived)
|
||||
eventBus.addEventHandler(this, VaultServiceEvent.VaultRootKeyRotated)
|
||||
|
||||
this.eventDisposers.push(
|
||||
items.addObserver<TrustedContactInterface>(
|
||||
ContentType.TYPES.TrustedContact,
|
||||
async ({ changed, inserted, source }) => {
|
||||
await this.reprocessCachedInvitesTrustStatusAfterTrustedContactsChange()
|
||||
|
||||
if (source === PayloadEmitSource.LocalChanged && inserted.length > 0) {
|
||||
void this.handleCreationOfNewTrustedContacts(inserted)
|
||||
}
|
||||
if (source === PayloadEmitSource.LocalChanged && changed.length > 0) {
|
||||
void this.handleTrustedContactsChange(changed)
|
||||
}
|
||||
},
|
||||
),
|
||||
items.addObserver<TrustedContactInterface>(ContentType.TYPES.TrustedContact, async ({ changed, source }) => {
|
||||
if (source === PayloadEmitSource.LocalChanged && changed.length > 0) {
|
||||
void this.handleTrustedContactsChange(changed)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
this.eventDisposers.push(
|
||||
@@ -120,54 +76,88 @@ export class SharedVaultService
|
||||
)
|
||||
}
|
||||
|
||||
override deinit(): void {
|
||||
super.deinit()
|
||||
;(this.items as unknown) = undefined
|
||||
;(this.encryption as unknown) = undefined
|
||||
;(this.session as unknown) = undefined
|
||||
;(this.vaults as unknown) = undefined
|
||||
;(this._getVault as unknown) = undefined
|
||||
;(this._createSharedVaultUseCase as unknown) = undefined
|
||||
;(this._handleKeyPairChange as unknown) = undefined
|
||||
;(this._notifyVaultUsersOfKeyRotation as unknown) = undefined
|
||||
;(this._sendVaultDataChangeMessage as unknown) = undefined
|
||||
;(this._findContact as unknown) = undefined
|
||||
;(this._deleteThirdPartyVault as unknown) = undefined
|
||||
;(this._shareContactWithVault as unknown) = undefined
|
||||
;(this._convertToSharedVault as unknown) = undefined
|
||||
;(this._deleteSharedVaultUseCase as unknown) = undefined
|
||||
;(this._isVaultAdmin as unknown) = undefined
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === SessionEvent.UserKeyPairChanged) {
|
||||
void this.invitesServer.deleteAllInboundInvites()
|
||||
|
||||
const eventData = event.payload as UserKeyPairChangedEventData
|
||||
|
||||
void this.handleKeyPairChange.execute({
|
||||
newKeys: eventData.current,
|
||||
previousKeys: eventData.previous,
|
||||
})
|
||||
} else if (event.type === UserEventServiceEvent.UserEventReceived) {
|
||||
await this.handleUserEvent(event.payload as UserEventServiceEventPayload)
|
||||
} else if (event.type === VaultServiceEvent.VaultRootKeyRotated) {
|
||||
const payload = event.payload as VaultServiceEventPayload[VaultServiceEvent.VaultRootKeyRotated]
|
||||
await this.handleVaultRootKeyRotatedEvent(payload.vault)
|
||||
} else if (event.type === SyncEvent.ReceivedSharedVaultInvites) {
|
||||
await this.processInboundInvites(event.payload as SyncEventReceivedSharedVaultInvitesData)
|
||||
} else if (event.type === SyncEvent.ReceivedRemoteSharedVaults) {
|
||||
void this.notifyCollaborationStatusChanged()
|
||||
switch (event.type) {
|
||||
case SessionEvent.UserKeyPairChanged: {
|
||||
const eventData = event.payload as UserKeyPairChangedEventData
|
||||
void this._handleKeyPairChange.execute({
|
||||
newKeys: eventData.current,
|
||||
previousKeys: eventData.previous,
|
||||
})
|
||||
break
|
||||
}
|
||||
case UserEventServiceEvent.UserEventReceived:
|
||||
await this.handleUserEvent(event.payload as UserEventServiceEventPayload)
|
||||
break
|
||||
case VaultServiceEvent.VaultRootKeyRotated: {
|
||||
const payload = event.payload as VaultServiceEventPayload[VaultServiceEvent.VaultRootKeyRotated]
|
||||
await this.handleVaultRootKeyRotatedEvent(payload.vault)
|
||||
break
|
||||
}
|
||||
case SyncEvent.ReceivedRemoteSharedVaults:
|
||||
void this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private async handleUserEvent(event: UserEventServiceEventPayload): Promise<void> {
|
||||
if (event.eventPayload.eventType === UserEventType.RemovedFromSharedVault) {
|
||||
const vault = this.getVault.execute<SharedVaultListingInterface>({
|
||||
sharedVaultUuid: event.eventPayload.sharedVaultUuid,
|
||||
})
|
||||
if (!vault.isFailed()) {
|
||||
await this.deleteThirdPartyVault.execute(vault.getValue())
|
||||
switch (event.eventPayload.eventType) {
|
||||
case UserEventType.RemovedFromSharedVault: {
|
||||
const vault = this._getVault.execute<SharedVaultListingInterface>({
|
||||
sharedVaultUuid: event.eventPayload.sharedVaultUuid,
|
||||
})
|
||||
if (!vault.isFailed()) {
|
||||
await this._deleteThirdPartyVault.execute(vault.getValue())
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if (event.eventPayload.eventType === UserEventType.SharedVaultItemRemoved) {
|
||||
const item = this.items.findItem(event.eventPayload.itemUuid)
|
||||
if (item) {
|
||||
this.items.removeItemsLocally([item])
|
||||
case UserEventType.SharedVaultItemRemoved: {
|
||||
const item = this.items.findItem(event.eventPayload.itemUuid)
|
||||
if (item) {
|
||||
this.items.removeItemsLocally([item])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isCurrentUserVaultOwner(sharedVault: SharedVaultListingInterface): boolean {
|
||||
if (!sharedVault.sharing.ownerUserUuid) {
|
||||
throw new Error(`Shared vault ${sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
|
||||
}
|
||||
|
||||
return sharedVault.sharing.ownerUserUuid === this.session.userUuid
|
||||
}
|
||||
|
||||
private async handleVaultRootKeyRotatedEvent(vault: VaultListingInterface): Promise<void> {
|
||||
if (!vault.isSharedVaultListing()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.isCurrentUserSharedVaultOwner(vault)) {
|
||||
if (!this.isCurrentUserVaultOwner(vault)) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.notifyVaultUsersOfKeyRotation.execute({
|
||||
await this._notifyVaultUsersOfKeyRotation.execute({
|
||||
sharedVault: vault,
|
||||
senderUuid: this.session.getSureUser().uuid,
|
||||
keys: {
|
||||
@@ -183,7 +173,7 @@ export class SharedVaultService
|
||||
userInputtedPassword: string | undefined
|
||||
storagePreference?: KeySystemRootKeyStorageMode
|
||||
}): Promise<VaultListingInterface | ClientDisplayableError> {
|
||||
return this.createSharedVaultUseCase.execute({
|
||||
return this._createSharedVaultUseCase.execute({
|
||||
vaultName: dto.name,
|
||||
vaultDescription: dto.description,
|
||||
userInputtedPassword: dto.userInputtedPassword,
|
||||
@@ -194,11 +184,7 @@ export class SharedVaultService
|
||||
async convertVaultToSharedVault(
|
||||
vault: VaultListingInterface,
|
||||
): Promise<SharedVaultListingInterface | ClientDisplayableError> {
|
||||
return this.convertToSharedVault.execute({ vault })
|
||||
}
|
||||
|
||||
public getCachedPendingInviteRecords(): PendingSharedVaultInviteRecord[] {
|
||||
return Object.values(this.pendingInvites)
|
||||
return this._convertToSharedVault.execute({ vault })
|
||||
}
|
||||
|
||||
private getAllSharedVaults(): SharedVaultListingInterface[] {
|
||||
@@ -206,38 +192,6 @@ export class SharedVaultService
|
||||
return vaults as SharedVaultListingInterface[]
|
||||
}
|
||||
|
||||
private findSharedVault(sharedVaultUuid: string): SharedVaultListingInterface | undefined {
|
||||
const result = this.getVault.execute<SharedVaultListingInterface>({ sharedVaultUuid })
|
||||
if (result.isFailed()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return result.getValue()
|
||||
}
|
||||
|
||||
public isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean {
|
||||
if (!sharedVault.sharing.ownerUserUuid) {
|
||||
throw new Error(`Shared vault ${sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
|
||||
}
|
||||
return sharedVault.sharing.ownerUserUuid === this.session.userUuid
|
||||
}
|
||||
|
||||
public isCurrentUserSharedVaultOwner(sharedVault: SharedVaultListingInterface): boolean {
|
||||
if (!sharedVault.sharing.ownerUserUuid) {
|
||||
throw new Error(`Shared vault ${sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
|
||||
}
|
||||
return sharedVault.sharing.ownerUserUuid === this.session.userUuid
|
||||
}
|
||||
|
||||
public isSharedVaultUserSharedVaultOwner(user: SharedVaultUserServerHash): boolean {
|
||||
const vault = this.findSharedVault(user.shared_vault_uuid)
|
||||
return vault != undefined && vault.sharing.ownerUserUuid === user.user_uuid
|
||||
}
|
||||
|
||||
private async handleCreationOfNewTrustedContacts(_contacts: TrustedContactInterface[]): Promise<void> {
|
||||
await this.downloadInboundInvites()
|
||||
}
|
||||
|
||||
private async handleTrustedContactsChange(contacts: TrustedContactInterface[]): Promise<void> {
|
||||
for (const contact of contacts) {
|
||||
if (contact.isMe) {
|
||||
@@ -254,7 +208,7 @@ export class SharedVaultService
|
||||
continue
|
||||
}
|
||||
|
||||
await this.sendVaultDataChangeMessage.execute({
|
||||
await this._sendVaultDataChangeMessage.execute({
|
||||
vault,
|
||||
senderUuid: this.session.getSureUser().uuid,
|
||||
keys: {
|
||||
@@ -265,220 +219,8 @@ export class SharedVaultService
|
||||
}
|
||||
}
|
||||
|
||||
public async downloadInboundInvites(): Promise<ClientDisplayableError | SharedVaultInviteServerHash[]> {
|
||||
const response = await this.invitesServer.getInboundUserInvites()
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromString(`Failed to get inbound user invites ${response}`)
|
||||
}
|
||||
|
||||
this.pendingInvites = {}
|
||||
|
||||
await this.processInboundInvites(response.data.invites)
|
||||
|
||||
return response.data.invites
|
||||
}
|
||||
|
||||
public async getOutboundInvites(
|
||||
sharedVault?: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError> {
|
||||
const response = await this.invitesServer.getOutboundUserInvites()
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromString(`Failed to get outbound user invites ${response}`)
|
||||
}
|
||||
|
||||
if (sharedVault) {
|
||||
return response.data.invites.filter((invite) => invite.shared_vault_uuid === sharedVault.sharing.sharedVaultUuid)
|
||||
}
|
||||
|
||||
return response.data.invites
|
||||
}
|
||||
|
||||
public async deleteInvite(invite: SharedVaultInviteServerHash): Promise<ClientDisplayableError | void> {
|
||||
const response = await this.invitesServer.deleteInvite({
|
||||
sharedVaultUuid: invite.shared_vault_uuid,
|
||||
inviteUuid: invite.uuid,
|
||||
})
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromString(`Failed to delete invite ${response}`)
|
||||
}
|
||||
|
||||
delete this.pendingInvites[invite.uuid]
|
||||
}
|
||||
|
||||
public async deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
|
||||
return this.deleteSharedVaultUseCase.execute({ sharedVault })
|
||||
}
|
||||
|
||||
private async reprocessCachedInvitesTrustStatusAfterTrustedContactsChange(): Promise<void> {
|
||||
const cachedInvites = this.getCachedPendingInviteRecords().map((record) => record.invite)
|
||||
|
||||
await this.processInboundInvites(cachedInvites)
|
||||
}
|
||||
|
||||
private async processInboundInvites(invites: SharedVaultInviteServerHash[]): Promise<void> {
|
||||
if (invites.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const invite of invites) {
|
||||
const sender = this.findContact.execute({ userUuid: invite.sender_uuid })
|
||||
if (!sender.isFailed()) {
|
||||
const trustedMessage = this.getTrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
|
||||
message: invite,
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
sender: sender.getValue(),
|
||||
})
|
||||
|
||||
if (!trustedMessage.isFailed()) {
|
||||
this.pendingInvites[invite.uuid] = {
|
||||
invite,
|
||||
message: trustedMessage.getValue(),
|
||||
trusted: true,
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const untrustedMessage = this.getUntrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
|
||||
message: invite,
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
})
|
||||
|
||||
if (!untrustedMessage.isFailed()) {
|
||||
this.pendingInvites[invite.uuid] = {
|
||||
invite,
|
||||
message: untrustedMessage.getValue(),
|
||||
trusted: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.notifyCollaborationStatusChanged()
|
||||
}
|
||||
|
||||
private async notifyCollaborationStatusChanged(): Promise<void> {
|
||||
await this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
|
||||
}
|
||||
|
||||
async acceptPendingSharedVaultInvite(pendingInvite: PendingSharedVaultInviteRecord): Promise<void> {
|
||||
if (!pendingInvite.trusted) {
|
||||
throw new Error('Cannot accept untrusted invite')
|
||||
}
|
||||
|
||||
await this.acceptVaultInvite.execute({ invite: pendingInvite.invite, message: pendingInvite.message })
|
||||
|
||||
delete this.pendingInvites[pendingInvite.invite.uuid]
|
||||
|
||||
void this.sync.sync()
|
||||
|
||||
await this.decryptErroredItemsAfterInviteAccept()
|
||||
|
||||
await this.sync.syncSharedVaultsFromScratch([pendingInvite.invite.shared_vault_uuid])
|
||||
}
|
||||
|
||||
private async decryptErroredItemsAfterInviteAccept(): Promise<void> {
|
||||
await this.encryption.decryptErroredPayloads()
|
||||
}
|
||||
|
||||
public async getInvitableContactsForSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
): Promise<TrustedContactInterface[]> {
|
||||
const users = await this.getSharedVaultUsers(sharedVault)
|
||||
if (!users) {
|
||||
return []
|
||||
}
|
||||
|
||||
const contacts = this.getAllContacts.execute()
|
||||
if (contacts.isFailed()) {
|
||||
return []
|
||||
}
|
||||
return contacts.getValue().filter((contact) => {
|
||||
const isContactAlreadyInVault = users.some((user) => user.user_uuid === contact.contactUuid)
|
||||
return !isContactAlreadyInVault
|
||||
})
|
||||
}
|
||||
|
||||
private async getSharedVaultContacts(sharedVault: SharedVaultListingInterface): Promise<TrustedContactInterface[]> {
|
||||
const contacts = await this.getVaultContacts.execute(sharedVault.sharing.sharedVaultUuid)
|
||||
if (contacts.isFailed()) {
|
||||
return []
|
||||
}
|
||||
|
||||
return contacts.getValue()
|
||||
}
|
||||
|
||||
async inviteContactToSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
contact: TrustedContactInterface,
|
||||
permissions: SharedVaultPermission,
|
||||
): Promise<Result<SharedVaultInviteServerHash>> {
|
||||
const sharedVaultContacts = await this.getSharedVaultContacts(sharedVault)
|
||||
|
||||
const result = await this.inviteToVault.execute({
|
||||
keys: {
|
||||
encryption: this.encryption.getKeyPair(),
|
||||
signing: this.encryption.getSigningKeyPair(),
|
||||
},
|
||||
senderUuid: this.session.getSureUser().uuid,
|
||||
sharedVault,
|
||||
recipient: contact,
|
||||
sharedVaultContacts,
|
||||
permissions,
|
||||
})
|
||||
|
||||
void this.notifyCollaborationStatusChanged()
|
||||
|
||||
await this.sync.sync()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async removeUserFromSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
userUuid: string,
|
||||
): Promise<ClientDisplayableError | void> {
|
||||
if (!this.isCurrentUserSharedVaultAdmin(sharedVault)) {
|
||||
throw new Error('Only vault admins can remove users')
|
||||
}
|
||||
|
||||
if (this.vaults.isVaultLocked(sharedVault)) {
|
||||
throw new Error('Cannot remove user from locked vault')
|
||||
}
|
||||
|
||||
const result = await this.removeVaultMember.execute({
|
||||
sharedVaultUuid: sharedVault.sharing.sharedVaultUuid,
|
||||
userUuid,
|
||||
})
|
||||
if (isClientDisplayableError(result)) {
|
||||
return result
|
||||
}
|
||||
|
||||
void this.notifyCollaborationStatusChanged()
|
||||
|
||||
await this.vaults.rotateVaultRootKey(sharedVault)
|
||||
}
|
||||
|
||||
async leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
|
||||
const result = await this.leaveVault.execute({
|
||||
sharedVault: sharedVault,
|
||||
userUuid: this.session.getSureUser().uuid,
|
||||
})
|
||||
|
||||
if (isClientDisplayableError(result)) {
|
||||
return result
|
||||
}
|
||||
|
||||
void this.notifyCollaborationStatusChanged()
|
||||
}
|
||||
|
||||
async getSharedVaultUsers(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultUserServerHash[] | undefined> {
|
||||
return this.getSharedVaultUsersUseCase.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid })
|
||||
return this._deleteSharedVaultUseCase.execute({ sharedVault })
|
||||
}
|
||||
|
||||
async shareContactWithVaults(contact: TrustedContactInterface): Promise<void> {
|
||||
@@ -486,10 +228,17 @@ export class SharedVaultService
|
||||
throw new Error('Cannot share self contact')
|
||||
}
|
||||
|
||||
const ownedVaults = this.getAllSharedVaults().filter(this.isCurrentUserSharedVaultAdmin.bind(this))
|
||||
const ownedVaults = this.getAllSharedVaults().filter((vault) => {
|
||||
return this._isVaultAdmin
|
||||
.execute({
|
||||
sharedVault: vault,
|
||||
userUuid: this.session.userUuid,
|
||||
})
|
||||
.getValue()
|
||||
})
|
||||
|
||||
for (const vault of ownedVaults) {
|
||||
await this.shareContactWithVault.execute({
|
||||
await this._shareContactWithVault.execute({
|
||||
keys: {
|
||||
encryption: this.encryption.getKeyPair(),
|
||||
signing: this.encryption.getSigningKeyPair(),
|
||||
@@ -506,7 +255,7 @@ export class SharedVaultService
|
||||
return undefined
|
||||
}
|
||||
|
||||
const contact = this.findContact.execute({ userUuid: item.last_edited_by_uuid })
|
||||
const contact = this._findContact.execute({ userUuid: item.last_edited_by_uuid })
|
||||
|
||||
return contact.isFailed() ? undefined : contact.getValue()
|
||||
}
|
||||
@@ -516,37 +265,8 @@ export class SharedVaultService
|
||||
return undefined
|
||||
}
|
||||
|
||||
const contact = this.findContact.execute({ userUuid: item.user_uuid })
|
||||
const contact = this._findContact.execute({ userUuid: item.user_uuid })
|
||||
|
||||
return contact.isFailed() ? undefined : contact.getValue()
|
||||
}
|
||||
|
||||
override deinit(): void {
|
||||
super.deinit()
|
||||
;(this.sync as unknown) = undefined
|
||||
;(this.items as unknown) = undefined
|
||||
;(this.encryption as unknown) = undefined
|
||||
;(this.session as unknown) = undefined
|
||||
;(this.vaults as unknown) = undefined
|
||||
;(this.invitesServer as unknown) = undefined
|
||||
;(this.getVault as unknown) = undefined
|
||||
;(this.createSharedVaultUseCase as unknown) = undefined
|
||||
;(this.handleKeyPairChange as unknown) = undefined
|
||||
;(this.notifyVaultUsersOfKeyRotation as unknown) = undefined
|
||||
;(this.sendVaultDataChangeMessage as unknown) = undefined
|
||||
;(this.getTrustedPayload as unknown) = undefined
|
||||
;(this.getUntrustedPayload as unknown) = undefined
|
||||
;(this.findContact as unknown) = undefined
|
||||
;(this.getAllContacts as unknown) = undefined
|
||||
;(this.getVaultContacts as unknown) = undefined
|
||||
;(this.acceptVaultInvite as unknown) = undefined
|
||||
;(this.inviteToVault as unknown) = undefined
|
||||
;(this.leaveVault as unknown) = undefined
|
||||
;(this.deleteThirdPartyVault as unknown) = undefined
|
||||
;(this.shareContactWithVault as unknown) = undefined
|
||||
;(this.convertToSharedVault as unknown) = undefined
|
||||
;(this.deleteSharedVaultUseCase as unknown) = undefined
|
||||
;(this.removeVaultMember as unknown) = undefined
|
||||
;(this.getSharedVaultUsersUseCase as unknown) = undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
ClientDisplayableError,
|
||||
SharedVaultInviteServerHash,
|
||||
SharedVaultUserServerHash,
|
||||
SharedVaultPermission,
|
||||
} from '@standardnotes/responses'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import {
|
||||
DecryptedItemInterface,
|
||||
TrustedContactInterface,
|
||||
@@ -13,8 +8,6 @@ import {
|
||||
} from '@standardnotes/models'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent'
|
||||
import { PendingSharedVaultInviteRecord } from './PendingSharedVaultInviteRecord'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
export interface SharedVaultServiceInterface
|
||||
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload> {
|
||||
@@ -25,32 +18,8 @@ export interface SharedVaultServiceInterface
|
||||
storagePreference?: KeySystemRootKeyStorageMode
|
||||
}): Promise<VaultListingInterface | ClientDisplayableError>
|
||||
deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void>
|
||||
|
||||
convertVaultToSharedVault(vault: VaultListingInterface): Promise<SharedVaultListingInterface | ClientDisplayableError>
|
||||
|
||||
inviteContactToSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
contact: TrustedContactInterface,
|
||||
permissions: SharedVaultPermission,
|
||||
): Promise<Result<SharedVaultInviteServerHash>>
|
||||
removeUserFromSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
userUuid: string,
|
||||
): Promise<ClientDisplayableError | void>
|
||||
leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void>
|
||||
getSharedVaultUsers(sharedVault: SharedVaultListingInterface): Promise<SharedVaultUserServerHash[] | undefined>
|
||||
isSharedVaultUserSharedVaultOwner(user: SharedVaultUserServerHash): boolean
|
||||
isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean
|
||||
|
||||
getItemLastEditedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined
|
||||
getItemSharedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined
|
||||
|
||||
downloadInboundInvites(): Promise<ClientDisplayableError | SharedVaultInviteServerHash[]>
|
||||
getOutboundInvites(
|
||||
sharedVault?: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError>
|
||||
acceptPendingSharedVaultInvite(pendingInvite: PendingSharedVaultInviteRecord): Promise<void>
|
||||
getCachedPendingInviteRecords(): PendingSharedVaultInviteRecord[]
|
||||
getInvitableContactsForSharedVault(sharedVault: SharedVaultListingInterface): Promise<TrustedContactInterface[]>
|
||||
deleteInvite(invite: SharedVaultInviteServerHash): Promise<ClientDisplayableError | void>
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import { SharedVaultInviteServerHash, isErrorResponse } from '@standardnotes/res
|
||||
import { SendVaultKeyChangedMessage } from './SendVaultKeyChangedMessage'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { InviteToVault } from './InviteToVault'
|
||||
import { GetVaultContacts } from './GetVaultContacts'
|
||||
import { InviteToVault } from '../../VaultInvite/UseCase/InviteToVault'
|
||||
import { GetVaultContacts } from '../../VaultUser/UseCase/GetVaultContacts'
|
||||
import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
|
||||
import { FindContact } from '../../Contacts/UseCase/FindContact'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
TrustedContactInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { GetVaultUsers } from './GetVaultUsers'
|
||||
import { GetVaultUsers } from '../../VaultUser/UseCase/GetVaultUsers'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
|
||||
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
TrustedContactInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { GetVaultUsers } from './GetVaultUsers'
|
||||
import { GetVaultUsers } from '../../VaultUser/UseCase/GetVaultUsers'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
|
||||
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
|
||||
|
||||
@@ -8,7 +8,7 @@ import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
|
||||
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { FindContact } from '../../Contacts/UseCase/FindContact'
|
||||
import { GetVaultUsers } from './GetVaultUsers'
|
||||
import { GetVaultUsers } from '../../VaultUser/UseCase/GetVaultUsers'
|
||||
|
||||
export class ShareContactWithVault implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import {
|
||||
ClientDisplayableError,
|
||||
SharedVaultInviteServerHash,
|
||||
isErrorResponse,
|
||||
SharedVaultPermission,
|
||||
} from '@standardnotes/responses'
|
||||
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
|
||||
|
||||
export class UpdateSharedVaultInviteUseCase {
|
||||
constructor(private vaultInvitesServer: SharedVaultInvitesServerInterface) {}
|
||||
|
||||
async execute(params: {
|
||||
sharedVaultUuid: string
|
||||
inviteUuid: string
|
||||
encryptedMessage: string
|
||||
permissions: SharedVaultPermission
|
||||
}): Promise<SharedVaultInviteServerHash | ClientDisplayableError> {
|
||||
const response = await this.vaultInvitesServer.updateInvite({
|
||||
sharedVaultUuid: params.sharedVaultUuid,
|
||||
inviteUuid: params.inviteUuid,
|
||||
encryptedMessage: params.encryptedMessage,
|
||||
permissions: params.permissions,
|
||||
})
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromNetworkError(response)
|
||||
}
|
||||
|
||||
return response.data.invite
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { AsymmetricMessageSharedVaultInvite } from '@standardnotes/models'
|
||||
import { SharedVaultInviteServerHash } from '@standardnotes/responses'
|
||||
|
||||
export type PendingSharedVaultInviteRecord = {
|
||||
export type InviteRecord = {
|
||||
invite: SharedVaultInviteServerHash
|
||||
message: AsymmetricMessageSharedVaultInvite
|
||||
trusted: boolean
|
||||
+1
-1
@@ -9,7 +9,7 @@ import { SendVaultInvite } from './SendVaultInvite'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { ShareContactWithVault } from './ShareContactWithVault'
|
||||
import { ShareContactWithVault } from '../../SharedVaults/UseCase/ShareContactWithVault'
|
||||
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
|
||||
|
||||
export class InviteToVault implements UseCaseInterface<SharedVaultInviteServerHash> {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { DecryptOwnMessage } from './../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
|
||||
import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
|
||||
import { AsymmetricMessageSharedVaultInvite, TrustedContactInterface } from '@standardnotes/models'
|
||||
import { SharedVaultInviteServerHash } from '@standardnotes/responses'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
@@ -0,0 +1,274 @@
|
||||
import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite'
|
||||
import { SyncEvent, SyncEventReceivedSharedVaultInvitesData } from './../Event/SyncEvent'
|
||||
import { SessionEvent } from './../Session/SessionEvent'
|
||||
import { InternalEventInterface } from './../Internal/InternalEventInterface'
|
||||
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
|
||||
import { ItemManagerInterface } from './../Item/ItemManagerInterface'
|
||||
import { FindContact } from './../Contacts/UseCase/FindContact'
|
||||
import { GetUntrustedPayload } from './../AsymmetricMessage/UseCase/GetUntrustedPayload'
|
||||
import { GetTrustedPayload } from './../AsymmetricMessage/UseCase/GetTrustedPayload'
|
||||
import { InviteRecord } from './InviteRecord'
|
||||
import { VaultUserServiceInterface } from './../VaultUser/VaultUserServiceInterface'
|
||||
import { GetVault } from './../Vaults/UseCase/GetVault'
|
||||
import { InviteToVault } from './UseCase/InviteToVault'
|
||||
import { GetVaultContacts } from '../VaultUser/UseCase/GetVaultContacts'
|
||||
import { SyncServiceInterface } from './../Sync/SyncServiceInterface'
|
||||
import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface'
|
||||
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
|
||||
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
|
||||
import { GetAllContacts } from './../Contacts/UseCase/GetAllContacts'
|
||||
import {
|
||||
AsymmetricMessageSharedVaultInvite,
|
||||
PayloadEmitSource,
|
||||
SharedVaultListingInterface,
|
||||
TrustedContactInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { VaultInviteServiceInterface } from './VaultInviteServiceInterface'
|
||||
import {
|
||||
ClientDisplayableError,
|
||||
SharedVaultInviteServerHash,
|
||||
SharedVaultPermission,
|
||||
SharedVaultUserServerHash,
|
||||
isErrorResponse,
|
||||
} from '@standardnotes/responses'
|
||||
import { AbstractService } from './../Service/AbstractService'
|
||||
import { VaultInviteServiceEvent } from './VaultInviteServiceEvent'
|
||||
import { ContentType, Result } from '@standardnotes/domain-core'
|
||||
import { SharedVaultInvitesServer } from '@standardnotes/api'
|
||||
|
||||
export class VaultInviteService
|
||||
extends AbstractService<VaultInviteServiceEvent>
|
||||
implements VaultInviteServiceInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private pendingInvites: Record<string, InviteRecord> = {}
|
||||
|
||||
constructor(
|
||||
items: ItemManagerInterface,
|
||||
private session: SessionsClientInterface,
|
||||
private vaultUsers: VaultUserServiceInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
private encryption: EncryptionProviderInterface,
|
||||
private invitesServer: SharedVaultInvitesServer,
|
||||
private _getAllContacts: GetAllContacts,
|
||||
private _getVault: GetVault,
|
||||
private _getVaultContacts: GetVaultContacts,
|
||||
private _inviteToVault: InviteToVault,
|
||||
private _getTrustedPayload: GetTrustedPayload,
|
||||
private _getUntrustedPayload: GetUntrustedPayload,
|
||||
private _findContact: FindContact,
|
||||
private _acceptVaultInvite: AcceptVaultInvite,
|
||||
eventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(eventBus)
|
||||
|
||||
this.eventDisposers.push(
|
||||
items.addObserver<TrustedContactInterface>(ContentType.TYPES.TrustedContact, async ({ inserted, source }) => {
|
||||
if (source === PayloadEmitSource.LocalChanged && inserted.length > 0) {
|
||||
void this.downloadInboundInvites()
|
||||
}
|
||||
|
||||
await this.reprocessCachedInvitesTrustStatusAfterTrustedContactsChange()
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
override deinit(): void {
|
||||
super.deinit()
|
||||
;(this.session as unknown) = undefined
|
||||
;(this.vaultUsers as unknown) = undefined
|
||||
;(this.sync as unknown) = undefined
|
||||
;(this.encryption as unknown) = undefined
|
||||
;(this.invitesServer as unknown) = undefined
|
||||
;(this._getAllContacts as unknown) = undefined
|
||||
;(this._getVault as unknown) = undefined
|
||||
;(this._getVaultContacts as unknown) = undefined
|
||||
;(this._inviteToVault as unknown) = undefined
|
||||
;(this._getTrustedPayload as unknown) = undefined
|
||||
;(this._getUntrustedPayload as unknown) = undefined
|
||||
;(this._findContact as unknown) = undefined
|
||||
;(this._acceptVaultInvite as unknown) = undefined
|
||||
|
||||
this.pendingInvites = {}
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
switch (event.type) {
|
||||
case SessionEvent.UserKeyPairChanged:
|
||||
void this.invitesServer.deleteAllInboundInvites()
|
||||
break
|
||||
case SyncEvent.ReceivedSharedVaultInvites:
|
||||
await this.processInboundInvites(event.payload as SyncEventReceivedSharedVaultInvitesData)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public getCachedPendingInviteRecords(): InviteRecord[] {
|
||||
return Object.values(this.pendingInvites)
|
||||
}
|
||||
|
||||
public async downloadInboundInvites(): Promise<ClientDisplayableError | SharedVaultInviteServerHash[]> {
|
||||
const response = await this.invitesServer.getInboundUserInvites()
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromString(`Failed to get inbound user invites ${response}`)
|
||||
}
|
||||
|
||||
this.pendingInvites = {}
|
||||
|
||||
await this.processInboundInvites(response.data.invites)
|
||||
|
||||
return response.data.invites
|
||||
}
|
||||
|
||||
public async getOutboundInvites(
|
||||
sharedVault?: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError> {
|
||||
const response = await this.invitesServer.getOutboundUserInvites()
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromString(`Failed to get outbound user invites ${response}`)
|
||||
}
|
||||
|
||||
if (sharedVault) {
|
||||
return response.data.invites.filter((invite) => invite.shared_vault_uuid === sharedVault.sharing.sharedVaultUuid)
|
||||
}
|
||||
|
||||
return response.data.invites
|
||||
}
|
||||
|
||||
public async acceptInvite(pendingInvite: InviteRecord): Promise<void> {
|
||||
if (!pendingInvite.trusted) {
|
||||
throw new Error('Cannot accept untrusted invite')
|
||||
}
|
||||
|
||||
await this._acceptVaultInvite.execute({ invite: pendingInvite.invite, message: pendingInvite.message })
|
||||
|
||||
delete this.pendingInvites[pendingInvite.invite.uuid]
|
||||
|
||||
void this.sync.sync()
|
||||
|
||||
await this.encryption.decryptErroredPayloads()
|
||||
|
||||
await this.sync.syncSharedVaultsFromScratch([pendingInvite.invite.shared_vault_uuid])
|
||||
}
|
||||
|
||||
public async getInvitableContactsForSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
): Promise<TrustedContactInterface[]> {
|
||||
const users = await this.vaultUsers.getSharedVaultUsers(sharedVault)
|
||||
if (!users) {
|
||||
return []
|
||||
}
|
||||
|
||||
const contacts = this._getAllContacts.execute()
|
||||
if (contacts.isFailed()) {
|
||||
return []
|
||||
}
|
||||
return contacts.getValue().filter((contact) => {
|
||||
const isContactAlreadyInVault = users.some((user) => user.user_uuid === contact.contactUuid)
|
||||
return !isContactAlreadyInVault
|
||||
})
|
||||
}
|
||||
|
||||
public async inviteContactToSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
contact: TrustedContactInterface,
|
||||
permissions: SharedVaultPermission,
|
||||
): Promise<Result<SharedVaultInviteServerHash>> {
|
||||
const contactsResult = await this._getVaultContacts.execute(sharedVault.sharing.sharedVaultUuid)
|
||||
if (contactsResult.isFailed()) {
|
||||
return Result.fail(contactsResult.getError())
|
||||
}
|
||||
|
||||
const contacts = contactsResult.getValue()
|
||||
|
||||
const result = await this._inviteToVault.execute({
|
||||
keys: {
|
||||
encryption: this.encryption.getKeyPair(),
|
||||
signing: this.encryption.getSigningKeyPair(),
|
||||
},
|
||||
senderUuid: this.session.getSureUser().uuid,
|
||||
sharedVault,
|
||||
recipient: contact,
|
||||
sharedVaultContacts: contacts,
|
||||
permissions,
|
||||
})
|
||||
|
||||
void this.notifyEvent(VaultInviteServiceEvent.InviteSent)
|
||||
|
||||
await this.sync.sync()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public isVaultUserOwner(user: SharedVaultUserServerHash): boolean {
|
||||
const result = this._getVault.execute<SharedVaultListingInterface>({ sharedVaultUuid: user.shared_vault_uuid })
|
||||
if (result.isFailed()) {
|
||||
return false
|
||||
}
|
||||
|
||||
const vault = result.getValue()
|
||||
return vault != undefined && vault.sharing.ownerUserUuid === user.user_uuid
|
||||
}
|
||||
|
||||
public async deleteInvite(invite: SharedVaultInviteServerHash): Promise<ClientDisplayableError | void> {
|
||||
const response = await this.invitesServer.deleteInvite({
|
||||
sharedVaultUuid: invite.shared_vault_uuid,
|
||||
inviteUuid: invite.uuid,
|
||||
})
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromString(`Failed to delete invite ${response}`)
|
||||
}
|
||||
|
||||
delete this.pendingInvites[invite.uuid]
|
||||
}
|
||||
|
||||
private async reprocessCachedInvitesTrustStatusAfterTrustedContactsChange(): Promise<void> {
|
||||
const cachedInvites = this.getCachedPendingInviteRecords().map((record) => record.invite)
|
||||
|
||||
await this.processInboundInvites(cachedInvites)
|
||||
}
|
||||
|
||||
private async processInboundInvites(invites: SharedVaultInviteServerHash[]): Promise<void> {
|
||||
if (invites.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const invite of invites) {
|
||||
const sender = this._findContact.execute({ userUuid: invite.sender_uuid })
|
||||
if (!sender.isFailed()) {
|
||||
const trustedMessage = this._getTrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
|
||||
message: invite,
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
sender: sender.getValue(),
|
||||
})
|
||||
|
||||
if (!trustedMessage.isFailed()) {
|
||||
this.pendingInvites[invite.uuid] = {
|
||||
invite,
|
||||
message: trustedMessage.getValue(),
|
||||
trusted: true,
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const untrustedMessage = this._getUntrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
|
||||
message: invite,
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
})
|
||||
|
||||
if (!untrustedMessage.isFailed()) {
|
||||
this.pendingInvites[invite.uuid] = {
|
||||
invite,
|
||||
message: untrustedMessage.getValue(),
|
||||
trusted: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void this.notifyEvent(VaultInviteServiceEvent.InvitesReloaded)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum VaultInviteServiceEvent {
|
||||
InviteSent = 'VaultInviteServiceEvent.InviteSent',
|
||||
InvitesReloaded = 'VaultInviteServiceEvent.InvitesReloaded',
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { InviteRecord } from './InviteRecord'
|
||||
import { ApplicationServiceInterface } from '../Service/ApplicationServiceInterface'
|
||||
import { SharedVaultListingInterface, TrustedContactInterface } from '@standardnotes/models'
|
||||
import { ClientDisplayableError, SharedVaultInviteServerHash, SharedVaultPermission } from '@standardnotes/responses'
|
||||
import { VaultInviteServiceEvent } from './VaultInviteServiceEvent'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
export interface VaultInviteServiceInterface extends ApplicationServiceInterface<VaultInviteServiceEvent, unknown> {
|
||||
getInvitableContactsForSharedVault(sharedVault: SharedVaultListingInterface): Promise<TrustedContactInterface[]>
|
||||
inviteContactToSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
contact: TrustedContactInterface,
|
||||
permissions: SharedVaultPermission,
|
||||
): Promise<Result<SharedVaultInviteServerHash>>
|
||||
getCachedPendingInviteRecords(): InviteRecord[]
|
||||
deleteInvite(invite: SharedVaultInviteServerHash): Promise<ClientDisplayableError | void>
|
||||
downloadInboundInvites(): Promise<ClientDisplayableError | SharedVaultInviteServerHash[]>
|
||||
getOutboundInvites(
|
||||
sharedVault?: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError>
|
||||
acceptInvite(pendingInvite: InviteRecord): Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
|
||||
export class IsVaultAdmin implements SyncUseCaseInterface<boolean> {
|
||||
execute(dto: { sharedVault: SharedVaultListingInterface; userUuid: string }): Result<boolean> {
|
||||
if (!dto.sharedVault.sharing.ownerUserUuid) {
|
||||
throw new Error(`Shared vault ${dto.sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
|
||||
}
|
||||
|
||||
return Result.ok(dto.sharedVault.sharing.ownerUserUuid === dto.userUuid)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
|
||||
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
|
||||
import { DeleteThirdPartyVault } from './DeleteExternalSharedVault'
|
||||
import { DeleteThirdPartyVault } from '../../SharedVaults/UseCase/DeleteExternalSharedVault'
|
||||
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
|
||||
+7
-4
@@ -1,17 +1,20 @@
|
||||
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
|
||||
import { getErrorFromErrorResponse, isErrorResponse } from '@standardnotes/responses'
|
||||
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export class RemoveVaultMember {
|
||||
export class RemoveVaultMember implements UseCaseInterface<void> {
|
||||
constructor(private vaultUserServer: SharedVaultUsersServerInterface) {}
|
||||
|
||||
async execute(params: { sharedVaultUuid: string; userUuid: string }): Promise<ClientDisplayableError | void> {
|
||||
async execute(params: { sharedVaultUuid: string; userUuid: string }): Promise<Result<void>> {
|
||||
const response = await this.vaultUserServer.deleteSharedVaultUser({
|
||||
sharedVaultUuid: params.sharedVaultUuid,
|
||||
userUuid: params.userUuid,
|
||||
})
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromNetworkError(response)
|
||||
return Result.fail(getErrorFromErrorResponse(response).message)
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { LeaveVault } from './UseCase/LeaveSharedVault'
|
||||
import { GetVault } from './../Vaults/UseCase/GetVault'
|
||||
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
|
||||
import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember'
|
||||
import { VaultServiceInterface } from './../Vaults/VaultServiceInterface'
|
||||
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
|
||||
import { GetVaultUsers } from './UseCase/GetVaultUsers'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
import { VaultUserServiceInterface } from './VaultUserServiceInterface'
|
||||
import { ClientDisplayableError, SharedVaultUserServerHash, isClientDisplayableError } from '@standardnotes/responses'
|
||||
import { AbstractService } from './../Service/AbstractService'
|
||||
import { VaultUserServiceEvent } from './VaultUserServiceEvent'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { IsVaultAdmin } from './UseCase/IsVaultAdmin'
|
||||
|
||||
export class VaultUserService extends AbstractService<VaultUserServiceEvent> implements VaultUserServiceInterface {
|
||||
constructor(
|
||||
private session: SessionsClientInterface,
|
||||
private vaults: VaultServiceInterface,
|
||||
private _getVaultUsers: GetVaultUsers,
|
||||
private _removeVaultMember: RemoveVaultMember,
|
||||
private _isVaultAdmin: IsVaultAdmin,
|
||||
private _getVault: GetVault,
|
||||
private _leaveVault: LeaveVault,
|
||||
eventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(eventBus)
|
||||
}
|
||||
|
||||
override deinit(): void {
|
||||
super.deinit()
|
||||
;(this.session as unknown) = undefined
|
||||
;(this.vaults as unknown) = undefined
|
||||
;(this._getVaultUsers as unknown) = undefined
|
||||
;(this._removeVaultMember as unknown) = undefined
|
||||
;(this._isVaultAdmin as unknown) = undefined
|
||||
;(this._getVault as unknown) = undefined
|
||||
;(this._leaveVault as unknown) = undefined
|
||||
}
|
||||
|
||||
public async getSharedVaultUsers(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultUserServerHash[] | undefined> {
|
||||
return this._getVaultUsers.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid })
|
||||
}
|
||||
|
||||
public isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean {
|
||||
return this._isVaultAdmin
|
||||
.execute({
|
||||
sharedVault,
|
||||
userUuid: this.session.userUuid,
|
||||
})
|
||||
.getValue()
|
||||
}
|
||||
|
||||
async removeUserFromSharedVault(sharedVault: SharedVaultListingInterface, userUuid: string): Promise<Result<void>> {
|
||||
if (!this.isCurrentUserSharedVaultAdmin(sharedVault)) {
|
||||
throw new Error('Only vault admins can remove users')
|
||||
}
|
||||
|
||||
if (this.vaults.isVaultLocked(sharedVault)) {
|
||||
throw new Error('Cannot remove user from locked vault')
|
||||
}
|
||||
|
||||
const result = await this._removeVaultMember.execute({
|
||||
sharedVaultUuid: sharedVault.sharing.sharedVaultUuid,
|
||||
userUuid,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return result
|
||||
}
|
||||
|
||||
void this.notifyEvent(VaultUserServiceEvent.UsersChanged)
|
||||
|
||||
await this.vaults.rotateVaultRootKey(sharedVault)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
public isVaultUserOwner(user: SharedVaultUserServerHash): boolean {
|
||||
const result = this._getVault.execute<SharedVaultListingInterface>({ sharedVaultUuid: user.shared_vault_uuid })
|
||||
if (result.isFailed()) {
|
||||
return false
|
||||
}
|
||||
|
||||
const vault = result.getValue()
|
||||
return vault != undefined && vault.sharing.ownerUserUuid === user.user_uuid
|
||||
}
|
||||
|
||||
async leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
|
||||
const result = await this._leaveVault.execute({
|
||||
sharedVault: sharedVault,
|
||||
userUuid: this.session.getSureUser().uuid,
|
||||
})
|
||||
|
||||
if (isClientDisplayableError(result)) {
|
||||
return result
|
||||
}
|
||||
|
||||
void this.notifyEvent(VaultUserServiceEvent.UsersChanged)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum VaultUserServiceEvent {
|
||||
UsersChanged = 'VaultUserServiceEvent.UsersChanged',
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ApplicationServiceInterface } from './../Service/ApplicationServiceInterface'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
import { ClientDisplayableError, SharedVaultUserServerHash } from '@standardnotes/responses'
|
||||
import { VaultUserServiceEvent } from './VaultUserServiceEvent'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
export interface VaultUserServiceInterface extends ApplicationServiceInterface<VaultUserServiceEvent, unknown> {
|
||||
getSharedVaultUsers(sharedVault: SharedVaultListingInterface): Promise<SharedVaultUserServerHash[] | undefined>
|
||||
isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean
|
||||
removeUserFromSharedVault(sharedVault: SharedVaultListingInterface, userUuid: string): Promise<Result<void>>
|
||||
leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void>
|
||||
isVaultUserOwner(user: SharedVaultUserServerHash): boolean
|
||||
}
|
||||
@@ -57,15 +57,15 @@ export * from './Device/MobileDeviceInterface'
|
||||
export * from './Device/TypeCheck'
|
||||
export * from './Device/WebOrDesktopDeviceInterface'
|
||||
export * from './Diagnostics/ServiceDiagnostics'
|
||||
export * from './Encryption/UseCase/DecryptBackupFile'
|
||||
export * from './Encryption/EncryptionService'
|
||||
export * from './Encryption/EncryptionProviderInterface'
|
||||
export * from './Encryption/EncryptionService'
|
||||
export * from './Encryption/EncryptionServiceEvent'
|
||||
export * from './Encryption/Functions'
|
||||
export * from './Encryption/UseCase/Asymmetric/DecryptMessage'
|
||||
export * from './Encryption/UseCase/Asymmetric/DecryptOwnMessage'
|
||||
export * from './Encryption/UseCase/Asymmetric/EncryptMessage'
|
||||
export * from './Encryption/UseCase/Asymmetric/GetMessageAdditionalData'
|
||||
export * from './Encryption/UseCase/DecryptBackupFile'
|
||||
export * from './Encryption/UseCase/ItemsKey/CreateNewDefaultItemsKey'
|
||||
export * from './Encryption/UseCase/ItemsKey/CreateNewItemsKeyWithRollback'
|
||||
export * from './Encryption/UseCase/ItemsKey/FindDefaultItemsKey'
|
||||
@@ -111,6 +111,7 @@ export * from './Item/ItemRelationshipDirection'
|
||||
export * from './Item/ItemsServerInterface'
|
||||
export * from './Item/StaticItemCounter'
|
||||
export * from './ItemsEncryption/ItemsEncryption'
|
||||
export * from './ItemsEncryption/ItemsEncryption'
|
||||
export * from './KeySystem/KeySystemKeyManager'
|
||||
export * from './Mutator/ImportDataUseCase'
|
||||
export * from './Mutator/MutatorClientInterface'
|
||||
@@ -121,39 +122,25 @@ export * from './Protection/ProtectionClientInterface'
|
||||
export * from './Protection/TimingDisplayOption'
|
||||
export * from './Revision/RevisionClientInterface'
|
||||
export * from './Revision/RevisionManager'
|
||||
export * from './RootKeyManager/RootKeyManager'
|
||||
export * from './RootKeyManager/KeyMode'
|
||||
export * from './ItemsEncryption/ItemsEncryption'
|
||||
export * from './RootKeyManager/RootKeyManager'
|
||||
export * from './Service/AbstractService'
|
||||
export * from './Service/ApplicationServiceInterface'
|
||||
export * from './Session/SessionEvent'
|
||||
export * from './Session/SessionManagerResponse'
|
||||
export * from './Session/SessionsClientInterface'
|
||||
export * from './Session/UserKeyPairChangedEventData'
|
||||
export * from './SharedVaults/PendingSharedVaultInviteRecord'
|
||||
export * from './SharedVaults/SharedVaultService'
|
||||
export * from './SharedVaults/SharedVaultServiceEvent'
|
||||
export * from './SharedVaults/SharedVaultServiceInterface'
|
||||
export * from './SharedVaults/UseCase/AcceptVaultInvite'
|
||||
export * from './SharedVaults/UseCase/ConvertToSharedVault'
|
||||
export * from './SharedVaults/UseCase/CreateSharedVault'
|
||||
export * from './SharedVaults/UseCase/DeleteExternalSharedVault'
|
||||
export * from './SharedVaults/UseCase/DeleteSharedVault'
|
||||
export * from './SharedVaults/UseCase/GetVaultContacts'
|
||||
export * from './SharedVaults/UseCase/GetVaultContacts'
|
||||
export * from './SharedVaults/UseCase/GetVaultUsers'
|
||||
export * from './SharedVaults/UseCase/InviteToVault'
|
||||
export * from './SharedVaults/UseCase/LeaveSharedVault'
|
||||
export * from './SharedVaults/UseCase/NotifyVaultUsersOfKeyRotation'
|
||||
export * from './SharedVaults/UseCase/RemoveSharedVaultMember'
|
||||
export * from './SharedVaults/UseCase/ReuploadAllInvites'
|
||||
export * from './SharedVaults/UseCase/ReuploadInvite'
|
||||
export * from './SharedVaults/UseCase/ReuploadVaultInvites'
|
||||
export * from './SharedVaults/UseCase/SendVaultDataChangedMessage'
|
||||
export * from './SharedVaults/UseCase/SendVaultInvite'
|
||||
export * from './SharedVaults/UseCase/SendVaultKeyChangedMessage'
|
||||
export * from './SharedVaults/UseCase/ShareContactWithVault'
|
||||
export * from './SharedVaults/UseCase/UpdateSharedVaultInvite'
|
||||
export * from './Singleton/SingletonManagerInterface'
|
||||
export * from './Status/StatusService'
|
||||
export * from './Status/StatusServiceInterface'
|
||||
@@ -186,6 +173,16 @@ export * from './User/UserClientInterface'
|
||||
export * from './User/UserService'
|
||||
export * from './UserEvent/UserEventService'
|
||||
export * from './UserEvent/UserEventServiceEvent'
|
||||
export * from './VaultInvite/InviteRecord'
|
||||
export * from './VaultInvite/UseCase/AcceptVaultInvite'
|
||||
export * from './VaultInvite/UseCase/InviteToVault'
|
||||
export * from './VaultInvite/UseCase/ReuploadAllInvites'
|
||||
export * from './VaultInvite/UseCase/ReuploadInvite'
|
||||
export * from './VaultInvite/UseCase/ReuploadVaultInvites'
|
||||
export * from './VaultInvite/UseCase/SendVaultInvite'
|
||||
export * from './VaultInvite/VaultInviteService'
|
||||
export * from './VaultInvite/VaultInviteServiceEvent'
|
||||
export * from './VaultInvite/VaultInviteServiceInterface'
|
||||
export * from './Vaults/ChangeVaultOptionsDTO'
|
||||
export * from './Vaults/UseCase/ChangeVaultKeyOptions'
|
||||
export * from './Vaults/UseCase/CreateVault'
|
||||
@@ -197,3 +194,12 @@ export * from './Vaults/UseCase/RotateVaultKey'
|
||||
export * from './Vaults/VaultService'
|
||||
export * from './Vaults/VaultServiceEvent'
|
||||
export * from './Vaults/VaultServiceInterface'
|
||||
export * from './VaultUser/UseCase/GetVaultContacts'
|
||||
export * from './VaultUser/UseCase/GetVaultContacts'
|
||||
export * from './VaultUser/UseCase/GetVaultUsers'
|
||||
export * from './VaultUser/UseCase/IsVaultAdmin'
|
||||
export * from './VaultUser/UseCase/LeaveSharedVault'
|
||||
export * from './VaultUser/UseCase/RemoveSharedVaultMember'
|
||||
export * from './VaultUser/VaultUserService'
|
||||
export * from './VaultUser/VaultUserServiceEvent'
|
||||
export * from './VaultUser/VaultUserServiceInterface'
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [2.202.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
## [2.202.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/snjs
|
||||
|
||||
@@ -71,6 +71,10 @@ import {
|
||||
HistoryServiceInterface,
|
||||
InternalEventPublishStrategy,
|
||||
EncryptionProviderInterface,
|
||||
VaultUserServiceInterface,
|
||||
VaultInviteServiceInterface,
|
||||
UserEventServiceEvent,
|
||||
VaultServiceEvent,
|
||||
} from '@standardnotes/services'
|
||||
import {
|
||||
PayloadEmitSource,
|
||||
@@ -1130,7 +1134,15 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.UserService), AccountEvent.SignedInOrRegistered)
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SessionManager), ApiServiceEvent.SessionRefreshed)
|
||||
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SyncEvent.ReceivedSharedVaultInvites)
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SyncEvent.ReceivedSharedVaultInvites)
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SessionEvent.UserKeyPairChanged)
|
||||
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SessionEvent.UserKeyPairChanged)
|
||||
this.events.addEventHandler(
|
||||
this.dependencies.get(TYPES.SharedVaultService),
|
||||
UserEventServiceEvent.UserEventReceived,
|
||||
)
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), VaultServiceEvent.VaultRootKeyRotated)
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SyncEvent.ReceivedRemoteSharedVaults)
|
||||
|
||||
this.events.addEventHandler(
|
||||
@@ -1332,14 +1344,26 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return this.dependencies.get<HistoryServiceInterface>(TYPES.HistoryManager)
|
||||
}
|
||||
|
||||
private get migrations(): MigrationService {
|
||||
return this.dependencies.get<MigrationService>(TYPES.MigrationService)
|
||||
}
|
||||
|
||||
public get encryption(): EncryptionProviderInterface {
|
||||
return this.dependencies.get<EncryptionProviderInterface>(TYPES.EncryptionService)
|
||||
}
|
||||
|
||||
public get events(): InternalEventBusInterface {
|
||||
return this.dependencies.get<InternalEventBusInterface>(TYPES.InternalEventBus)
|
||||
}
|
||||
|
||||
public get vaultUsers(): VaultUserServiceInterface {
|
||||
return this.dependencies.get<VaultUserServiceInterface>(TYPES.VaultUserService)
|
||||
}
|
||||
|
||||
public get vaultInvites(): VaultInviteServiceInterface {
|
||||
return this.dependencies.get<VaultInviteServiceInterface>(TYPES.VaultInviteService)
|
||||
}
|
||||
|
||||
private get migrations(): MigrationService {
|
||||
return this.dependencies.get<MigrationService>(TYPES.MigrationService)
|
||||
}
|
||||
|
||||
private get legacyApi(): LegacyApiService {
|
||||
return this.dependencies.get<LegacyApiService>(TYPES.LegacyApiService)
|
||||
}
|
||||
@@ -1352,10 +1376,6 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return this.dependencies.get<WebSocketsService>(TYPES.WebSocketsService)
|
||||
}
|
||||
|
||||
public get events(): InternalEventBusInterface {
|
||||
return this.dependencies.get<InternalEventBusInterface>(TYPES.InternalEventBus)
|
||||
}
|
||||
|
||||
private get mfa(): SNMfaService {
|
||||
return this.dependencies.get<SNMfaService>(TYPES.MfaService)
|
||||
}
|
||||
|
||||
@@ -108,6 +108,9 @@ import {
|
||||
RootKeyManager,
|
||||
ItemsEncryptionService,
|
||||
DecryptBackupFile,
|
||||
VaultUserService,
|
||||
IsVaultAdmin,
|
||||
VaultInviteService,
|
||||
} from '@standardnotes/services'
|
||||
import { ItemManager } from '../../Services/Items/ItemManager'
|
||||
import { PayloadManager } from '../../Services/Payloads/PayloadManager'
|
||||
@@ -204,6 +207,10 @@ export class Dependencies {
|
||||
)
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.IsVaultAdmin, () => {
|
||||
return new IsVaultAdmin()
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.DecryptBackupFile, () => {
|
||||
return new DecryptBackupFile(this.get(TYPES.EncryptionService))
|
||||
})
|
||||
@@ -608,6 +615,39 @@ export class Dependencies {
|
||||
return new SharedVaultUsersServer(this.get(TYPES.HttpService))
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.VaultUserService, () => {
|
||||
return new VaultUserService(
|
||||
this.get(TYPES.SessionManager),
|
||||
this.get(TYPES.VaultService),
|
||||
this.get(TYPES.GetVaultUsers),
|
||||
this.get(TYPES.RemoveVaultMember),
|
||||
this.get(TYPES.IsVaultAdmin),
|
||||
this.get(TYPES.GetVault),
|
||||
this.get(TYPES.LeaveVault),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.VaultInviteService, () => {
|
||||
return new VaultInviteService(
|
||||
this.get(TYPES.ItemManager),
|
||||
this.get(TYPES.SessionManager),
|
||||
this.get(TYPES.VaultUserService),
|
||||
this.get(TYPES.SyncService),
|
||||
this.get(TYPES.EncryptionService),
|
||||
this.get(TYPES.SharedVaultInvitesServer),
|
||||
this.get(TYPES.GetAllContacts),
|
||||
this.get(TYPES.GetVault),
|
||||
this.get(TYPES.GetVaultContacts),
|
||||
this.get(TYPES.InviteToVault),
|
||||
this.get(TYPES.GetTrustedPayload),
|
||||
this.get(TYPES.GetUntrustedPayload),
|
||||
this.get(TYPES.FindContact),
|
||||
this.get(TYPES.AcceptVaultInvite),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
|
||||
this.factory.set(TYPES.AsymmetricMessageService, () => {
|
||||
return new AsymmetricMessageService(
|
||||
this.get(TYPES.AsymmetricMessageServer),
|
||||
@@ -630,31 +670,21 @@ export class Dependencies {
|
||||
|
||||
this.factory.set(TYPES.SharedVaultService, () => {
|
||||
return new SharedVaultService(
|
||||
this.get(TYPES.SyncService),
|
||||
this.get(TYPES.ItemManager),
|
||||
this.get(TYPES.EncryptionService),
|
||||
this.get(TYPES.SessionManager),
|
||||
this.get(TYPES.VaultService),
|
||||
this.get(TYPES.SharedVaultInvitesServer),
|
||||
this.get(TYPES.GetVault),
|
||||
this.get(TYPES.CreateSharedVault),
|
||||
this.get(TYPES.HandleKeyPairChange),
|
||||
this.get(TYPES.NotifyVaultUsersOfKeyRotation),
|
||||
this.get(TYPES.SendVaultDataChangedMessage),
|
||||
this.get(TYPES.GetTrustedPayload),
|
||||
this.get(TYPES.GetUntrustedPayload),
|
||||
this.get(TYPES.FindContact),
|
||||
this.get(TYPES.GetAllContacts),
|
||||
this.get(TYPES.GetVaultContacts),
|
||||
this.get(TYPES.AcceptVaultInvite),
|
||||
this.get(TYPES.InviteToVault),
|
||||
this.get(TYPES.LeaveVault),
|
||||
this.get(TYPES.DeleteThirdPartyVault),
|
||||
this.get(TYPES.ShareContactWithVault),
|
||||
this.get(TYPES.ConvertToSharedVault),
|
||||
this.get(TYPES.DeleteSharedVault),
|
||||
this.get(TYPES.RemoveVaultMember),
|
||||
this.get(TYPES.GetVaultUsers),
|
||||
this.get(TYPES.IsVaultAdmin),
|
||||
this.get(TYPES.InternalEventBus),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -59,6 +59,8 @@ export const TYPES = {
|
||||
EncryptionOperators: Symbol.for('EncryptionOperators'),
|
||||
RootKeyManager: Symbol.for('RootKeyManager'),
|
||||
ItemsEncryptionService: Symbol.for('ItemsEncryptionService'),
|
||||
VaultUserService: Symbol.for('VaultUserService'),
|
||||
VaultInviteService: Symbol.for('VaultInviteService'),
|
||||
|
||||
// Servers
|
||||
RevisionServer: Symbol.for('RevisionServer'),
|
||||
@@ -140,6 +142,7 @@ export const TYPES = {
|
||||
EncryptTypeAPayload: Symbol.for('EncryptTypeAPayload'),
|
||||
EncryptTypeAPayloadWithKeyLookup: Symbol.for('EncryptTypeAPayloadWithKeyLookup'),
|
||||
DecryptBackupFile: Symbol.for('DecryptBackupFile'),
|
||||
IsVaultAdmin: Symbol.for('IsVaultAdmin'),
|
||||
|
||||
// Mappers
|
||||
SessionStorageMapper: Symbol.for('SessionStorageMapper'),
|
||||
|
||||
@@ -97,6 +97,14 @@ export class AppContext {
|
||||
return this.application.sharedVaults
|
||||
}
|
||||
|
||||
get vaultUsers() {
|
||||
return this.application.vaultUsers
|
||||
}
|
||||
|
||||
get vaultInvites() {
|
||||
return this.application.vaultInvites
|
||||
}
|
||||
|
||||
get files() {
|
||||
return this.application.files
|
||||
}
|
||||
@@ -210,16 +218,6 @@ export class AppContext {
|
||||
})
|
||||
}
|
||||
|
||||
resolveWhenSharedVaultUserKeysResolved() {
|
||||
return new Promise((resolve) => {
|
||||
this.application.vaults.collaboration.addEventObserver((eventName) => {
|
||||
if (eventName === SharedVaultServiceEvent.SharedVaultStatusChanged) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async awaitSignInEvent() {
|
||||
return new Promise((resolve) => {
|
||||
this.application.user.addEventObserver((eventName) => {
|
||||
@@ -394,7 +392,7 @@ export class AppContext {
|
||||
|
||||
resolveWhenAllInboundSharedVaultInvitesAreDeleted() {
|
||||
return new Promise((resolve) => {
|
||||
const objectToSpy = this.application.sharedVaults.invitesServer
|
||||
const objectToSpy = this.application.vaultInvites.invitesServer
|
||||
sinon.stub(objectToSpy, 'deleteAllInboundInvites').callsFake(async (params) => {
|
||||
objectToSpy.deleteAllInboundInvites.restore()
|
||||
const result = await objectToSpy.deleteAllInboundInvites(params)
|
||||
|
||||
@@ -26,13 +26,13 @@ export const createTrustedContactForUserOfContext = async (
|
||||
}
|
||||
|
||||
export const acceptAllInvites = async (context) => {
|
||||
const inviteRecords = context.sharedVaults.getCachedPendingInviteRecords()
|
||||
const inviteRecords = context.vaultInvites.getCachedPendingInviteRecords()
|
||||
if (inviteRecords.length === 0) {
|
||||
throw new Error('No pending invites to accept')
|
||||
}
|
||||
|
||||
for (const record of inviteRecords) {
|
||||
await context.sharedVaults.acceptPendingSharedVaultInvite(record)
|
||||
await context.vaultInvites.acceptInvite(record)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export const createSharedVaultWithUnacceptedButTrustedInvite = async (
|
||||
const contact = await createTrustedContactForUserOfContext(context, contactContext)
|
||||
await createTrustedContactForUserOfContext(contactContext, context)
|
||||
|
||||
const invite = (await context.sharedVaults.inviteContactToSharedVault(sharedVault, contact, permissions)).getValue()
|
||||
const invite = (await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, permissions)).getValue()
|
||||
await contactContext.sync()
|
||||
|
||||
return { sharedVault, contact, contactContext, deinitContactContext, invite }
|
||||
@@ -91,7 +91,7 @@ export const createSharedVaultWithUnacceptedAndUntrustedInvite = async (
|
||||
const { contactContext, deinitContactContext } = await createContactContext()
|
||||
const contact = await createTrustedContactForUserOfContext(context, contactContext)
|
||||
|
||||
const invite = (await context.sharedVaults.inviteContactToSharedVault(sharedVault, contact, permissions)).getValue()
|
||||
const invite = (await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, permissions)).getValue()
|
||||
await contactContext.sync()
|
||||
|
||||
return { sharedVault, contact, contactContext, deinitContactContext, invite }
|
||||
@@ -103,7 +103,7 @@ export const inviteNewPartyToSharedVault = async (context, sharedVault, permissi
|
||||
|
||||
const thirdPartyContact = await createTrustedContactForUserOfContext(context, thirdPartyContext)
|
||||
await createTrustedContactForUserOfContext(thirdPartyContext, context)
|
||||
await context.sharedVaults.inviteContactToSharedVault(sharedVault, thirdPartyContact, permissions)
|
||||
await context.vaultInvites.inviteContactToSharedVault(sharedVault, thirdPartyContact, permissions)
|
||||
|
||||
await thirdPartyContext.sync()
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('shared vault conflicts', function () {
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
contactContext.lockSyncing()
|
||||
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
const promise = contactContext.resolveWithConflicts()
|
||||
contactContext.unlockSyncing()
|
||||
await contactContext.changeNoteTitleAndSync(note, 'new title')
|
||||
@@ -98,7 +98,7 @@ describe('shared vault conflicts', function () {
|
||||
const { sharedVault, note, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context)
|
||||
|
||||
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
await contactContext.changeNoteTitleAndSync(note, 'new title')
|
||||
const notes = contactContext.notes
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ describe('shared vault deletion', function () {
|
||||
const contactNote = contactContext.items.findItem(note.uuid)
|
||||
expect(contactNote).to.not.be.undefined
|
||||
|
||||
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
|
||||
await contactContext.sync()
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('shared vault deletion', function () {
|
||||
expect(originalNote).to.not.be.undefined
|
||||
|
||||
const contactVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
|
||||
await contactContext.sharedVaults.leaveSharedVault(contactVault)
|
||||
await contactContext.vaultUsers.leaveSharedVault(contactVault)
|
||||
|
||||
const updatedContactNote = contactContext.items.findItem(note.uuid)
|
||||
expect(updatedContactNote).to.be.undefined
|
||||
@@ -140,14 +140,13 @@ describe('shared vault deletion', function () {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const originalSharedVaultUsers = await sharedVaults.getSharedVaultUsers(sharedVault)
|
||||
const originalSharedVaultUsers = await context.vaultUsers.getSharedVaultUsers(sharedVault)
|
||||
expect(originalSharedVaultUsers.length).to.equal(2)
|
||||
|
||||
const result = await sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
const result = await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
expect(result.isFailed()).to.be.false
|
||||
|
||||
expect(isClientDisplayableError(result)).to.be.false
|
||||
|
||||
const updatedSharedVaultUsers = await sharedVaults.getSharedVaultUsers(sharedVault)
|
||||
const updatedSharedVaultUsers = await context.vaultUsers.getSharedVaultUsers(sharedVault)
|
||||
expect(updatedSharedVaultUsers.length).to.equal(1)
|
||||
|
||||
await deinitContactContext()
|
||||
|
||||
@@ -249,7 +249,7 @@ describe('shared vault files', function () {
|
||||
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault)
|
||||
await contactContext.sync()
|
||||
|
||||
await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
|
||||
const file = contactContext.items.findItem(uploadedFile.uuid)
|
||||
await Factory.expectThrowsAsync(() => Files.downloadFile(contactContext.files, file), 'Could not download file')
|
||||
|
||||
@@ -8,7 +8,6 @@ describe('shared vault invites', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let sharedVaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
@@ -21,8 +20,6 @@ describe('shared vault invites', function () {
|
||||
context = await Factory.createAppContextWithRealCrypto()
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
sharedVaults = context.sharedVaults
|
||||
})
|
||||
|
||||
it('should invite contact to vault', async () => {
|
||||
@@ -31,7 +28,7 @@ describe('shared vault invites', function () {
|
||||
const contact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
|
||||
|
||||
const vaultInvite = (
|
||||
await sharedVaults.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write)
|
||||
await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write)
|
||||
).getValue()
|
||||
|
||||
expect(vaultInvite).to.not.be.undefined
|
||||
@@ -49,7 +46,7 @@ describe('shared vault invites', function () {
|
||||
const { contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context)
|
||||
|
||||
const invites = contactContext.sharedVaults.getCachedPendingInviteRecords()
|
||||
const invites = contactContext.vaultInvites.getCachedPendingInviteRecords()
|
||||
|
||||
expect(invites[0].trusted).to.be.true
|
||||
|
||||
@@ -60,7 +57,7 @@ describe('shared vault invites', function () {
|
||||
const { contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithUnacceptedAndUntrustedInvite(context)
|
||||
|
||||
const invites = contactContext.sharedVaults.getCachedPendingInviteRecords()
|
||||
const invites = contactContext.vaultInvites.getCachedPendingInviteRecords()
|
||||
|
||||
expect(invites[0].trusted).to.be.false
|
||||
|
||||
@@ -76,7 +73,7 @@ describe('shared vault invites', function () {
|
||||
sharedVault,
|
||||
)
|
||||
|
||||
const invites = thirdPartyContext.sharedVaults.getCachedPendingInviteRecords()
|
||||
const invites = thirdPartyContext.vaultInvites.getCachedPendingInviteRecords()
|
||||
|
||||
const message = invites[0].message
|
||||
const delegatedContacts = message.data.trustedContacts
|
||||
@@ -103,7 +100,7 @@ describe('shared vault invites', function () {
|
||||
/** Sync the contact context so that they wouldn't naturally receive changes made before this point */
|
||||
await contactContext.sync()
|
||||
|
||||
await sharedVaults.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write)
|
||||
await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write)
|
||||
|
||||
/** Contact should now sync and expect to find note */
|
||||
const promise = contactContext.awaitNextSyncSharedVaultFromScratchEvent()
|
||||
@@ -125,10 +122,14 @@ describe('shared vault invites', function () {
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
|
||||
await sharedVaults.inviteContactToSharedVault(sharedVault, currentContextContact, SharedVaultPermission.Write)
|
||||
await context.vaultInvites.inviteContactToSharedVault(
|
||||
sharedVault,
|
||||
currentContextContact,
|
||||
SharedVaultPermission.Write,
|
||||
)
|
||||
|
||||
await contactContext.sharedVaults.downloadInboundInvites()
|
||||
expect(contactContext.sharedVaults.getCachedPendingInviteRecords()[0].trusted).to.be.false
|
||||
await contactContext.vaultInvites.downloadInboundInvites()
|
||||
expect(contactContext.vaultInvites.getCachedPendingInviteRecords()[0].trusted).to.be.false
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
@@ -139,14 +140,18 @@ describe('shared vault invites', function () {
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
|
||||
await sharedVaults.inviteContactToSharedVault(sharedVault, currentContextContact, SharedVaultPermission.Write)
|
||||
await context.vaultInvites.inviteContactToSharedVault(
|
||||
sharedVault,
|
||||
currentContextContact,
|
||||
SharedVaultPermission.Write,
|
||||
)
|
||||
|
||||
await contactContext.sharedVaults.downloadInboundInvites()
|
||||
expect(contactContext.sharedVaults.getCachedPendingInviteRecords()[0].trusted).to.be.false
|
||||
await contactContext.vaultInvites.downloadInboundInvites()
|
||||
expect(contactContext.vaultInvites.getCachedPendingInviteRecords()[0].trusted).to.be.false
|
||||
|
||||
await Collaboration.createTrustedContactForUserOfContext(contactContext, context)
|
||||
|
||||
expect(contactContext.sharedVaults.getCachedPendingInviteRecords()[0].trusted).to.be.true
|
||||
expect(contactContext.vaultInvites.getCachedPendingInviteRecords()[0].trusted).to.be.true
|
||||
|
||||
await deinitContactContext()
|
||||
})
|
||||
@@ -184,12 +189,12 @@ describe('shared vault invites', function () {
|
||||
const { invite, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context)
|
||||
|
||||
const preInvites = await contactContext.sharedVaults.downloadInboundInvites()
|
||||
const preInvites = await contactContext.vaultInvites.downloadInboundInvites()
|
||||
expect(preInvites.length).to.equal(1)
|
||||
|
||||
await sharedVaults.deleteInvite(invite)
|
||||
await context.vaultInvites.deleteInvite(invite)
|
||||
|
||||
const postInvites = await contactContext.sharedVaults.downloadInboundInvites()
|
||||
const postInvites = await contactContext.vaultInvites.downloadInboundInvites()
|
||||
expect(postInvites.length).to.equal(0)
|
||||
|
||||
await deinitContactContext()
|
||||
@@ -208,7 +213,7 @@ describe('shared vault invites', function () {
|
||||
await contactContext.changePassword('new-password')
|
||||
await promise
|
||||
|
||||
const invites = await contactContext.sharedVaults.downloadInboundInvites()
|
||||
const invites = await contactContext.vaultInvites.downloadInboundInvites()
|
||||
expect(invites.length).to.equal(0)
|
||||
|
||||
await deinitContactContext()
|
||||
|
||||
@@ -8,7 +8,6 @@ describe('shared vault items', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let sharedVaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
@@ -22,8 +21,6 @@ describe('shared vault items', function () {
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
sharedVaults = context.sharedVaults
|
||||
})
|
||||
|
||||
it('should add item to shared vault with no other members', async () => {
|
||||
@@ -60,7 +57,11 @@ describe('shared vault items', function () {
|
||||
const currentContextContact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext)
|
||||
|
||||
contactContext.lockSyncing()
|
||||
await sharedVaults.inviteContactToSharedVault(sharedVault, currentContextContact, SharedVaultPermission.Write)
|
||||
await context.vaultInvites.inviteContactToSharedVault(
|
||||
sharedVault,
|
||||
currentContextContact,
|
||||
SharedVaultPermission.Write,
|
||||
)
|
||||
await Collaboration.moveItemToVault(context, sharedVault, note)
|
||||
|
||||
const promise = contactContext.awaitNextSyncSharedVaultFromScratchEvent()
|
||||
|
||||
@@ -8,8 +8,6 @@ describe('shared vault key rotation', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let vaults
|
||||
let sharedVaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
@@ -23,9 +21,6 @@ describe('shared vault key rotation', function () {
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
vaults = context.vaults
|
||||
sharedVaults = context.sharedVaults
|
||||
})
|
||||
|
||||
it('should reencrypt all items keys belonging to key system', async () => {
|
||||
@@ -37,7 +32,7 @@ describe('shared vault key rotation', function () {
|
||||
const spy = sinon.spy(context.keys, 'reencryptKeySystemItemsKeysForVault')
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
expect(spy.callCount).to.equal(1)
|
||||
@@ -52,7 +47,7 @@ describe('shared vault key rotation', function () {
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
const outboundMessages = await context.asymmetric.getOutboundMessages()
|
||||
@@ -79,7 +74,7 @@ describe('shared vault key rotation', function () {
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
const rootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)
|
||||
@@ -107,7 +102,7 @@ describe('shared vault key rotation', function () {
|
||||
expect(previousPrimaryItemsKey).to.not.be.undefined
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
const contactPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes()
|
||||
@@ -129,15 +124,15 @@ describe('shared vault key rotation', function () {
|
||||
await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context)
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const originalOutboundInvites = await sharedVaults.getOutboundInvites()
|
||||
const originalOutboundInvites = await context.vaultInvites.getOutboundInvites()
|
||||
expect(originalOutboundInvites.length).to.equal(1)
|
||||
const originalInviteMessage = originalOutboundInvites[0].encrypted_message
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
const updatedOutboundInvites = await sharedVaults.getOutboundInvites()
|
||||
const updatedOutboundInvites = await context.vaultInvites.getOutboundInvites()
|
||||
expect(updatedOutboundInvites.length).to.equal(1)
|
||||
|
||||
const joinInvite = updatedOutboundInvites[0]
|
||||
@@ -150,7 +145,7 @@ describe('shared vault key rotation', function () {
|
||||
it('new key system items key in rotated shared vault should belong to shared vault', async () => {
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
|
||||
const keySystemItemsKeys = context.keys
|
||||
.getAllKeySystemItemsKeys()
|
||||
@@ -170,7 +165,7 @@ describe('shared vault key rotation', function () {
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const firstPromise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
await firstPromise
|
||||
|
||||
const asymmetricMessageAfterFirstChange = await context.asymmetric.getOutboundMessages()
|
||||
@@ -180,7 +175,7 @@ describe('shared vault key rotation', function () {
|
||||
const messageAfterFirstChange = asymmetricMessageAfterFirstChange[0]
|
||||
|
||||
const secondPromise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
await secondPromise
|
||||
|
||||
const asymmetricMessageAfterSecondChange = await context.asymmetric.getOutboundMessages()
|
||||
@@ -204,7 +199,7 @@ describe('shared vault key rotation', function () {
|
||||
contactContext.lockSyncing()
|
||||
|
||||
const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault)
|
||||
await vaults.rotateVaultRootKey(sharedVault)
|
||||
await context.vaults.rotateVaultRootKey(sharedVault)
|
||||
await promise
|
||||
|
||||
const acceptMessage = sinon.spy(contactContext.asymmetric, 'handleTrustedSharedVaultRootKeyChangedMessage')
|
||||
@@ -223,7 +218,7 @@ describe('shared vault key rotation', function () {
|
||||
|
||||
const originalKeySystemRootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)
|
||||
|
||||
await sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
|
||||
const newKeySystemRootKey = context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ describe('shared vault permissions', function () {
|
||||
this.timeout(Factory.TwentySecondTimeout)
|
||||
|
||||
let context
|
||||
let sharedVaults
|
||||
|
||||
afterEach(async function () {
|
||||
await context.deinit()
|
||||
@@ -22,8 +21,6 @@ describe('shared vault permissions', function () {
|
||||
|
||||
await context.launch()
|
||||
await context.register()
|
||||
|
||||
sharedVaults = context.sharedVaults
|
||||
})
|
||||
|
||||
it('non-admin user should not be able to invite user', async () => {
|
||||
@@ -37,7 +34,7 @@ describe('shared vault permissions', function () {
|
||||
contactContext,
|
||||
thirdParty.contactContext,
|
||||
)
|
||||
const result = await contactContext.sharedVaults.inviteContactToSharedVault(
|
||||
const result = await contactContext.vaultInvites.inviteContactToSharedVault(
|
||||
sharedVault,
|
||||
thirdPartyContact,
|
||||
SharedVaultPermission.Write,
|
||||
@@ -53,16 +50,15 @@ describe('shared vault permissions', function () {
|
||||
|
||||
const sharedVault = await Collaboration.createSharedVault(context)
|
||||
|
||||
const result = await sharedVaults.removeUserFromSharedVault(sharedVault, context.userUuid)
|
||||
|
||||
expect(isClientDisplayableError(result)).to.be.true
|
||||
const result = await context.vaultUsers.removeUserFromSharedVault(sharedVault, context.userUuid)
|
||||
expect(result.isFailed()).to.be.true
|
||||
})
|
||||
|
||||
it('should be able to leave shared vault as added admin', async () => {
|
||||
const { contactVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context, SharedVaultPermission.Admin)
|
||||
|
||||
const result = await contactContext.sharedVaults.leaveSharedVault(contactVault)
|
||||
const result = await contactContext.vaultUsers.leaveSharedVault(contactVault)
|
||||
|
||||
expect(isClientDisplayableError(result)).to.be.false
|
||||
|
||||
|
||||
@@ -54,9 +54,8 @@ describe('shared vaults', function () {
|
||||
const { sharedVault, contactContext, deinitContactContext } =
|
||||
await Collaboration.createSharedVaultWithAcceptedInvite(context)
|
||||
|
||||
const result = await context.sharedVaults.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
|
||||
expect(result).to.be.undefined
|
||||
const result = await context.vaultUsers.removeUserFromSharedVault(sharedVault, contactContext.userUuid)
|
||||
expect(result.isFailed()).to.be.false
|
||||
|
||||
const promise = contactContext.resolveWhenUserMessagesProcessingCompletes()
|
||||
await contactContext.sync()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/snjs",
|
||||
"version": "2.202.10",
|
||||
"version": "2.202.11",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.28.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
## [1.28.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/ui-services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/ui-services",
|
||||
"version": "1.28.8",
|
||||
"version": "1.28.9",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.167.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.167.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.167.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.167.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
## [3.167.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/web
|
||||
|
||||
@@ -1,5 +1,49 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "3.167.8",
|
||||
"title": "[3.167.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.167.7",
|
||||
"title": "[3.167.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.167.6",
|
||||
"title": "[3.167.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.167.5",
|
||||
"title": "[3.167.5](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)",
|
||||
"date": null,
|
||||
"body": "**Note:** Version bump only for package @standardnotes/web",
|
||||
"parsed": {
|
||||
"_": [
|
||||
"Note: Version bump only for package @standardnotes/web"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "3.167.4",
|
||||
"title": "[3.167.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/web",
|
||||
"version": "3.167.4",
|
||||
"version": "3.167.8",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/app.js",
|
||||
"author": "Standard Notes.",
|
||||
@@ -15,7 +15,7 @@
|
||||
"lint": "eslint src/javascripts && yarn tsc",
|
||||
"lint:fix": "eslint src/javascripts --fix",
|
||||
"start": "webpack-dev-server --config web.webpack.dev.js",
|
||||
"start-secure": "yarn start --server-type https",
|
||||
"start-secure": "yarn start --https",
|
||||
"test": "jest --config jest.config.js",
|
||||
"tsc": "tsc --project tsconfig.json",
|
||||
"upgrade:snjs": "ncu -u '@standardnotes/*'",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@babel/plugin-transform-react-jsx": "^7.19.0",
|
||||
"@babel/preset-env": "*",
|
||||
"@babel/preset-typescript": "^7.21.5",
|
||||
"@lexical/react": "0.11.2",
|
||||
"@lexical/react": "0.11.3",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||
"@simplewebauthn/browser": "^7.1.0",
|
||||
"@standardnotes/authenticator": "^2.4.0",
|
||||
@@ -76,7 +76,7 @@
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^29.3.1",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"lexical": "0.11.2",
|
||||
"lexical": "0.11.3",
|
||||
"lint-staged": ">=13",
|
||||
"mini-css-extract-plugin": "^2.7.2",
|
||||
"minimatch": "^5.1.1",
|
||||
@@ -109,7 +109,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0.2.8",
|
||||
"@lexical/headless": "0.11.2",
|
||||
"@lexical/headless": "0.11.3",
|
||||
"@radix-ui/react-slot": "^1.0.1",
|
||||
"fast-diff": "^1.3.0"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ const IosKeyboardClose = () => {
|
||||
const application = useApplication()
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const [isFocusInSuperEditor, setIsFocusInSuperEditor] = useState(
|
||||
() => !!document.activeElement?.closest('#blocks-editor'),
|
||||
() => !!document.activeElement?.closest('#blocks-editor,#super-mobile-toolbar'),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -22,7 +22,7 @@ const IosKeyboardClose = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const handleFocusChange = () => {
|
||||
setIsFocusInSuperEditor(!!document.activeElement?.closest('#blocks-editor'))
|
||||
setIsFocusInSuperEditor(!!document.activeElement?.closest('#blocks-editor,#super-mobile-toolbar'))
|
||||
}
|
||||
|
||||
document.addEventListener('focusin', handleFocusChange)
|
||||
|
||||
@@ -32,7 +32,7 @@ const LinkedItemBubblesContainer = ({
|
||||
hideToggle = false,
|
||||
readonly = false,
|
||||
className = {},
|
||||
isCollapsedByDefault = false,
|
||||
isCollapsedByDefault = true,
|
||||
}: Props) => {
|
||||
const { toggleAppPane } = useResponsiveAppPane()
|
||||
|
||||
@@ -119,10 +119,13 @@ const LinkedItemBubblesContainer = ({
|
||||
)
|
||||
}
|
||||
|
||||
const [isCollapsed, setIsCollapsed] = useState(() => isCollapsedByDefault)
|
||||
|
||||
const itemsToDisplay = allItemsLinkedToItem.concat(notesLinkingToItem).concat(filesLinkingToItem)
|
||||
const visibleItems = isCollapsed ? itemsToDisplay.slice(0, 5) : itemsToDisplay
|
||||
const ItemsToShowWhenCollapsed = 5
|
||||
const [isCollapsed, setIsCollapsed] = useState(
|
||||
itemsToDisplay.length < ItemsToShowWhenCollapsed ? false : isCollapsedByDefault,
|
||||
)
|
||||
|
||||
const visibleItems = isCollapsed ? itemsToDisplay.slice(0, ItemsToShowWhenCollapsed) : itemsToDisplay
|
||||
const nonVisibleItems = itemsToDisplay.length - visibleItems.length
|
||||
|
||||
const [canShowContainerToggle, setCanShowContainerToggle] = useState(true)
|
||||
|
||||
@@ -61,14 +61,14 @@ export const PreprocessingStep = ({
|
||||
|
||||
useEffect(() => {
|
||||
const processPendingInvites = async () => {
|
||||
await application.sharedVaults.downloadInboundInvites()
|
||||
const hasPendingInvites = application.sharedVaults.getCachedPendingInviteRecords().length > 0
|
||||
await application.vaultInvites.downloadInboundInvites()
|
||||
const hasPendingInvites = application.vaultInvites.getCachedPendingInviteRecords().length > 0
|
||||
setNeedsUserConfirmation(hasPendingInvites ? 'yes' : 'no')
|
||||
setIsProcessingInvites(false)
|
||||
}
|
||||
|
||||
void processPendingInvites()
|
||||
}, [application.sharedVaults])
|
||||
}, [application])
|
||||
|
||||
const isProcessing = isProcessingSync || isProcessingMessages || isProcessingInvites
|
||||
|
||||
|
||||
+2
-2
@@ -2,10 +2,10 @@ import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 're
|
||||
import Modal, { ModalAction } from '@/Components/Modal/Modal'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import { useApplication } from '@/Components/ApplicationProvider'
|
||||
import { PendingSharedVaultInviteRecord, TrustedContactInterface } from '@standardnotes/snjs'
|
||||
import { InviteRecord, TrustedContactInterface } from '@standardnotes/snjs'
|
||||
|
||||
type Props = {
|
||||
fromInvite?: PendingSharedVaultInviteRecord
|
||||
fromInvite?: InviteRecord
|
||||
editContactUuid?: string
|
||||
onCloseDialog: () => void
|
||||
onAddContact?: (contact: TrustedContactInterface) => void
|
||||
|
||||
+4
-4
@@ -16,11 +16,11 @@ const ContactInviteModal: FunctionComponent<Props> = ({ vault, onCloseDialog })
|
||||
|
||||
useEffect(() => {
|
||||
const loadContacts = async () => {
|
||||
const contacts = await application.sharedVaults.getInvitableContactsForSharedVault(vault)
|
||||
const contacts = await application.vaultInvites.getInvitableContactsForSharedVault(vault)
|
||||
setContacts(contacts)
|
||||
}
|
||||
void loadContacts()
|
||||
}, [application.sharedVaults, vault])
|
||||
}, [application.vaultInvites, vault])
|
||||
|
||||
const handleDialogClose = useCallback(() => {
|
||||
onCloseDialog()
|
||||
@@ -28,10 +28,10 @@ const ContactInviteModal: FunctionComponent<Props> = ({ vault, onCloseDialog })
|
||||
|
||||
const inviteSelectedContacts = useCallback(async () => {
|
||||
for (const contact of selectedContacts) {
|
||||
await application.sharedVaults.inviteContactToSharedVault(vault, contact, SharedVaultPermission.Write)
|
||||
await application.vaultInvites.inviteContactToSharedVault(vault, contact, SharedVaultPermission.Write)
|
||||
}
|
||||
handleDialogClose()
|
||||
}, [application.sharedVaults, vault, handleDialogClose, selectedContacts])
|
||||
}, [application.vaultInvites, vault, handleDialogClose, selectedContacts])
|
||||
|
||||
const toggleContact = useCallback(
|
||||
(contact: TrustedContactInterface) => {
|
||||
|
||||
+4
-4
@@ -2,13 +2,13 @@ import { useApplication } from '@/Components/ApplicationProvider'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import ModalOverlay from '@/Components/Modal/ModalOverlay'
|
||||
import { PendingSharedVaultInviteRecord } from '@standardnotes/snjs'
|
||||
import { InviteRecord } from '@standardnotes/snjs'
|
||||
import { useCallback, useState } from 'react'
|
||||
import EditContactModal from '../Contacts/EditContactModal'
|
||||
import { CheckmarkCircle } from '../../../../UIElements/CheckmarkCircle'
|
||||
|
||||
type Props = {
|
||||
inviteRecord: PendingSharedVaultInviteRecord
|
||||
inviteRecord: InviteRecord
|
||||
}
|
||||
|
||||
const InviteItem = ({ inviteRecord }: Props) => {
|
||||
@@ -23,8 +23,8 @@ const InviteItem = ({ inviteRecord }: Props) => {
|
||||
}, [])
|
||||
|
||||
const acceptInvite = useCallback(async () => {
|
||||
await application.sharedVaults.acceptPendingSharedVaultInvite(inviteRecord)
|
||||
}, [application.sharedVaults, inviteRecord])
|
||||
await application.vaultInvites.acceptInvite(inviteRecord)
|
||||
}, [application, inviteRecord])
|
||||
|
||||
const closeAddContactModal = () => setIsAddContactModalOpen(false)
|
||||
const collaborationId = application.contacts.getCollaborationIDFromInvite(inviteRecord.invite)
|
||||
|
||||
@@ -10,9 +10,10 @@ import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
VaultListingInterface,
|
||||
TrustedContactInterface,
|
||||
PendingSharedVaultInviteRecord,
|
||||
InviteRecord,
|
||||
ContentType,
|
||||
SharedVaultServiceEvent,
|
||||
VaultUserServiceEvent,
|
||||
} from '@standardnotes/snjs'
|
||||
import VaultItem from './Vaults/VaultItem'
|
||||
import Button from '@/Components/Button/Button'
|
||||
@@ -23,7 +24,7 @@ const Vaults = () => {
|
||||
const application = useApplication()
|
||||
|
||||
const [vaults, setVaults] = useState<VaultListingInterface[]>([])
|
||||
const [invites, setInvites] = useState<PendingSharedVaultInviteRecord[]>([])
|
||||
const [invites, setInvites] = useState<InviteRecord[]>([])
|
||||
const [contacts, setContacts] = useState<TrustedContactInterface[]>([])
|
||||
|
||||
const [isAddContactModalOpen, setIsAddContactModalOpen] = useState(false)
|
||||
@@ -33,7 +34,6 @@ const Vaults = () => {
|
||||
const closeVaultModal = () => setIsVaultModalOpen(false)
|
||||
|
||||
const vaultService = application.vaults
|
||||
const sharedVaultService = application.sharedVaults
|
||||
const contactService = application.contacts
|
||||
|
||||
const updateVaults = useCallback(async () => {
|
||||
@@ -41,8 +41,8 @@ const Vaults = () => {
|
||||
}, [vaultService])
|
||||
|
||||
const updateInvites = useCallback(async () => {
|
||||
setInvites(sharedVaultService.getCachedPendingInviteRecords())
|
||||
}, [sharedVaultService])
|
||||
setInvites(application.vaultInvites.getCachedPendingInviteRecords())
|
||||
}, [application.vaultInvites])
|
||||
|
||||
const updateContacts = useCallback(async () => {
|
||||
setContacts(contactService.getAllContacts())
|
||||
@@ -60,6 +60,20 @@ const Vaults = () => {
|
||||
})
|
||||
}, [application.sharedVaults, updateAllData])
|
||||
|
||||
useEffect(() => {
|
||||
return application.vaultUsers.addEventObserver((event) => {
|
||||
if (event === VaultUserServiceEvent.UsersChanged) {
|
||||
void updateAllData()
|
||||
}
|
||||
})
|
||||
}, [application.vaultUsers, updateAllData])
|
||||
|
||||
useEffect(() => {
|
||||
return application.vaultInvites.addEventObserver(() => {
|
||||
void updateAllData()
|
||||
})
|
||||
}, [application.vaultInvites, updateAllData])
|
||||
|
||||
useEffect(() => {
|
||||
return application.streamItems([ContentType.TYPES.VaultListing, ContentType.TYPES.TrustedContact], () => {
|
||||
void updateAllData()
|
||||
@@ -67,9 +81,9 @@ const Vaults = () => {
|
||||
}, [application, updateAllData])
|
||||
|
||||
useEffect(() => {
|
||||
void sharedVaultService.downloadInboundInvites()
|
||||
void application.vaultInvites.downloadInboundInvites()
|
||||
void updateAllData()
|
||||
}, [updateAllData, sharedVaultService])
|
||||
}, [updateAllData, application.vaultInvites])
|
||||
|
||||
const createNewVault = useCallback(async () => {
|
||||
setIsVaultModalOpen(true)
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ const VaultItem = ({ vault }: Props) => {
|
||||
const [isVaultModalOpen, setIsVaultModalOpen] = useState(false)
|
||||
const closeVaultModal = () => setIsVaultModalOpen(false)
|
||||
|
||||
const isAdmin = !vault.isSharedVaultListing() ? true : application.sharedVaults.isCurrentUserSharedVaultAdmin(vault)
|
||||
const isAdmin = !vault.isSharedVaultListing() ? true : application.vaultUsers.isCurrentUserSharedVaultAdmin(vault)
|
||||
|
||||
const deleteVault = useCallback(async () => {
|
||||
const confirm = await application.alerts.confirm(
|
||||
@@ -61,11 +61,11 @@ const VaultItem = ({ vault }: Props) => {
|
||||
return
|
||||
}
|
||||
|
||||
const success = await application.sharedVaults.leaveSharedVault(vault)
|
||||
const success = await application.vaultUsers.leaveSharedVault(vault)
|
||||
if (!success) {
|
||||
void application.alerts.alert('Unable to leave vault. Please try again.')
|
||||
}
|
||||
}, [application.alerts, application.sharedVaults, vault])
|
||||
}, [application, vault])
|
||||
|
||||
const convertToSharedVault = useCallback(async () => {
|
||||
await application.sharedVaults.convertVaultToSharedVault(vault)
|
||||
|
||||
+4
-4
@@ -54,20 +54,20 @@ const EditVaultModal: FunctionComponent<Props> = ({ onCloseDialog, existingVault
|
||||
|
||||
if (existingVault.isSharedVaultListing()) {
|
||||
setIsAdmin(
|
||||
existingVault.isSharedVaultListing() && application.sharedVaults.isCurrentUserSharedVaultAdmin(existingVault),
|
||||
existingVault.isSharedVaultListing() && application.vaultUsers.isCurrentUserSharedVaultAdmin(existingVault),
|
||||
)
|
||||
|
||||
const users = await application.sharedVaults.getSharedVaultUsers(existingVault)
|
||||
const users = await application.vaultUsers.getSharedVaultUsers(existingVault)
|
||||
if (users) {
|
||||
setMembers(users)
|
||||
}
|
||||
|
||||
const invites = await application.sharedVaults.getOutboundInvites(existingVault)
|
||||
const invites = await application.vaultInvites.getOutboundInvites(existingVault)
|
||||
if (!isClientDisplayableError(invites)) {
|
||||
setInvites(invites)
|
||||
}
|
||||
}
|
||||
}, [application.sharedVaults, existingVault])
|
||||
}, [application, existingVault])
|
||||
|
||||
useEffect(() => {
|
||||
void reloadVaultInfo()
|
||||
|
||||
+2
-2
@@ -17,10 +17,10 @@ export const VaultModalInvites = ({
|
||||
|
||||
const deleteInvite = useCallback(
|
||||
async (invite: SharedVaultInviteServerHash) => {
|
||||
await application.sharedVaults.deleteInvite(invite)
|
||||
await application.vaultInvites.deleteInvite(invite)
|
||||
onChange()
|
||||
},
|
||||
[application.sharedVaults, onChange],
|
||||
[application.vaultInvites, onChange],
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
+3
-3
@@ -20,18 +20,18 @@ export const VaultModalMembers = ({
|
||||
const removeMemberFromVault = useCallback(
|
||||
async (memberItem: SharedVaultUserServerHash) => {
|
||||
if (vault.isSharedVaultListing()) {
|
||||
await application.sharedVaults.removeUserFromSharedVault(vault, memberItem.user_uuid)
|
||||
await application.vaultUsers.removeUserFromSharedVault(vault, memberItem.user_uuid)
|
||||
onChange()
|
||||
}
|
||||
},
|
||||
[application.sharedVaults, vault, onChange],
|
||||
[application.vaultUsers, vault, onChange],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="mb-3">
|
||||
<div className="mb-3 text-lg">Vault Members</div>
|
||||
{members.map((member) => {
|
||||
if (application.sharedVaults.isSharedVaultUserSharedVaultOwner(member)) {
|
||||
if (application.vaultUsers.isVaultUserOwner(member)) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import CollapsiblePlugin from './Plugins/CollapsiblePlugin'
|
||||
import DraggableBlockPlugin from './Plugins/DraggableBlockPlugin'
|
||||
import CodeHighlightPlugin from './Plugins/CodeHighlightPlugin'
|
||||
import FloatingTextFormatToolbarPlugin from './Plugins/FloatingTextFormatToolbarPlugin'
|
||||
import FloatingLinkEditorPlugin from './Plugins/FloatingLinkEditorPlugin'
|
||||
import { TabIndentationPlugin } from './Plugins/TabIndentationPlugin'
|
||||
import { handleEditorChange } from './Utils'
|
||||
import { SuperEditorContentId } from './Constants'
|
||||
@@ -109,7 +108,6 @@ export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
|
||||
{!readonly && floatingAnchorElem && (
|
||||
<>
|
||||
<FloatingTextFormatToolbarPlugin anchorElem={floatingAnchorElem} />
|
||||
<FloatingLinkEditorPlugin anchorElem={floatingAnchorElem} />
|
||||
<DraggableBlockPlugin anchorElem={floatingAnchorElem} />
|
||||
</>
|
||||
)}
|
||||
|
||||
+8
-2
@@ -4,7 +4,7 @@ import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { IconComponent } from '../../Lexical/Theme/IconComponent'
|
||||
import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl'
|
||||
import { TOGGLE_LINK_COMMAND } from '@lexical/link'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback, useState, useRef } from 'react'
|
||||
import { GridSelection, LexicalEditor, NodeSelection, RangeSelection } from 'lexical'
|
||||
import { classNames } from '@standardnotes/snjs'
|
||||
|
||||
@@ -19,6 +19,7 @@ type Props = {
|
||||
|
||||
const LinkEditor = ({ linkUrl, isEditMode, setEditMode, editor, lastSelection, isAutoLink }: Props) => {
|
||||
const [editedLinkUrl, setEditedLinkUrl] = useState('')
|
||||
const editModeContainer = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleLinkSubmission = () => {
|
||||
if (lastSelection !== null) {
|
||||
@@ -36,7 +37,7 @@ const LinkEditor = ({ linkUrl, isEditMode, setEditMode, editor, lastSelection, i
|
||||
}, [])
|
||||
|
||||
return isEditMode ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2" ref={editModeContainer}>
|
||||
<input
|
||||
id="link-input"
|
||||
ref={focusInput}
|
||||
@@ -53,6 +54,11 @@ const LinkEditor = ({ linkUrl, isEditMode, setEditMode, editor, lastSelection, i
|
||||
setEditMode(false)
|
||||
}
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
if (!editModeContainer.current?.contains(event.relatedTarget as Node)) {
|
||||
setEditMode(false)
|
||||
}
|
||||
}}
|
||||
className="flex-grow rounded-sm bg-contrast p-1 text-text sm:min-w-[40ch]"
|
||||
/>
|
||||
<button
|
||||
|
||||
+146
-38
@@ -11,11 +11,15 @@ import {
|
||||
CAN_UNDO_COMMAND,
|
||||
COMMAND_PRIORITY_CRITICAL,
|
||||
FORMAT_TEXT_COMMAND,
|
||||
GridSelection,
|
||||
NodeSelection,
|
||||
REDO_COMMAND,
|
||||
RangeSelection,
|
||||
SELECTION_CHANGE_COMMAND,
|
||||
UNDO_COMMAND,
|
||||
} from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
||||
import { $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { GetAlignmentBlocks } from '../Blocks/Alignment'
|
||||
import { GetBulletedListBlock } from '../Blocks/BulletedList'
|
||||
@@ -38,6 +42,7 @@ import { SUPER_TOGGLE_SEARCH } from '@standardnotes/ui-services'
|
||||
import { useApplication } from '@/Components/ApplicationProvider'
|
||||
import { GetRemoteImageBlock } from '../Blocks/RemoteImage'
|
||||
import { InsertRemoteImageDialog } from '../RemoteImagePlugin/RemoteImagePlugin'
|
||||
import LinkEditor from '../FloatingLinkEditorPlugin/LinkEditor'
|
||||
|
||||
const MobileToolbarPlugin = () => {
|
||||
const application = useApplication()
|
||||
@@ -45,10 +50,12 @@ const MobileToolbarPlugin = () => {
|
||||
const [modal, showModal] = useModal()
|
||||
|
||||
const [isInEditor, setIsInEditor] = useState(false)
|
||||
const [isInLinkEditor, setIsInLinkEditor] = useState(false)
|
||||
const [isInToolbar, setIsInToolbar] = useState(false)
|
||||
const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
|
||||
const toolbarRef = useRef<HTMLDivElement>(null)
|
||||
const linkEditorRef = useRef<HTMLDivElement>(null)
|
||||
const backspaceButtonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const insertLink = useCallback(() => {
|
||||
@@ -214,8 +221,10 @@ const MobileToolbarPlugin = () => {
|
||||
const handleBlur = (event: FocusEvent) => {
|
||||
const elementToBeFocused = event.relatedTarget as Node
|
||||
const toolbarContainsElementToFocus = toolbarRef.current && toolbarRef.current.contains(elementToBeFocused)
|
||||
const linkEditorContainsElementToFocus =
|
||||
linkEditorRef.current && linkEditorRef.current.contains(elementToBeFocused)
|
||||
const willFocusBackspaceButton = backspaceButtonRef.current && elementToBeFocused === backspaceButtonRef.current
|
||||
if (toolbarContainsElementToFocus || willFocusBackspaceButton) {
|
||||
if (toolbarContainsElementToFocus || linkEditorContainsElementToFocus || willFocusBackspaceButton) {
|
||||
return
|
||||
}
|
||||
setIsInEditor(false)
|
||||
@@ -231,31 +240,116 @@ const MobileToolbarPlugin = () => {
|
||||
}, [editor])
|
||||
|
||||
useEffect(() => {
|
||||
if (!toolbarRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
const toolbar = toolbarRef.current
|
||||
const linkEditor = linkEditorRef.current
|
||||
|
||||
const handleFocus = () => setIsInToolbar(true)
|
||||
const handleBlur = (event: FocusEvent) => {
|
||||
const handleToolbarFocus = () => setIsInToolbar(true)
|
||||
const handleLinkEditorFocus = () => setIsInLinkEditor(true)
|
||||
const handleToolbarBlur = (event: FocusEvent) => {
|
||||
const elementToBeFocused = event.relatedTarget as Node
|
||||
if (elementToBeFocused === backspaceButtonRef.current) {
|
||||
return
|
||||
}
|
||||
setIsInToolbar(false)
|
||||
}
|
||||
const handleLinkEditorBlur = (event: FocusEvent) => {
|
||||
const elementToBeFocused = event.relatedTarget as Node
|
||||
if (elementToBeFocused === backspaceButtonRef.current) {
|
||||
return
|
||||
}
|
||||
setIsInLinkEditor(false)
|
||||
}
|
||||
|
||||
toolbar.addEventListener('focus', handleFocus)
|
||||
toolbar.addEventListener('blur', handleBlur)
|
||||
if (toolbar) {
|
||||
toolbar.addEventListener('focus', handleToolbarFocus)
|
||||
toolbar.addEventListener('blur', handleToolbarBlur)
|
||||
}
|
||||
|
||||
if (linkEditor) {
|
||||
linkEditor.addEventListener('focus', handleLinkEditorFocus)
|
||||
linkEditor.addEventListener('blur', handleLinkEditorBlur)
|
||||
}
|
||||
|
||||
return () => {
|
||||
toolbar?.removeEventListener('focus', handleFocus)
|
||||
toolbar?.removeEventListener('blur', handleBlur)
|
||||
toolbar?.removeEventListener('focus', handleToolbarFocus)
|
||||
toolbar?.removeEventListener('blur', handleToolbarBlur)
|
||||
linkEditor?.removeEventListener('focus', handleLinkEditorFocus)
|
||||
linkEditor?.removeEventListener('blur', handleLinkEditorBlur)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const isFocusInEditorOrToolbar = isInEditor || isInToolbar
|
||||
const [isSelectionLink, setIsSelectionLink] = useState(false)
|
||||
const [isSelectionAutoLink, setIsSelectionAutoLink] = useState(false)
|
||||
const [linkUrl, setLinkUrl] = useState('')
|
||||
const [isLinkEditMode, setIsLinkEditMode] = useState(false)
|
||||
const [lastSelection, setLastSelection] = useState<RangeSelection | GridSelection | NodeSelection | null>(null)
|
||||
|
||||
const updateEditorSelection = useCallback(() => {
|
||||
editor.getEditorState().read(() => {
|
||||
const selection = $getSelection()
|
||||
const nativeSelection = window.getSelection()
|
||||
const activeElement = document.activeElement
|
||||
const rootElement = editor.getRootElement()
|
||||
|
||||
if (!$isRangeSelection(selection)) {
|
||||
return
|
||||
}
|
||||
|
||||
const node = getSelectedNode(selection)
|
||||
const parent = node.getParent()
|
||||
|
||||
if ($isLinkNode(parent) || $isLinkNode(node)) {
|
||||
setIsSelectionLink(true)
|
||||
} else {
|
||||
setIsSelectionLink(false)
|
||||
}
|
||||
|
||||
if ($isAutoLinkNode(parent) || $isAutoLinkNode(node)) {
|
||||
setIsSelectionAutoLink(true)
|
||||
} else {
|
||||
setIsSelectionAutoLink(false)
|
||||
}
|
||||
|
||||
if ($isLinkNode(parent)) {
|
||||
setLinkUrl(parent.getURL())
|
||||
} else if ($isLinkNode(node)) {
|
||||
setLinkUrl(node.getURL())
|
||||
} else {
|
||||
setLinkUrl('')
|
||||
}
|
||||
|
||||
if (
|
||||
selection !== null &&
|
||||
nativeSelection !== null &&
|
||||
rootElement !== null &&
|
||||
rootElement.contains(nativeSelection.anchorNode)
|
||||
) {
|
||||
setLastSelection(selection)
|
||||
} else if (!activeElement || activeElement.id !== 'link-input') {
|
||||
setLastSelection(null)
|
||||
setIsLinkEditMode(false)
|
||||
setLinkUrl('')
|
||||
}
|
||||
})
|
||||
}, [editor])
|
||||
|
||||
useEffect(() => {
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
SELECTION_CHANGE_COMMAND,
|
||||
() => {
|
||||
updateEditorSelection()
|
||||
return false
|
||||
},
|
||||
COMMAND_PRIORITY_CRITICAL,
|
||||
),
|
||||
editor.registerUpdateListener(() => {
|
||||
updateEditorSelection()
|
||||
}),
|
||||
)
|
||||
}, [editor, updateEditorSelection])
|
||||
|
||||
const isFocusInEditorOrToolbar = isInEditor || isInToolbar || isInLinkEditor
|
||||
if (!isMobile || !isFocusInEditorOrToolbar) {
|
||||
return null
|
||||
}
|
||||
@@ -263,32 +357,46 @@ const MobileToolbarPlugin = () => {
|
||||
return (
|
||||
<>
|
||||
{modal}
|
||||
<div className="flex w-full flex-shrink-0 border-t border-border bg-contrast">
|
||||
<div
|
||||
tabIndex={-1}
|
||||
className={classNames('flex items-center gap-1 overflow-x-auto', '[&::-webkit-scrollbar]:h-0')}
|
||||
ref={toolbarRef}
|
||||
>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<button
|
||||
className="flex items-center justify-center rounded px-3 py-3 disabled:opacity-50"
|
||||
aria-label={item.name}
|
||||
onClick={item.onSelect}
|
||||
key={item.name}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
<Icon type={item.iconName} size="medium" className="!text-current [&>path]:!text-current" />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
<div className="bg-contrast" id="super-mobile-toolbar">
|
||||
{isSelectionLink && (
|
||||
<div className="border-t border-border px-2" ref={linkEditorRef}>
|
||||
<LinkEditor
|
||||
linkUrl={linkUrl}
|
||||
isEditMode={isLinkEditMode}
|
||||
setEditMode={setIsLinkEditMode}
|
||||
isAutoLink={isSelectionAutoLink}
|
||||
editor={editor}
|
||||
lastSelection={lastSelection}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex w-full flex-shrink-0 border-t border-border bg-contrast">
|
||||
<div
|
||||
tabIndex={-1}
|
||||
className={classNames('flex items-center gap-1 overflow-x-auto', '[&::-webkit-scrollbar]:h-0')}
|
||||
ref={toolbarRef}
|
||||
>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<button
|
||||
className="flex items-center justify-center rounded px-3 py-3 disabled:opacity-50"
|
||||
aria-label={item.name}
|
||||
onClick={item.onSelect}
|
||||
key={item.name}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
<Icon type={item.iconName} size="medium" className="!text-current [&>path]:!text-current" />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<button
|
||||
className="flex flex-shrink-0 items-center justify-center rounded border-l border-border px-3 py-3"
|
||||
aria-label="Dismiss keyboard"
|
||||
>
|
||||
<Icon type="keyboard-close" size="medium" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="flex flex-shrink-0 items-center justify-center rounded border-l border-border px-3 py-3"
|
||||
aria-label="Dismiss keyboard"
|
||||
>
|
||||
<Icon type="keyboard-close" size="medium" />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -2911,255 +2911,255 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/clipboard@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/clipboard@npm:0.11.2"
|
||||
"@lexical/clipboard@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/clipboard@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/html": 0.11.2
|
||||
"@lexical/list": 0.11.2
|
||||
"@lexical/selection": 0.11.2
|
||||
"@lexical/utils": 0.11.2
|
||||
"@lexical/html": 0.11.3
|
||||
"@lexical/list": 0.11.3
|
||||
"@lexical/selection": 0.11.3
|
||||
"@lexical/utils": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: ab102ac6bf803fe61e48b9ef19576af1db9fb39e0eb8af0501882337e69f41324243b8f95094f958f8181c1ef27816cba1c1eaaa71eb2d09e9b323ea0b6ee7f6
|
||||
lexical: 0.11.3
|
||||
checksum: 0a630b630197a8f7f8118049993c1aecee603272733c0c0e2a40e699f79efc11f93e8e1b65e8835a5ebbf22aa6af559dfe60f3d4b214cbea323d3902ed69a923
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/code@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/code@npm:0.11.2"
|
||||
"@lexical/code@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/code@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/utils": 0.11.2
|
||||
"@lexical/utils": 0.11.3
|
||||
prismjs: ^1.27.0
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: af715151a741c1a74b8e5fe338329541c08635398c07b7c4078d8c8d8160be62416ac2c5995a032cad4308cf8db98deaf5029e1547c9a377660803e358e78ccb
|
||||
lexical: 0.11.3
|
||||
checksum: 35494f71c7f102c61a8737880762f64087b6345a1c069e8a13aa0c30a7ffaca433e192e70d1e891571e53cb3854e2ec18643615f18d693c9e3b0bf5cd6542624
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/dragon@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/dragon@npm:0.11.2"
|
||||
"@lexical/dragon@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/dragon@npm:0.11.3"
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: 1926f48f08ededfee416c3ce6ffade01010156510fd8eb04b7c8d68f9d37b7196ce0356c4e5d1c863d582831a69204858d9df5d2804c606880b1afdcc5a7c903
|
||||
lexical: 0.11.3
|
||||
checksum: 8bafde2d36d67c04d2d21f45def17a7b21834260b2366c7ca0f6c5bb12d64ed5d5b24bf1190347fb03b5e10cc5f2b57e9726eba52ca1f5f517fcbb87eff7ab07
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/hashtag@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/hashtag@npm:0.11.2"
|
||||
"@lexical/hashtag@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/hashtag@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/utils": 0.11.2
|
||||
"@lexical/utils": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: 34dbe2a803739239b0ab3f60dbb1ab7f13c30a7614d4e5a62c5b498a8a07babe014eda309786cde96a050e0afa056cad5f99e3748c4d8cb1d2a29dcd7480b57c
|
||||
lexical: 0.11.3
|
||||
checksum: db68da41653eb74765197b9630e1f78b7d1e5785b331f88f4578da707f44bf0afdcc6e076186400bac02dfda8c47751ecb57ad69a42cad59c79f9f8f346cd7d9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/headless@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/headless@npm:0.11.2"
|
||||
"@lexical/headless@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/headless@npm:0.11.3"
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: abc8b7602b29da48a767470a8dc0c680404214fc615f1689dc1188d17b1667391a13cbe53aa56b004047ba9f62dbeea912cb8e6d35871818fc666d0147e9c1bf
|
||||
lexical: 0.11.3
|
||||
checksum: 9bbabea45714a69deff7a6df7df55033c71904ffcee7398b8713e6178aace28f2fd75e111ca1f4113b13b2161498413ec82d90587f6289b35ab0de355494ebd2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/history@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/history@npm:0.11.2"
|
||||
"@lexical/history@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/history@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/utils": 0.11.2
|
||||
"@lexical/utils": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: a63562baa20cf11412004601978846da059b87506f64f498b2fcb5b1401f48c1e3048dc531aa3dcb813e1dd377164beeb651ac46180f618f3727838dfa89389f
|
||||
lexical: 0.11.3
|
||||
checksum: 59d828f647193f99ac6c8e04b8be7faa10f9862ff99efb5a24a2c4c5dd881b5da677a9531774f77737b9fe38015463588f5f6ea81d3d6c6ba5d47d5731ddd3c6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/html@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/html@npm:0.11.2"
|
||||
"@lexical/html@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/html@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/selection": 0.11.2
|
||||
"@lexical/selection": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: 32aa097370188fe825dc61fe7750ee2162f94cd8204e1f4f501eeecb7b437b7b1f22bc834a093cde49657a26c97bd57b3a4cd7c365a95c0365400389751fe8d3
|
||||
lexical: 0.11.3
|
||||
checksum: 08ecd189fc4ebc0c9e598658b07035f8905dd5ea8c254e01e4e655296453a4d03796f354fb9e8febdddadb8f3c29f57bbf2ef15a013d1a1bb69cf41853276936
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/link@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/link@npm:0.11.2"
|
||||
"@lexical/link@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/link@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/utils": 0.11.2
|
||||
"@lexical/utils": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: 78a71053f73c3c659f786e3aa8a9e6332f019cf7d9e90ffbf64bd6466f842bb9a9dcba770454ed30fff646aa3d5abe016c80a7364b38349e0987a1891d9bec0b
|
||||
lexical: 0.11.3
|
||||
checksum: f0b91dcb00c7d2419763ba3ea0c02dff8dc669a343f7ef8aaeef5025a048556b223f501ad20c54f3848c936489eb74e41e51e3cbd1653f70d365fa6b80f87cc3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/list@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/list@npm:0.11.2"
|
||||
"@lexical/list@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/list@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/utils": 0.11.2
|
||||
"@lexical/utils": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: c94f35ec62a980c7af66582a4b1c9c0d8d7af3e4a9bdcff0323626882dd8a2a80ca0e2a8a212b09e6066741e207ebbeb743e0817950d14216ad55d9e22d8bd61
|
||||
lexical: 0.11.3
|
||||
checksum: 59bf95dc1f23c7a86a55caa435bc7861194ff271ea20444f888aaff201d3972722c43fb311e60b6cab9331910bb3afed45e71e7275555e0965d2af193c4aef60
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/mark@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/mark@npm:0.11.2"
|
||||
"@lexical/mark@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/mark@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/utils": 0.11.2
|
||||
"@lexical/utils": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: e7edadd085ebcff1b4ad6c4467d2a63c6c417b48ac76bd63e64659d30e63d7ec5e86fc56f7a3480b50582f069cd6c81a8ea28afa2a4df96fb7dfab4dda878408
|
||||
lexical: 0.11.3
|
||||
checksum: e323b1d614975507b3bcc7ad6a5c48551c96c5986b09efc483914a1b1d2f481a1a99d6420042e992520e4f60827aa2b401ba22f60a7ef81d7f84387f241305f1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/markdown@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/markdown@npm:0.11.2"
|
||||
"@lexical/markdown@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/markdown@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/code": 0.11.2
|
||||
"@lexical/link": 0.11.2
|
||||
"@lexical/list": 0.11.2
|
||||
"@lexical/rich-text": 0.11.2
|
||||
"@lexical/text": 0.11.2
|
||||
"@lexical/utils": 0.11.2
|
||||
"@lexical/code": 0.11.3
|
||||
"@lexical/link": 0.11.3
|
||||
"@lexical/list": 0.11.3
|
||||
"@lexical/rich-text": 0.11.3
|
||||
"@lexical/text": 0.11.3
|
||||
"@lexical/utils": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: 89bb22134d3c1835a7936c7b2aa63df7d70cac8a369b4a5bdae96de72181d23db1ffe31c6d1b6e6d1b648a0bde6f177d665f48953a4ae43ab0eebaf6b3602d3e
|
||||
lexical: 0.11.3
|
||||
checksum: c43e2fff7ee7aad174e829923aa4df1bfd480ab1f645f24e22e8728bf5178995e31b465aed71e7e1aa82152aba748cf71cbf7f5e927da80d6e19d1a63e731485
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/offset@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/offset@npm:0.11.2"
|
||||
"@lexical/offset@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/offset@npm:0.11.3"
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: 4d1093bc0afa4f2f51d45c4b36b21779e6fa2b17b9a8d5d95c9ca0482b8759aca026dba7fae5ac2d695e7e22d9262b828b9157b4a7a266a1f7861848cd3520a4
|
||||
lexical: 0.11.3
|
||||
checksum: bdc1f8fc040d2213d16233140e719b951932f3d22a6c7528aadd5867bca9be7e487cfa5c45d96ff7ce7bc1e4fabea17b4ce31dc331fede657323125ae01335e3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/overflow@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/overflow@npm:0.11.2"
|
||||
"@lexical/overflow@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/overflow@npm:0.11.3"
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: a6751df5984be024bfc60414e92bee1fc6336bf647234d30b4197b8bf972bbd0c81f61fd15449399fdc06f0de4ae03001431aacb90356097293cd149c8c07551
|
||||
lexical: 0.11.3
|
||||
checksum: d0f74582cc71bada93bb5c5653c7a425922d1bb8f69e782676ad2c52560a5d4a4f97ab4b2234d3047c9895ae8bdc4bed8f8d064ea23b756e2dbc4dafb2d4663c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/plain-text@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/plain-text@npm:0.11.2"
|
||||
"@lexical/plain-text@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/plain-text@npm:0.11.3"
|
||||
peerDependencies:
|
||||
"@lexical/clipboard": 0.11.2
|
||||
"@lexical/selection": 0.11.2
|
||||
"@lexical/utils": 0.11.2
|
||||
lexical: 0.11.2
|
||||
checksum: ddc0ac63f19652134473d287279c7bac06948e78e0d9e2549d6fed8472b3eb40ff6966c0f358e4e085b46bcd1b95822bd6d865bc848e576aed0ec8339675517d
|
||||
"@lexical/clipboard": 0.11.3
|
||||
"@lexical/selection": 0.11.3
|
||||
"@lexical/utils": 0.11.3
|
||||
lexical: 0.11.3
|
||||
checksum: 7d0929b87c6ba1658537163df6f8e58f0df7234e4b356aa612f793b845b594786235c78aaab59dbca93860f5d3d6a3b438622b368dfa270443857422fc811c66
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/react@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/react@npm:0.11.2"
|
||||
"@lexical/react@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/react@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/clipboard": 0.11.2
|
||||
"@lexical/code": 0.11.2
|
||||
"@lexical/dragon": 0.11.2
|
||||
"@lexical/hashtag": 0.11.2
|
||||
"@lexical/history": 0.11.2
|
||||
"@lexical/link": 0.11.2
|
||||
"@lexical/list": 0.11.2
|
||||
"@lexical/mark": 0.11.2
|
||||
"@lexical/markdown": 0.11.2
|
||||
"@lexical/overflow": 0.11.2
|
||||
"@lexical/plain-text": 0.11.2
|
||||
"@lexical/rich-text": 0.11.2
|
||||
"@lexical/selection": 0.11.2
|
||||
"@lexical/table": 0.11.2
|
||||
"@lexical/text": 0.11.2
|
||||
"@lexical/utils": 0.11.2
|
||||
"@lexical/yjs": 0.11.2
|
||||
"@lexical/clipboard": 0.11.3
|
||||
"@lexical/code": 0.11.3
|
||||
"@lexical/dragon": 0.11.3
|
||||
"@lexical/hashtag": 0.11.3
|
||||
"@lexical/history": 0.11.3
|
||||
"@lexical/link": 0.11.3
|
||||
"@lexical/list": 0.11.3
|
||||
"@lexical/mark": 0.11.3
|
||||
"@lexical/markdown": 0.11.3
|
||||
"@lexical/overflow": 0.11.3
|
||||
"@lexical/plain-text": 0.11.3
|
||||
"@lexical/rich-text": 0.11.3
|
||||
"@lexical/selection": 0.11.3
|
||||
"@lexical/table": 0.11.3
|
||||
"@lexical/text": 0.11.3
|
||||
"@lexical/utils": 0.11.3
|
||||
"@lexical/yjs": 0.11.3
|
||||
react-error-boundary: ^3.1.4
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
lexical: 0.11.3
|
||||
react: ">=17.x"
|
||||
react-dom: ">=17.x"
|
||||
checksum: 8fdc60712c248c9c242b717f39fe0c359b4db73a512b7ed7f7ac761061a789d4992cc533664f5ae6229384c4f426fe92f465f6b73b013c072dd3b7b057a5ec1d
|
||||
checksum: a58c1ae508b9422a0ee542e62a28cbccf10b24b9029966de8b79c091cf4b697e9cfa23e05aa280a613d0dca3bd858480eb00ba5180868c55cc65381a0debcdec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/rich-text@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/rich-text@npm:0.11.2"
|
||||
"@lexical/rich-text@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/rich-text@npm:0.11.3"
|
||||
peerDependencies:
|
||||
"@lexical/clipboard": 0.11.2
|
||||
"@lexical/selection": 0.11.2
|
||||
"@lexical/utils": 0.11.2
|
||||
lexical: 0.11.2
|
||||
checksum: 78d50a506b8431d33f99e1dc6041b68645f44791c6012490f5de62cc78214f546d8518ce495c8e887cf77ab57d010c8ff82841e5cb76bc4d61021da66cd1c9fd
|
||||
"@lexical/clipboard": 0.11.3
|
||||
"@lexical/selection": 0.11.3
|
||||
"@lexical/utils": 0.11.3
|
||||
lexical: 0.11.3
|
||||
checksum: e3491a14f8a880e7fa0fad91c5e9a242a8d2977d1bbfec4ac807300249623c074c882dae30924c68c9f46263eaf816f1fb0a063180631bb045f0df1d4996b54a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/selection@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/selection@npm:0.11.2"
|
||||
"@lexical/selection@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/selection@npm:0.11.3"
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: 7b1f30331de349df9d5aab936b962676c1ffa8dd15b03049851c0bf8a232a0a747998b83656cf970279ea3e6f426e429ae5db2e0e4180c0a94f9f900c6c4070c
|
||||
lexical: 0.11.3
|
||||
checksum: e57ca9a08f1e302f62349245e44949f71235ac48e297cb0390b6dff3db4aeb475fced62e78d81692f1e2974a0766dd25c0f83e6130870e848de0f5f0554af4f5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/table@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/table@npm:0.11.2"
|
||||
"@lexical/table@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/table@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/utils": 0.11.2
|
||||
"@lexical/utils": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: 9ee3cd33631dcf85956990fe27489c6d2eee554505d4e0c5dd7a57c19f2d4fa878448bb3d7ae8083e4db633ed05d02f305f4c4a298b9dd7e499be4646b69613b
|
||||
lexical: 0.11.3
|
||||
checksum: d1aa2fc8d192830aec4fe2e8d2f6a64b4e061b70a618c166478ee889fc561c28487351e7dba8b7463053dc67c85faeb1f44cf0984ca3e15e6e41aa483063a40e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/text@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/text@npm:0.11.2"
|
||||
"@lexical/text@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/text@npm:0.11.3"
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: ee7e0859d7f4119a5bee9ee2f8255845ce32b16997cc353087976c8f65967e620494d31c63dc54b6001003534d0682418ccd407f61af53bd3bf7836da1d3b474
|
||||
lexical: 0.11.3
|
||||
checksum: 48b3e0e54f2dc08958872dd59ad4806c8fe20ab5bc832241ad7811ea36159366b58ea201b97cf96ce9c383f3b534aa1627b713bd3777cc3bff292a1a92bde6d2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/utils@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/utils@npm:0.11.2"
|
||||
"@lexical/utils@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/utils@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/list": 0.11.2
|
||||
"@lexical/selection": 0.11.2
|
||||
"@lexical/table": 0.11.2
|
||||
"@lexical/list": 0.11.3
|
||||
"@lexical/selection": 0.11.3
|
||||
"@lexical/table": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
checksum: 182f9150843b4ada726c31732afca2fa9465c9e08372fd6fbc91e676768a01866d3878247c12ee1f9a94f6397e3b0dfea462c0a35139bce174c773b95400c08b
|
||||
lexical: 0.11.3
|
||||
checksum: f7e185ee8f50398b7533a4f1e0f495c803a6ac4b350af30e1791013b63b2ee45465e65f6338ceea72bee894076fe9e46d33a40ae3c5e5fbaebda6eb3daf261b9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lexical/yjs@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "@lexical/yjs@npm:0.11.2"
|
||||
"@lexical/yjs@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "@lexical/yjs@npm:0.11.3"
|
||||
dependencies:
|
||||
"@lexical/offset": 0.11.2
|
||||
"@lexical/offset": 0.11.3
|
||||
peerDependencies:
|
||||
lexical: 0.11.2
|
||||
lexical: 0.11.3
|
||||
yjs: ">=13.5.22"
|
||||
checksum: a886ed415f845f9bd1e724dede42b10611f48b7bd6acef6781219e3d6220b3e984abcd7017e3ada636355558dd298c5cd4c202e0b87fb7f3c5fb36a494d7a4e9
|
||||
checksum: f201fc07c863aea24043d9add6f3b98a871ef1811fca8a3ca90d6b36cc5aa56ab164728e9bbf007dd3401598bdc75fc68daf0b9bcb027b4fd7584da8b3d3e2d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5086,8 +5086,8 @@ __metadata:
|
||||
"@babel/plugin-transform-react-jsx": ^7.19.0
|
||||
"@babel/preset-env": "*"
|
||||
"@babel/preset-typescript": ^7.21.5
|
||||
"@lexical/headless": 0.11.2
|
||||
"@lexical/react": 0.11.2
|
||||
"@lexical/headless": 0.11.3
|
||||
"@lexical/react": 0.11.3
|
||||
"@pmmmwh/react-refresh-webpack-plugin": ^0.5.10
|
||||
"@radix-ui/react-slot": ^1.0.1
|
||||
"@simplewebauthn/browser": ^7.1.0
|
||||
@@ -5137,7 +5137,7 @@ __metadata:
|
||||
identity-obj-proxy: ^3.0.0
|
||||
jest: ^29.3.1
|
||||
jest-environment-jsdom: ^29.3.1
|
||||
lexical: 0.11.2
|
||||
lexical: 0.11.3
|
||||
lint-staged: ">=13"
|
||||
mini-css-extract-plugin: ^2.7.2
|
||||
minimatch: ^5.1.1
|
||||
@@ -15316,10 +15316,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lexical@npm:0.11.2":
|
||||
version: 0.11.2
|
||||
resolution: "lexical@npm:0.11.2"
|
||||
checksum: 85ec66227b38e60c430e4a4be489b483022477fc4b815229fc96ab71e30b059f8ff443e2e10e2c68f1d731d856027e4e1a86a4fed2730fbde1267a0156676b84
|
||||
"lexical@npm:0.11.3":
|
||||
version: 0.11.3
|
||||
resolution: "lexical@npm:0.11.3"
|
||||
checksum: 89fa276db6e253c1b52f097155e6bd6402c4292845a37a0910b992c9051e339df5186d41f3f35eb3040ad8f322716c63c22b5e154b04e698814f5f4a02e49635
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user