chore: handle notifications from websockets (#2472)

This commit is contained in:
Karol Sójko
2023-08-31 15:32:05 +02:00
committed by GitHub
parent f35c34567e
commit cc6d300dc7
16 changed files with 76 additions and 33 deletions

View File

@@ -0,0 +1,7 @@
import { Either } from '@standardnotes/common'
import { UserRolesChangedEventPayload, NotificationAddedForUserEventPayload } from '@standardnotes/domain-events'
export interface WebSocketsEventData {
type: string
payload: Either<UserRolesChangedEventPayload, NotificationAddedForUserEventPayload>
}

View File

@@ -1,3 +1,4 @@
export enum WebSocketsServiceEvent { export enum WebSocketsServiceEvent {
UserRoleMessageReceived = 'WebSocketMessageReceived', UserRoleMessageReceived = 'WebSocketMessageReceived',
NotificationAddedForUser = 'NotificationAddedForUser',
} }

View File

@@ -1,13 +1,14 @@
import { InternalEventBusInterface } from '@standardnotes/services'
import { WebSocketApiServiceInterface } from '@standardnotes/api' import { WebSocketApiServiceInterface } from '@standardnotes/api'
import { StorageKey, DiskStorageService } from '@Lib/index'
import { WebSocketsService } from './WebsocketsService' import { WebSocketsService } from './WebsocketsService'
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { StorageKey } from '../Storage/StorageKeys'
describe('webSocketsService', () => { describe('webSocketsService', () => {
const webSocketUrl = '' const webSocketUrl = ''
let storageService: DiskStorageService let storageService: StorageServiceInterface
let webSocketApiService: WebSocketApiServiceInterface let webSocketApiService: WebSocketApiServiceInterface
let internalEventBus: InternalEventBusInterface let internalEventBus: InternalEventBusInterface
@@ -16,7 +17,7 @@ describe('webSocketsService', () => {
} }
beforeEach(() => { beforeEach(() => {
storageService = {} as jest.Mocked<DiskStorageService> storageService = {} as jest.Mocked<StorageServiceInterface>
storageService.setValue = jest.fn() storageService.setValue = jest.fn()
internalEventBus = {} as jest.Mocked<InternalEventBusInterface> internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
@@ -27,9 +28,9 @@ describe('webSocketsService', () => {
}) })
describe('setWebSocketUrl()', () => { describe('setWebSocketUrl()', () => {
it('saves url in local storage', async () => { it('saves url in local storage', () => {
const webSocketUrl = 'wss://test-websocket' const webSocketUrl = 'wss://test-websocket'
await createService().setWebSocketUrl(webSocketUrl) createService().setWebSocketUrl(webSocketUrl)
expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.WebSocketUrl, webSocketUrl) expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.WebSocketUrl, webSocketUrl)
}) })
}) })

View File

@@ -1,15 +1,18 @@
import { isErrorResponse } from '@standardnotes/responses' import { isErrorResponse } from '@standardnotes/responses'
import { UserRolesChangedEvent } from '@standardnotes/domain-events'
import { import {
AbstractService, DomainEventInterface,
InternalEventBusInterface, UserRolesChangedEvent,
StorageKey, NotificationAddedForUserEvent,
StorageServiceInterface, } from '@standardnotes/domain-events'
} from '@standardnotes/services'
import { WebSocketApiServiceInterface } from '@standardnotes/api' import { WebSocketApiServiceInterface } from '@standardnotes/api'
import { WebSocketsServiceEvent } from './WebSocketsServiceEvent' import { WebSocketsServiceEvent } from './WebSocketsServiceEvent'
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { AbstractService } from '../Service/AbstractService'
import { StorageKey } from '../Storage/StorageKeys'
import { WebSocketsEventData } from './WebSocketsEventData'
export class WebSocketsService extends AbstractService<WebSocketsServiceEvent, UserRolesChangedEvent> { export class WebSocketsService extends AbstractService<WebSocketsServiceEvent, WebSocketsEventData> {
private webSocket?: WebSocket private webSocket?: WebSocket
constructor( constructor(
@@ -61,9 +64,21 @@ export class WebSocketsService extends AbstractService<WebSocketsServiceEvent, U
this.webSocket?.close() this.webSocket?.close()
} }
private onWebSocketMessage(event: MessageEvent) { private onWebSocketMessage(messageEvent: MessageEvent) {
const eventData: UserRolesChangedEvent = JSON.parse(event.data) const eventData: DomainEventInterface = JSON.parse(messageEvent.data)
void this.notifyEvent(WebSocketsServiceEvent.UserRoleMessageReceived, eventData) switch (eventData.type) {
case 'USER_ROLES_CHANGED':
void this.notifyEvent(WebSocketsServiceEvent.UserRoleMessageReceived, eventData as UserRolesChangedEvent)
break
case 'NOTIFICATION_ADDED_FOR_USER':
void this.notifyEvent(
WebSocketsServiceEvent.NotificationAddedForUser,
eventData as NotificationAddedForUserEvent,
)
break
default:
break
}
} }
private onWebSocketClose() { private onWebSocketClose() {

View File

@@ -1,4 +1,5 @@
import { NotificationServerHash } from '@standardnotes/responses' import { NotificationServerHash } from '@standardnotes/responses'
import { NotificationAddedForUserEvent } from '@standardnotes/domain-events'
import { SyncEvent, SyncEventReceivedNotificationsData } from '../Event/SyncEvent' import { SyncEvent, SyncEventReceivedNotificationsData } from '../Event/SyncEvent'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface' import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
@@ -6,6 +7,7 @@ import { InternalEventInterface } from '../Internal/InternalEventInterface'
import { AbstractService } from '../Service/AbstractService' import { AbstractService } from '../Service/AbstractService'
import { NotificationServiceEventPayload, NotificationServiceEvent } from './NotificationServiceEvent' import { NotificationServiceEventPayload, NotificationServiceEvent } from './NotificationServiceEvent'
import { NotificationPayload } from '@standardnotes/domain-core' import { NotificationPayload } from '@standardnotes/domain-core'
import { WebSocketsServiceEvent } from '../Api/WebSocketsServiceEvent'
export class NotificationService export class NotificationService
extends AbstractService<NotificationServiceEvent, NotificationServiceEventPayload> extends AbstractService<NotificationServiceEvent, NotificationServiceEventPayload>
@@ -18,8 +20,13 @@ export class NotificationService
} }
async handleEvent(event: InternalEventInterface): Promise<void> { async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === SyncEvent.ReceivedNotifications) { switch (event.type) {
return this.handleReceivedNotifications(event.payload as SyncEventReceivedNotificationsData) case SyncEvent.ReceivedNotifications:
return this.handleReceivedNotifications(event.payload as SyncEventReceivedNotificationsData)
case WebSocketsServiceEvent.NotificationAddedForUser:
return this.handleReceivedNotifications([(event as NotificationAddedForUserEvent).payload.notification])
default:
break
} }
} }

View File

@@ -4,6 +4,9 @@ export * from './Api/ApiServiceEventData'
export * from './Api/LegacyApiServiceInterface' export * from './Api/LegacyApiServiceInterface'
export * from './Api/MetaReceivedData' export * from './Api/MetaReceivedData'
export * from './Api/SessionRefreshedData' export * from './Api/SessionRefreshedData'
export * from './Api/WebSocketsEventData'
export * from './Api/WebsocketsService'
export * from './Api/WebSocketsServiceEvent'
export * from './Application/AppGroupManagedApplication' export * from './Application/AppGroupManagedApplication'
export * from './Application/ApplicationInterface' export * from './Application/ApplicationInterface'
export * from './Application/ApplicationStage' export * from './Application/ApplicationStage'

View File

@@ -1,6 +1,5 @@
import { MfaService } from './../Services/Mfa/MfaService' import { MfaService } from './../Services/Mfa/MfaService'
import { KeyRecoveryService } from './../Services/KeyRecovery/KeyRecoveryService' import { KeyRecoveryService } from './../Services/KeyRecovery/KeyRecoveryService'
import { WebSocketsService } from './../Services/Api/WebsocketsService'
import { MigrationService } from './../Services/Migration/MigrationService' import { MigrationService } from './../Services/Migration/MigrationService'
import { LegacyApiService } from './../Services/Api/ApiService' import { LegacyApiService } from './../Services/Api/ApiService'
import { FeaturesService } from '@Lib/Services/Features/FeaturesService' import { FeaturesService } from '@Lib/Services/Features/FeaturesService'
@@ -83,6 +82,7 @@ import {
CreateEncryptedBackupFile, CreateEncryptedBackupFile,
GetTransitionStatus, GetTransitionStatus,
StartTransition, StartTransition,
WebSocketsService,
} from '@standardnotes/services' } from '@standardnotes/services'
import { import {
SNNote, SNNote,

View File

@@ -22,7 +22,6 @@ import { ProtectionService } from '../../Services/Protection/ProtectionService'
import { SyncService } from '../../Services/Sync/SyncService' import { SyncService } from '../../Services/Sync/SyncService'
import { HistoryManager } from '../../Services/History/HistoryManager' import { HistoryManager } from '../../Services/History/HistoryManager'
import { SessionManager } from '../../Services/Session/SessionManager' import { SessionManager } from '../../Services/Session/SessionManager'
import { WebSocketsService } from '../../Services/Api/WebsocketsService'
import { LegacyApiService } from '../../Services/Api/ApiService' import { LegacyApiService } from '../../Services/Api/ApiService'
import { SnjsVersion } from '../../Version' import { SnjsVersion } from '../../Version'
import { DeprecatedHttpService } from '../../Services/Api/DeprecatedHttpService' import { DeprecatedHttpService } from '../../Services/Api/DeprecatedHttpService'
@@ -143,6 +142,7 @@ import {
SyncLocalVaultsWithRemoteSharedVaults, SyncLocalVaultsWithRemoteSharedVaults,
GetTransitionStatus, GetTransitionStatus,
StartTransition, StartTransition,
WebSocketsService,
} from '@standardnotes/services' } from '@standardnotes/services'
import { ItemManager } from '../../Services/Items/ItemManager' import { ItemManager } from '../../Services/Items/ItemManager'
import { PayloadManager } from '../../Services/Payloads/PayloadManager' import { PayloadManager } from '../../Services/Payloads/PayloadManager'

View File

@@ -2,4 +2,3 @@ export * from './ApiService'
export * from './DeprecatedHttpService' export * from './DeprecatedHttpService'
export * from './Paths' export * from './Paths'
export * from '../Session/SessionManager' export * from '../Session/SessionManager'
export * from './WebsocketsService'

View File

@@ -4,7 +4,6 @@ import { SettingName } from '@standardnotes/settings'
import { FeaturesService } from '@Lib/Services/Features' import { FeaturesService } from '@Lib/Services/Features'
import { RoleName, ContentType, Uuid, Result } from '@standardnotes/domain-core' import { RoleName, ContentType, Uuid, Result } from '@standardnotes/domain-core'
import { NativeFeatureIdentifier, GetFeatures } from '@standardnotes/features' import { NativeFeatureIdentifier, GetFeatures } from '@standardnotes/features'
import { WebSocketsService } from '../Api/WebsocketsService'
import { SettingsService } from '../Settings' import { SettingsService } from '../Settings'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { import {
@@ -23,6 +22,7 @@ import {
UserServiceInterface, UserServiceInterface,
UserService, UserService,
IsApplicationUsingThirdPartyHost, IsApplicationUsingThirdPartyHost,
WebSocketsService,
} from '@standardnotes/services' } from '@standardnotes/services'
import { LegacyApiService, SessionManager } from '../Api' import { LegacyApiService, SessionManager } from '../Api'
import { ItemManager } from '../Items' import { ItemManager } from '../Items'

View File

@@ -3,8 +3,6 @@ import { arraysEqual, removeFromArray, lastElement, LoggerInterface } from '@sta
import { ClientDisplayableError } from '@standardnotes/responses' import { ClientDisplayableError } from '@standardnotes/responses'
import { RoleName, ContentType, Uuid } from '@standardnotes/domain-core' import { RoleName, ContentType, Uuid } from '@standardnotes/domain-core'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { WebSocketsService } from '../Api/WebsocketsService'
import { WebSocketsServiceEvent } from '../Api/WebSocketsServiceEvent'
import { UserRolesChangedEvent } from '@standardnotes/domain-events' import { UserRolesChangedEvent } from '@standardnotes/domain-events'
import { ExperimentalFeatures, FindNativeFeature, NativeFeatureIdentifier } from '@standardnotes/features' import { ExperimentalFeatures, FindNativeFeature, NativeFeatureIdentifier } from '@standardnotes/features'
import { import {
@@ -47,6 +45,8 @@ import {
ApplicationEvent, ApplicationEvent,
ApplicationStageChangedEventPayload, ApplicationStageChangedEventPayload,
IsApplicationUsingThirdPartyHost, IsApplicationUsingThirdPartyHost,
WebSocketsServiceEvent,
WebSocketsService,
} from '@standardnotes/services' } from '@standardnotes/services'
import { DownloadRemoteThirdPartyFeatureUseCase } from './UseCase/DownloadRemoteThirdPartyFeature' import { DownloadRemoteThirdPartyFeatureUseCase } from './UseCase/DownloadRemoteThirdPartyFeature'

View File

@@ -34,6 +34,7 @@ import {
ApplicationStage, ApplicationStage,
GetKeyPairs, GetKeyPairs,
IsApplicationUsingThirdPartyHost, IsApplicationUsingThirdPartyHost,
WebSocketsService,
} from '@standardnotes/services' } from '@standardnotes/services'
import { Base64String, PureCryptoInterface } from '@standardnotes/sncrypto-common' import { Base64String, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { import {
@@ -59,7 +60,6 @@ import { RawStorageValue } from './Sessions/Types'
import { ShareToken } from './ShareToken' import { ShareToken } from './ShareToken'
import { LegacyApiService } from '../Api/ApiService' import { LegacyApiService } from '../Api/ApiService'
import { DiskStorageService } from '../Storage/DiskStorageService' import { DiskStorageService } from '../Storage/DiskStorageService'
import { WebSocketsService } from '../Api/WebsocketsService'
import { Strings } from '@Lib/Strings' import { Strings } from '@Lib/Strings'
import { UuidString } from '@Lib/Types/UuidString' import { UuidString } from '@Lib/Types/UuidString'
import { ChallengeResponse, ChallengeService } from '../Challenge' import { ChallengeResponse, ChallengeService } from '../Challenge'

View File

@@ -38,7 +38,7 @@
"@standardnotes/api": "workspace:*", "@standardnotes/api": "workspace:*",
"@standardnotes/common": "^1.50.0", "@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.25.0", "@standardnotes/domain-core": "^1.25.0",
"@standardnotes/domain-events": "^2.108.1", "@standardnotes/domain-events": "^2.120.0",
"@standardnotes/encryption": "workspace:*", "@standardnotes/encryption": "workspace:*",
"@standardnotes/features": "workspace:*", "@standardnotes/features": "workspace:*",
"@standardnotes/files": "workspace:*", "@standardnotes/files": "workspace:*",

View File

@@ -4349,13 +4349,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@standardnotes/domain-events@npm:^2.108.1": "@standardnotes/domain-events@npm:^2.120.0":
version: 2.113.1 version: 2.120.0
resolution: "@standardnotes/domain-events@npm:2.113.1" resolution: "@standardnotes/domain-events@npm:2.120.0"
dependencies: dependencies:
"@standardnotes/predicates": 1.6.9 "@standardnotes/predicates": 1.6.9
"@standardnotes/security": 1.8.1 "@standardnotes/security": 1.12.0
checksum: de7a64b5882c2126fb6c7c7485c330e7065f38cfad83d6bfe3b4025bd42b28e6de6b7fb137086661b8ab16e8ff63b9b9a91e1b52b23ba413f108605d02f8e61a checksum: bcd6caf10bc050199db4d0fb13605d43773418c1420efea583381ae6cd33a1a365451a61f3d8d6e08d19a52005e5d67ad2b4c8f3b8f951269b1a2a4bc0ac33cf
languageName: node languageName: node
linkType: hard linkType: hard
@@ -4703,7 +4703,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@standardnotes/security@npm:1.8.1, @standardnotes/security@npm:^1.2.0": "@standardnotes/security@npm:1.12.0":
version: 1.12.0
resolution: "@standardnotes/security@npm:1.12.0"
dependencies:
jsonwebtoken: ^9.0.0
reflect-metadata: ^0.1.13
checksum: 96d42255e79fc2cf4c52f3d370c04ae00296673993e1c0cea2356ca4a6c934d22d6100ba95f9ad602ffb72ee686367ef4a263dc6a6c51795d9b61dc835e635cd
languageName: node
linkType: hard
"@standardnotes/security@npm:^1.2.0":
version: 1.8.1 version: 1.8.1
resolution: "@standardnotes/security@npm:1.8.1" resolution: "@standardnotes/security@npm:1.8.1"
dependencies: dependencies:
@@ -4819,7 +4829,7 @@ __metadata:
"@standardnotes/api": "workspace:*" "@standardnotes/api": "workspace:*"
"@standardnotes/common": ^1.50.0 "@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.25.0 "@standardnotes/domain-core": ^1.25.0
"@standardnotes/domain-events": ^2.108.1 "@standardnotes/domain-events": ^2.120.0
"@standardnotes/encryption": "workspace:*" "@standardnotes/encryption": "workspace:*"
"@standardnotes/features": "workspace:*" "@standardnotes/features": "workspace:*"
"@standardnotes/files": "workspace:*" "@standardnotes/files": "workspace:*"