Skip to content
This repository was archived by the owner on Jul 6, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/services/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"prebuild": "yarn clean",
"build": "tsc -p tsconfig.json",
"lint": "eslint . --ext .ts",
"test:unit": "jest spec --coverage --passWithNoTests"
"test:unit": "jest spec --coverage"
},
"dependencies": {
"@standardnotes/applications": "^1.1.3",
Expand Down
1 change: 1 addition & 0 deletions packages/services/src/Domain/Device/AbstractDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DeviceInterface } from './DeviceInterface'
* raw values from the database or value storage.
* This avoids the need for platforms to override migrations directly.
*/
/* istanbul ignore file */
export abstract class AbstractDevice implements DeviceInterface {
public interval: any
public timeout: any
Expand Down
49 changes: 49 additions & 0 deletions packages/services/src/Domain/Internal/InternalEventBus.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { InternalEventHandlerInterface } from './InternalEventHandlerInterface'
import { InternalEventBus } from './InternalEventBus'

describe('InternalEventBus', () => {
let eventHandler1: InternalEventHandlerInterface
let eventHandler2: InternalEventHandlerInterface
let eventHandler3: InternalEventHandlerInterface

const createEventBus = () => new InternalEventBus()

beforeEach(() => {
eventHandler1 = {} as jest.Mocked<InternalEventHandlerInterface>
eventHandler1.handleEvent = jest.fn()

eventHandler2 = {} as jest.Mocked<InternalEventHandlerInterface>
eventHandler2.handleEvent = jest.fn()

eventHandler3 = {} as jest.Mocked<InternalEventHandlerInterface>
eventHandler3.handleEvent = jest.fn()
})

it('should trigger appropriate event handlers upon event publishing', () => {
const eventBus = createEventBus()
eventBus.addEventHandler(eventHandler1, 'test_event_1')
eventBus.addEventHandler(eventHandler2, 'test_event_2')
eventBus.addEventHandler(eventHandler1, 'test_event_3')
eventBus.addEventHandler(eventHandler3, 'test_event_2')

eventBus.publish({ type: 'test_event_2', payload: { foo: 'bar' } })

expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
expect(eventHandler2.handleEvent).toHaveBeenCalledWith({ type: 'test_event_2', payload: { foo: 'bar' } })
expect(eventHandler3.handleEvent).toHaveBeenCalledWith({ type: 'test_event_2', payload: { foo: 'bar' } })
})

it('should do nothing if there are no appropriate event handlers', () => {
const eventBus = createEventBus()
eventBus.addEventHandler(eventHandler1, 'test_event_1')
eventBus.addEventHandler(eventHandler2, 'test_event_2')
eventBus.addEventHandler(eventHandler1, 'test_event_3')
eventBus.addEventHandler(eventHandler3, 'test_event_2')

eventBus.publish({ type: 'test_event_4', payload: { foo: 'bar' } })

expect(eventHandler1.handleEvent).not.toHaveBeenCalled()
expect(eventHandler2.handleEvent).not.toHaveBeenCalled()
expect(eventHandler3.handleEvent).not.toHaveBeenCalled()
})
})
35 changes: 35 additions & 0 deletions packages/services/src/Domain/Internal/InternalEventBus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { InternalEventBusInterface } from './InternalEventBusInterface'
import { InternalEventHandlerInterface } from './InternalEventHandlerInterface'
import { InternalEventInterface } from './InternalEventInterface'
import { InternalEventType } from './InternalEventType'

export class InternalEventBus implements InternalEventBusInterface {
private eventHandlers: Map<InternalEventType, InternalEventHandlerInterface[]>

constructor(
) {
this.eventHandlers = new Map<InternalEventType, InternalEventHandlerInterface[]>()
}

addEventHandler(handler: InternalEventHandlerInterface, eventType: string): void {
let handlersForEventType = this.eventHandlers.get(eventType)
if (handlersForEventType === undefined) {
handlersForEventType = []
}

handlersForEventType.push(handler)

this.eventHandlers.set(eventType, handlersForEventType)
}

publish(event: InternalEventInterface): void {
const handlersForEventType = this.eventHandlers.get(event.type)
if (handlersForEventType === undefined) {
return
}

for (const handlerForEventType of handlersForEventType) {
void handlerForEventType.handleEvent(event)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { InternalEventInterface } from './InternalEventInterface'
import { InternalEventType } from './InternalEventType'
import { InternalEventHandlerInterface } from './InternalEventHandlerInterface'

export interface InternalEventBusInterface {
addEventHandler(handler: InternalEventHandlerInterface, eventType: InternalEventType): void
publish(event: InternalEventInterface): void
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { InternalEventInterface } from './InternalEventInterface'

export interface InternalEventHandlerInterface {
handleEvent(event: InternalEventInterface): Promise<void>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { InternalEventType } from './InternalEventType'

export interface InternalEventInterface {
type: InternalEventType
payload: unknown
}
1 change: 1 addition & 0 deletions packages/services/src/Domain/Internal/InternalEventType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type InternalEventType = string
1 change: 1 addition & 0 deletions packages/services/src/Domain/Service/AbstractService.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* istanbul ignore file */
import { log, removeFromArray } from '@standardnotes/utils'
import { ApplicationStage } from '@standardnotes/applications'
import { DeviceInterface } from '../Device/DeviceInterface'
Expand Down
5 changes: 5 additions & 0 deletions packages/services/src/Domain/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export * from './Device/AbstractDevice'
export * from './Device/DeviceInterface'
export * from './Event/EventObserver'
export * from './Internal/InternalEventBus'
export * from './Internal/InternalEventBusInterface'
export * from './Internal/InternalEventHandlerInterface'
export * from './Internal/InternalEventInterface'
export * from './Internal/InternalEventType'
export * from './Service/AbstractService'
export * from './Service/ServiceInterface'
28 changes: 27 additions & 1 deletion packages/snjs/lib/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ import {
SNFileService,
SyncModes,
} from './services'
import { DeviceInterface, ServiceInterface } from '@standardnotes/services'
import {
DeviceInterface,
ServiceInterface,
InternalEventBusInterface,
InternalEventBus,
} from '@standardnotes/services'
import {
BACKUP_FILE_MORE_RECENT_THAN_ACCOUNT,
ErrorAlertStrings,
Expand Down Expand Up @@ -127,6 +132,7 @@ import { Subscription } from '@standardnotes/auth'
import { TagsToFoldersMigrationApplicator } from './migrations/applicators/tags_to_folders'
import { RemoteSession } from './services/Api/Session'
import { FilesClientInterface } from './services/Files/FileService'
import { ApiServiceEvent } from './services/Api/ApiService'

/** How often to automatically sync, in milliseconds */
const DEFAULT_AUTO_SYNC_INTERVAL = 30_000
Expand Down Expand Up @@ -178,6 +184,8 @@ export class SNApplication implements ListedInterface {
private listedService!: ListedService
private fileService!: SNFileService

private internalEventBus!: InternalEventBusInterface

private eventHandlers: ApplicationObserver[] = []
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private services: ServiceInterface<any, any>[] = []
Expand Down Expand Up @@ -240,7 +248,11 @@ export class SNApplication implements ListedInterface {
this.identifier = options.identifier
this.options = Object.freeze(fullyResovledOptions)

this.constructInternalEventBus()

this.constructServices()

this.defineInternalEventHandlers()
}

public get files(): FilesClientInterface {
Expand Down Expand Up @@ -1358,6 +1370,7 @@ export class SNApplication implements ListedInterface {
this.serviceObservers.length = 0
this.managedSubscribers.length = 0
this.streamRemovers.length = 0
this.clearInternalEventBus()
this.clearServices()
this.started = false

Expand Down Expand Up @@ -1707,6 +1720,18 @@ export class SNApplication implements ListedInterface {
this.services = []
}

private constructInternalEventBus(): void {
this.internalEventBus = new InternalEventBus()
}

private defineInternalEventHandlers(): void {
this.internalEventBus.addEventHandler(this.featuresService, ApiServiceEvent.MetaReceived)
}

private clearInternalEventBus(): void {
;(this.internalEventBus as unknown) = undefined
}

private createListedService(): void {
this.listedService = new ListedService(
this.apiService,
Expand Down Expand Up @@ -1815,6 +1840,7 @@ export class SNApplication implements ListedInterface {
this.apiService = new SNApiService(
this.httpService,
this.storageService,
this.internalEventBus,
this.options.defaultHost,
this.options.defaultFilesHost,
)
Expand Down
11 changes: 10 additions & 1 deletion packages/snjs/lib/services/Api/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { Role } from '@standardnotes/auth'
import { FeatureDescription } from '@standardnotes/features'
import { API_MESSAGE_FAILED_OFFLINE_ACTIVATION } from '@Lib/services/Api/Messages'
import { isUrlFirstParty, TRUSTED_FEATURE_HOSTS } from '@Lib/hosts'
import { AbstractService } from '@standardnotes/services'
import { AbstractService, InternalEventBusInterface } from '@standardnotes/services'

type PathNamesV1 = {
keyParams: string
Expand Down Expand Up @@ -148,6 +148,7 @@ export class SNApiService
constructor(
private httpService: SNHttpService,
private storageService: SNStorageService,
private internalEventBus: InternalEventBusInterface,
private host: string,
private filesHost: string,
) {
Expand Down Expand Up @@ -275,6 +276,14 @@ export class SNApiService
userUuid: meta.auth.userUuid,
userRoles: meta.auth.roles,
})

this.internalEventBus.publish({
type: ApiServiceEvent.MetaReceived,
payload: {
userUuid: meta.auth.userUuid,
userRoles: meta.auth.roles,
}
})
}
}

Expand Down
53 changes: 29 additions & 24 deletions packages/snjs/lib/services/Features/FeaturesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,26 @@ import { SNPureCrypto } from '@standardnotes/sncrypto-common'
import { ButtonType, SNAlertService } from '@Lib/services/AlertService'
import { TRUSTED_CUSTOM_EXTENSIONS_HOSTS, TRUSTED_FEATURE_HOSTS } from '@Lib/hosts'
import { Copy, lastElement } from '@standardnotes/utils'
import { AbstractService } from '@standardnotes/services'
import { FeaturesClientInterface } from './ClientInterface'
import {
FeaturesEvent,
FeatureStatus,
OfflineSubscriptionEntitlements,
SetOfflineFeaturesFunctionResponse,
} from './Types'
import { AbstractService, InternalEventHandlerInterface, InternalEventInterface } from '@standardnotes/services'

type GetOfflineSubscriptionDetailsResponse = OfflineSubscriptionEntitlements | ErrorObject

export class SNFeaturesService
extends AbstractService<FeaturesEvent>
implements FeaturesClientInterface
{
implements
FeaturesClientInterface,
InternalEventHandlerInterface {
private deinited = false
private roles: RoleName[] = []
private features: FeatureDescription[] = []
private enabledExperimentalFeatures: FeatureIdentifier[] = []
private removeApiServiceObserver: () => void
private removeWebSocketsServiceObserver: () => void
private removefeatureReposObserver: () => void
private removeSignInObserver: () => void
Expand All @@ -85,24 +85,6 @@ export class SNFeaturesService
) {
super()

this.removeApiServiceObserver = apiService.addEventObserver(async (eventName, data) => {
if (eventName === ApiServiceEvent.MetaReceived) {
/**
* All user data must be downloaded before we map features. Otherwise, feature mapping
* may think a component doesn't exist and create a new one, when in reality the component
* already exists but hasn't been downloaded yet.
*/
if (!this.syncService.completedOnlineDownloadFirstSync) {
return
}
const { userUuid, userRoles } = data as MetaReceivedData
await this.updateRolesAndFetchFeatures(
userUuid,
userRoles.map((role) => role.name),
)
}
})

this.removeWebSocketsServiceObserver = webSocketsService.addEventObserver(
async (eventName, data) => {
if (eventName === WebSocketsServiceEvent.UserRoleMessageReceived) {
Expand Down Expand Up @@ -148,6 +130,31 @@ export class SNFeaturesService
)
}

async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === ApiServiceEvent.MetaReceived) {
if (!this.syncService) {
this.log('[Features Service] Handling events interrupted. Sync service is not yet initialized.', event)

return
}

/**
* All user data must be downloaded before we map features. Otherwise, feature mapping
* may think a component doesn't exist and create a new one, when in reality the component
* already exists but hasn't been downloaded yet.
*/
if (!this.syncService.completedOnlineDownloadFirstSync) {
return
}

const { userUuid, userRoles } = event.payload as MetaReceivedData
await this.updateRolesAndFetchFeatures(
userUuid,
userRoles.map((role) => role.name),
)
}
}

async handleApplicationStage(stage: ApplicationStage): Promise<void> {
await super.handleApplicationStage(stage)
if (stage === ApplicationStage.FullSyncCompleted_13) {
Expand Down Expand Up @@ -670,8 +677,6 @@ export class SNFeaturesService
super.deinit()
this.removeSignInObserver()
;(this.removeSignInObserver as unknown) = undefined
this.removeApiServiceObserver()
;(this.removeApiServiceObserver as unknown) = undefined
this.removeWebSocketsServiceObserver()
;(this.removeWebSocketsServiceObserver as unknown) = undefined
this.removefeatureReposObserver()
Expand Down