fix: Fixes issue where lock screen would not use previously active theme (#2372)
This commit is contained in:
@@ -442,12 +442,8 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
|
||||
this.sockets.loadWebSocketUrl()
|
||||
|
||||
await this.sessions.initializeFromDisk()
|
||||
|
||||
this.settings.initializeFromDisk()
|
||||
|
||||
this.features.initializeFromDisk()
|
||||
|
||||
this.launched = true
|
||||
await this.notifyEvent(ApplicationEvent.Launched)
|
||||
await this.handleStage(ApplicationStage.Launched_10)
|
||||
@@ -1134,6 +1130,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SyncService), IntegrityEvent.IntegrityCheckCompleted)
|
||||
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.SubscriptionManager), SessionEvent.Restored)
|
||||
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SyncEvent.ReceivedSharedVaultInvites)
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SessionEvent.UserKeyPairChanged)
|
||||
@@ -1165,6 +1162,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
)
|
||||
}
|
||||
|
||||
this.events.addEventHandler(this.dependencies.get(TYPES.SessionManager), ApplicationEvent.ApplicationStageChanged)
|
||||
this.events.addEventHandler(
|
||||
this.dependencies.get(TYPES.SelfContactManager),
|
||||
ApplicationEvent.ApplicationStageChanged,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { ApplicationStage } from '@standardnotes/services'
|
||||
import { FeatureIdentifier } from '@standardnotes/features'
|
||||
import { Migration } from '@Lib/Migrations/Migration'
|
||||
import { ThemeInterface } from '@standardnotes/models'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
|
||||
const NoDistractionIdentifier = 'org.standardnotes.theme-no-distraction' as FeatureIdentifier
|
||||
const NoDistractionIdentifier = 'org.standardnotes.theme-no-distraction'
|
||||
|
||||
export class Migration2_42_0 extends Migration {
|
||||
static override version(): string {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FeaturesService } from '@Lib/Services/Features/FeaturesService'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { ContentType, Uuid } from '@standardnotes/domain-core'
|
||||
import {
|
||||
ActionObserver,
|
||||
PayloadEmitSource,
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
UIFeature,
|
||||
ComponentInterface,
|
||||
PrefKey,
|
||||
ThemeInterface,
|
||||
ComponentPreferencesEntry,
|
||||
AllComponentPreferences,
|
||||
SNNote,
|
||||
@@ -21,15 +20,14 @@ import {
|
||||
import {
|
||||
ComponentArea,
|
||||
FindNativeFeature,
|
||||
FeatureIdentifier,
|
||||
EditorFeatureDescription,
|
||||
FindNativeTheme,
|
||||
IframeComponentFeatureDescription,
|
||||
ComponentFeatureDescription,
|
||||
ThemeFeatureDescription,
|
||||
EditorIdentifier,
|
||||
GetIframeEditors,
|
||||
GetNativeThemes,
|
||||
NativeFeatureIdentifier,
|
||||
} from '@standardnotes/features'
|
||||
import { Copy, removeFromArray, sleep, isNotUndefined } from '@standardnotes/utils'
|
||||
import { ComponentViewer } from '@Lib/Services/ComponentManager/ComponentViewer'
|
||||
@@ -287,7 +285,7 @@ export class SNComponentManager
|
||||
const url = this.urlForFeature(feature)
|
||||
|
||||
if (url) {
|
||||
this.device.registerComponentUrl(feature.uniqueIdentifier, url)
|
||||
this.device.registerComponentUrl(feature.uniqueIdentifier.value, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,7 +370,7 @@ export class SNComponentManager
|
||||
return
|
||||
}
|
||||
|
||||
const featureStatus = this.features.getFeatureStatus(uiFeature.featureIdentifier)
|
||||
const featureStatus = this.features.getFeatureStatus(uiFeature.uniqueIdentifier)
|
||||
if (featureStatus !== FeatureStatus.Entitled) {
|
||||
return
|
||||
}
|
||||
@@ -398,28 +396,50 @@ export class SNComponentManager
|
||||
}
|
||||
|
||||
public getActiveThemes(): UIFeature<ThemeFeatureDescription>[] {
|
||||
const activeThemesIdentifiers = this.getActiveThemesIdentifiers()
|
||||
const { features, uuids } = this.getActiveThemesIdentifiers()
|
||||
|
||||
const thirdPartyThemes = this.items.findItems<ThemeInterface>(activeThemesIdentifiers).map((item) => {
|
||||
return new UIFeature<ThemeFeatureDescription>(item)
|
||||
})
|
||||
const thirdPartyThemes = uuids
|
||||
.map((uuid) => {
|
||||
const component = this.items.findItem<ComponentInterface>(uuid.value)
|
||||
if (component) {
|
||||
return new UIFeature<ThemeFeatureDescription>(component)
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
.filter(isNotUndefined)
|
||||
|
||||
const nativeThemes = activeThemesIdentifiers
|
||||
const nativeThemes = features
|
||||
.map((identifier) => {
|
||||
return FindNativeTheme(identifier as FeatureIdentifier)
|
||||
return FindNativeTheme(identifier.value)
|
||||
})
|
||||
.filter(isNotUndefined)
|
||||
.map((theme) => new UIFeature(theme))
|
||||
|
||||
const entitledThemes = [...thirdPartyThemes, ...nativeThemes].filter((theme) => {
|
||||
return this.features.getFeatureStatus(theme.featureIdentifier) === FeatureStatus.Entitled
|
||||
return this.features.getFeatureStatus(theme.uniqueIdentifier) === FeatureStatus.Entitled
|
||||
})
|
||||
|
||||
return entitledThemes
|
||||
}
|
||||
|
||||
public getActiveThemesIdentifiers(): string[] {
|
||||
return this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []
|
||||
public getActiveThemesIdentifiers(): { features: NativeFeatureIdentifier[]; uuids: Uuid[] } {
|
||||
const features: NativeFeatureIdentifier[] = []
|
||||
const uuids: Uuid[] = []
|
||||
|
||||
const strings = this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []
|
||||
for (const string of strings) {
|
||||
const nativeIdentifier = NativeFeatureIdentifier.create(string)
|
||||
if (!nativeIdentifier.isFailed()) {
|
||||
features.push(nativeIdentifier.getValue())
|
||||
}
|
||||
|
||||
const uuid = Uuid.create(string)
|
||||
if (!uuid.isFailed()) {
|
||||
uuids.push(uuid.getValue())
|
||||
}
|
||||
}
|
||||
|
||||
return { features, uuids }
|
||||
}
|
||||
|
||||
public async toggleComponent(component: ComponentInterface): Promise<void> {
|
||||
@@ -437,7 +457,7 @@ export class SNComponentManager
|
||||
return usecase.execute(note)
|
||||
}
|
||||
|
||||
getDefaultEditorIdentifier(currentTag?: SNTag): EditorIdentifier {
|
||||
getDefaultEditorIdentifier(currentTag?: SNTag): string {
|
||||
const usecase = new GetDefaultEditorIdentifier(this.preferences, this.items)
|
||||
return usecase.execute(currentTag).getValue()
|
||||
}
|
||||
@@ -468,7 +488,7 @@ export class SNComponentManager
|
||||
this.preferences.getValue(PrefKey.ComponentPreferences, undefined) ?? {},
|
||||
)
|
||||
|
||||
const preferencesLookupKey = uiFeature.uniqueIdentifier
|
||||
const preferencesLookupKey = uiFeature.uniqueIdentifier.value
|
||||
|
||||
mutablePreferencesValue[preferencesLookupKey] = preferences
|
||||
|
||||
@@ -482,7 +502,7 @@ export class SNComponentManager
|
||||
return undefined
|
||||
}
|
||||
|
||||
const preferencesLookupKey = component.uniqueIdentifier
|
||||
const preferencesLookupKey = component.uniqueIdentifier.value
|
||||
|
||||
return preferences[preferencesLookupKey]
|
||||
}
|
||||
@@ -490,31 +510,31 @@ export class SNComponentManager
|
||||
async addActiveTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void> {
|
||||
const activeThemes = (this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []).slice()
|
||||
|
||||
activeThemes.push(theme.uniqueIdentifier)
|
||||
activeThemes.push(theme.uniqueIdentifier.value)
|
||||
|
||||
await this.preferences.setValue(PrefKey.ActiveThemes, activeThemes)
|
||||
}
|
||||
|
||||
async replaceActiveTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void> {
|
||||
await this.preferences.setValue(PrefKey.ActiveThemes, [theme.uniqueIdentifier])
|
||||
await this.preferences.setValue(PrefKey.ActiveThemes, [theme.uniqueIdentifier.value])
|
||||
}
|
||||
|
||||
async removeActiveTheme(theme: UIFeature<ThemeFeatureDescription>): Promise<void> {
|
||||
const activeThemes = this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []
|
||||
|
||||
const filteredThemes = activeThemes.filter((activeTheme) => activeTheme !== theme.uniqueIdentifier)
|
||||
const filteredThemes = activeThemes.filter((activeTheme) => activeTheme !== theme.uniqueIdentifier.value)
|
||||
|
||||
await this.preferences.setValue(PrefKey.ActiveThemes, filteredThemes)
|
||||
}
|
||||
|
||||
isThemeActive(theme: UIFeature<ThemeFeatureDescription>): boolean {
|
||||
if (this.features.getFeatureStatus(theme.featureIdentifier) !== FeatureStatus.Entitled) {
|
||||
if (this.features.getFeatureStatus(theme.uniqueIdentifier) !== FeatureStatus.Entitled) {
|
||||
return false
|
||||
}
|
||||
|
||||
const activeThemes = this.preferences.getValue(PrefKey.ActiveThemes, undefined) ?? []
|
||||
|
||||
return activeThemes.includes(theme.uniqueIdentifier)
|
||||
return activeThemes.includes(theme.uniqueIdentifier.value)
|
||||
}
|
||||
|
||||
async addActiveComponent(component: ComponentInterface): Promise<void> {
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
ComponentPermission,
|
||||
ComponentArea,
|
||||
IframeComponentFeatureDescription,
|
||||
NativeFeatureIdentifier,
|
||||
} from '@standardnotes/features'
|
||||
import {
|
||||
isString,
|
||||
@@ -72,7 +73,7 @@ import {
|
||||
isNotUndefined,
|
||||
uniqueArray,
|
||||
} from '@standardnotes/utils'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { ContentType, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export class ComponentViewer implements ComponentViewerInterface {
|
||||
private streamItems?: string[]
|
||||
@@ -214,12 +215,12 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
this.readonly = readonly
|
||||
}
|
||||
|
||||
get componentUniqueIdentifier(): string {
|
||||
get componentUniqueIdentifier(): NativeFeatureIdentifier | Uuid {
|
||||
return this.componentOrFeature.uniqueIdentifier
|
||||
}
|
||||
|
||||
public getFeatureStatus(): FeatureStatus {
|
||||
return this.services.features.getFeatureStatus(this.componentOrFeature.featureIdentifier, {
|
||||
return this.services.features.getFeatureStatus(this.componentUniqueIdentifier, {
|
||||
inContextOfItem: this.getContextItem(),
|
||||
})
|
||||
}
|
||||
@@ -269,7 +270,9 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
return
|
||||
}
|
||||
|
||||
const updatedComponent = items.find((item) => item.uuid === this.componentUniqueIdentifier) as ComponentInterface
|
||||
const updatedComponent = items.find(
|
||||
(item) => item.uuid === this.componentUniqueIdentifier.value,
|
||||
) as ComponentInterface
|
||||
if (!updatedComponent) {
|
||||
return
|
||||
}
|
||||
@@ -289,7 +292,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
|
||||
this.updateOurComponentRefFromChangedItems(nondeletedItems)
|
||||
|
||||
const areWeOriginator = sourceKey && sourceKey === this.componentUniqueIdentifier
|
||||
const areWeOriginator = sourceKey && sourceKey === this.componentUniqueIdentifier.value
|
||||
if (areWeOriginator) {
|
||||
return
|
||||
}
|
||||
@@ -326,7 +329,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
]
|
||||
|
||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||
this.componentUniqueIdentifier,
|
||||
this.componentUniqueIdentifier.value,
|
||||
requiredPermissions,
|
||||
() => {
|
||||
this.sendItemsInReply(items, this.streamItemsOriginalMessage as ComponentMessage)
|
||||
@@ -341,7 +344,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
},
|
||||
] as ComponentPermission[]
|
||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||
this.componentUniqueIdentifier,
|
||||
this.componentUniqueIdentifier.value,
|
||||
requiredContextPermissions,
|
||||
() => {
|
||||
this.log(
|
||||
@@ -414,7 +417,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
|
||||
private getClientData(item: DecryptedItemInterface): Record<string, unknown> {
|
||||
const globalComponentData = item.getDomainData(ComponentDataDomain) || {}
|
||||
const thisComponentData = globalComponentData[this.componentUniqueIdentifier] || {}
|
||||
const thisComponentData = globalComponentData[this.componentUniqueIdentifier.value] || {}
|
||||
return thisComponentData as Record<string, unknown>
|
||||
}
|
||||
|
||||
@@ -530,7 +533,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
sessionKey: this.sessionKey,
|
||||
componentData: componentData,
|
||||
data: {
|
||||
uuid: this.componentUniqueIdentifier,
|
||||
uuid: this.componentUniqueIdentifier.value,
|
||||
environment: environmentToString(this.config.environment),
|
||||
platform: platformToString(this.config.platform),
|
||||
activeThemeUrls: this.config.componentManagerFunctions.urlsForActiveThemes(),
|
||||
@@ -602,7 +605,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
},
|
||||
]
|
||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||
this.componentUniqueIdentifier,
|
||||
this.componentUniqueIdentifier.value,
|
||||
requiredPermissions,
|
||||
() => {
|
||||
if (!this.streamItems) {
|
||||
@@ -627,7 +630,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
]
|
||||
|
||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||
this.componentUniqueIdentifier,
|
||||
this.componentUniqueIdentifier.value,
|
||||
requiredPermissions,
|
||||
() => {
|
||||
if (!this.streamContextItemOriginalMessage) {
|
||||
@@ -684,7 +687,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
}
|
||||
|
||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||
this.componentUniqueIdentifier,
|
||||
this.componentUniqueIdentifier.value,
|
||||
requiredPermissions,
|
||||
|
||||
async () => {
|
||||
@@ -766,13 +769,13 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
const allComponentData = Copy<Record<string, unknown>>(
|
||||
mutator.getItem().getDomainData(ComponentDataDomain) || {},
|
||||
)
|
||||
allComponentData[this.componentUniqueIdentifier] = responseItem.clientData
|
||||
allComponentData[this.componentUniqueIdentifier.value] = responseItem.clientData
|
||||
mutator.setDomainData(allComponentData, ComponentDataDomain)
|
||||
}
|
||||
},
|
||||
MutationType.UpdateUserTimestamps,
|
||||
PayloadEmitSource.ComponentRetrieved,
|
||||
this.componentUniqueIdentifier,
|
||||
this.componentUniqueIdentifier.value,
|
||||
)
|
||||
|
||||
this.services.sync
|
||||
@@ -807,7 +810,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
]
|
||||
|
||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||
this.componentUniqueIdentifier,
|
||||
this.componentUniqueIdentifier.value,
|
||||
requiredPermissions,
|
||||
async () => {
|
||||
responseItems = this.responseItemsByRemovingPrivateProperties(responseItems)
|
||||
@@ -834,13 +837,13 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
const allComponentClientData = Copy<Record<string, unknown>>(
|
||||
item.getDomainData(ComponentDataDomain) || {},
|
||||
)
|
||||
allComponentClientData[this.componentUniqueIdentifier] = responseItem.clientData
|
||||
allComponentClientData[this.componentUniqueIdentifier.value] = responseItem.clientData
|
||||
mutator.setDomainData(allComponentClientData, ComponentDataDomain)
|
||||
}
|
||||
},
|
||||
MutationType.UpdateUserTimestamps,
|
||||
PayloadEmitSource.ComponentCreated,
|
||||
this.componentUniqueIdentifier,
|
||||
this.componentUniqueIdentifier.value,
|
||||
)
|
||||
processedItems.push(item)
|
||||
}
|
||||
@@ -874,7 +877,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
]
|
||||
|
||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||
this.componentUniqueIdentifier,
|
||||
this.componentUniqueIdentifier.value,
|
||||
requiredPermissions,
|
||||
async () => {
|
||||
const itemsData = items
|
||||
@@ -911,7 +914,7 @@ export class ComponentViewer implements ComponentViewerInterface {
|
||||
private handleSetComponentPreferencesMessage(message: ComponentMessage): void {
|
||||
const noPermissionsRequired: ComponentPermission[] = []
|
||||
this.config.componentManagerFunctions.runWithPermissionsUseCase.execute(
|
||||
this.componentUniqueIdentifier,
|
||||
this.componentUniqueIdentifier.value,
|
||||
noPermissionsRequired,
|
||||
async () => {
|
||||
const newPreferences = <ComponentPreferencesEntry | undefined>message.data.componentData
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ComponentArea, ComponentAction, FeatureIdentifier, LegacyFileSafeIdentifier } from '@standardnotes/features'
|
||||
import { ComponentArea, ComponentAction, NativeFeatureIdentifier } from '@standardnotes/features'
|
||||
import { ComponentMessage, MessageData, OutgoingItemMessagePayload } from '@standardnotes/models'
|
||||
import { UuidString } from '@Lib/Types/UuidString'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
@@ -17,9 +17,9 @@ export type Writeable<T> = { -readonly [P in keyof T]: T[P] }
|
||||
* Extensions allowed to batch stream AllowedBatchContentTypes
|
||||
*/
|
||||
export const AllowedBatchStreaming = Object.freeze([
|
||||
LegacyFileSafeIdentifier,
|
||||
FeatureIdentifier.DeprecatedFileSafe,
|
||||
FeatureIdentifier.DeprecatedBoldEditor,
|
||||
NativeFeatureIdentifier.TYPES.LegacyFileSafeIdentifier,
|
||||
NativeFeatureIdentifier.TYPES.DeprecatedFileSafe,
|
||||
NativeFeatureIdentifier.TYPES.DeprecatedBoldEditor,
|
||||
])
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
FeatureIdentifier,
|
||||
NativeFeatureIdentifier,
|
||||
FindNativeFeature,
|
||||
IframeComponentFeatureDescription,
|
||||
UIFeatureDescriptionTypes,
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { DoesEditorChangeRequireAlertUseCase } from './DoesEditorChangeRequireAlert'
|
||||
import { UIFeature } from '@standardnotes/models'
|
||||
|
||||
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: FeatureIdentifier) => {
|
||||
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: string) => {
|
||||
return new UIFeature(FindNativeFeature<F>(identifier)!)
|
||||
}
|
||||
|
||||
@@ -19,57 +19,77 @@ describe('editor change alert', () => {
|
||||
})
|
||||
|
||||
it('should not require alert switching from plain editor', () => {
|
||||
const component = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.MarkdownProEditor)!
|
||||
const component = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||
)!
|
||||
const requiresAlert = usecase.execute(undefined, component)
|
||||
expect(requiresAlert).toBe(false)
|
||||
})
|
||||
|
||||
it('should not require alert switching to plain editor', () => {
|
||||
const component = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.MarkdownProEditor)!
|
||||
const component = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||
)!
|
||||
const requiresAlert = usecase.execute(component, undefined)
|
||||
expect(requiresAlert).toBe(false)
|
||||
})
|
||||
|
||||
it('should not require alert switching from a markdown editor', () => {
|
||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.PlusEditor)!
|
||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||
)!
|
||||
const markdownEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
FeatureIdentifier.MarkdownProEditor,
|
||||
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||
)
|
||||
const requiresAlert = usecase.execute(markdownEditor, htmlEditor)
|
||||
expect(requiresAlert).toBe(false)
|
||||
})
|
||||
|
||||
it('should not require alert switching to a markdown editor', () => {
|
||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.PlusEditor)!
|
||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||
)!
|
||||
const markdownEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
FeatureIdentifier.MarkdownProEditor,
|
||||
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||
)
|
||||
const requiresAlert = usecase.execute(htmlEditor, markdownEditor)
|
||||
expect(requiresAlert).toBe(false)
|
||||
})
|
||||
|
||||
it('should not require alert switching from & to a html editor', () => {
|
||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.PlusEditor)!
|
||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||
)!
|
||||
const requiresAlert = usecase.execute(htmlEditor, htmlEditor)
|
||||
expect(requiresAlert).toBe(false)
|
||||
})
|
||||
|
||||
it('should require alert switching from a html editor to custom editor', () => {
|
||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.PlusEditor)!
|
||||
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.TokenVaultEditor)
|
||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||
)!
|
||||
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.TokenVaultEditor,
|
||||
)
|
||||
const requiresAlert = usecase.execute(htmlEditor, customEditor)
|
||||
expect(requiresAlert).toBe(true)
|
||||
})
|
||||
|
||||
it('should require alert switching from a custom editor to html editor', () => {
|
||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.PlusEditor)!
|
||||
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.TokenVaultEditor)
|
||||
const htmlEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.PlusEditor,
|
||||
)!
|
||||
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.TokenVaultEditor,
|
||||
)
|
||||
const requiresAlert = usecase.execute(customEditor, htmlEditor)
|
||||
expect(requiresAlert).toBe(true)
|
||||
})
|
||||
|
||||
it('should require alert switching from a custom editor to custom editor', () => {
|
||||
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.TokenVaultEditor)
|
||||
const customEditor = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.TokenVaultEditor,
|
||||
)
|
||||
const requiresAlert = usecase.execute(customEditor, customEditor)
|
||||
expect(requiresAlert).toBe(true)
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createNote } from '@Lib/Spec/SpecUtils'
|
||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||
import { NativeFeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||
import { EditorForNoteUseCase } from './EditorForNote'
|
||||
import { ItemManagerInterface } from '@standardnotes/services'
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('EditorForNote', () => {
|
||||
noteType: NoteType.Plain,
|
||||
})
|
||||
|
||||
expect(usecase.execute(note).featureIdentifier).toBe(FeatureIdentifier.PlainEditor)
|
||||
expect(usecase.execute(note).featureIdentifier).toBe(NativeFeatureIdentifier.TYPES.PlainEditor)
|
||||
})
|
||||
|
||||
it('getEditorForNote should call legacy function if no note editorIdentifier or noteType', () => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
ComponentArea,
|
||||
EditorFeatureDescription,
|
||||
FeatureIdentifier,
|
||||
FindNativeFeature,
|
||||
GetIframeAndNativeEditors,
|
||||
GetPlainNoteFeature,
|
||||
@@ -47,11 +46,9 @@ export class EditorForNoteUseCase {
|
||||
}
|
||||
|
||||
private componentOrNativeFeatureForIdentifier(
|
||||
identifier: FeatureIdentifier | string,
|
||||
identifier: string,
|
||||
): UIFeature<EditorFeatureDescription | IframeComponentFeatureDescription> | undefined {
|
||||
const nativeFeature = FindNativeFeature<EditorFeatureDescription | IframeComponentFeatureDescription>(
|
||||
identifier as FeatureIdentifier,
|
||||
)
|
||||
const nativeFeature = FindNativeFeature<EditorFeatureDescription | IframeComponentFeatureDescription>(identifier)
|
||||
if (nativeFeature) {
|
||||
return new UIFeature(nativeFeature)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ItemManagerInterface, PreferenceServiceInterface } from '@standardnotes/services'
|
||||
import { GetDefaultEditorIdentifier } from './GetDefaultEditorIdentifier'
|
||||
import { ComponentArea, FeatureIdentifier } from '@standardnotes/features'
|
||||
import { ComponentArea, NativeFeatureIdentifier } from '@standardnotes/features'
|
||||
import { SNComponent, SNTag } from '@standardnotes/models'
|
||||
|
||||
describe('getDefaultEditorIdentifier', () => {
|
||||
@@ -21,33 +21,33 @@ describe('getDefaultEditorIdentifier', () => {
|
||||
it('should return plain editor if no default tag editor or component editor', () => {
|
||||
const editorIdentifier = usecase.execute().getValue()
|
||||
|
||||
expect(editorIdentifier).toEqual(FeatureIdentifier.PlainEditor)
|
||||
expect(editorIdentifier).toEqual(NativeFeatureIdentifier.TYPES.PlainEditor)
|
||||
})
|
||||
|
||||
it('should return pref key based value if available', () => {
|
||||
preferences.getValue = jest.fn().mockReturnValue(FeatureIdentifier.SuperEditor)
|
||||
preferences.getValue = jest.fn().mockReturnValue(NativeFeatureIdentifier.TYPES.SuperEditor)
|
||||
|
||||
const editorIdentifier = usecase.execute().getValue()
|
||||
|
||||
expect(editorIdentifier).toEqual(FeatureIdentifier.SuperEditor)
|
||||
expect(editorIdentifier).toEqual(NativeFeatureIdentifier.TYPES.SuperEditor)
|
||||
})
|
||||
|
||||
it('should return default tag identifier if tag supplied', () => {
|
||||
const tag = {
|
||||
preferences: {
|
||||
editorIdentifier: FeatureIdentifier.SuperEditor,
|
||||
editorIdentifier: NativeFeatureIdentifier.TYPES.SuperEditor,
|
||||
},
|
||||
} as jest.Mocked<SNTag>
|
||||
|
||||
const editorIdentifier = usecase.execute(tag).getValue()
|
||||
|
||||
expect(editorIdentifier).toEqual(FeatureIdentifier.SuperEditor)
|
||||
expect(editorIdentifier).toEqual(NativeFeatureIdentifier.TYPES.SuperEditor)
|
||||
})
|
||||
|
||||
it('should return legacy editor identifier', () => {
|
||||
const editor = {
|
||||
legacyIsDefaultEditor: jest.fn().mockReturnValue(true),
|
||||
identifier: FeatureIdentifier.MarkdownProEditor,
|
||||
identifier: NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||
area: ComponentArea.Editor,
|
||||
} as unknown as jest.Mocked<SNComponent>
|
||||
|
||||
@@ -55,6 +55,6 @@ describe('getDefaultEditorIdentifier', () => {
|
||||
|
||||
const editorIdentifier = usecase.execute().getValue()
|
||||
|
||||
expect(editorIdentifier).toEqual(FeatureIdentifier.MarkdownProEditor)
|
||||
expect(editorIdentifier).toEqual(NativeFeatureIdentifier.TYPES.MarkdownProEditor)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { ComponentArea, EditorIdentifier, FeatureIdentifier } from '@standardnotes/features'
|
||||
import { ComponentArea, NativeFeatureIdentifier } from '@standardnotes/features'
|
||||
import { ComponentInterface, PrefKey, SNTag } from '@standardnotes/models'
|
||||
import { ItemManagerInterface, PreferenceServiceInterface } from '@standardnotes/services'
|
||||
|
||||
export class GetDefaultEditorIdentifier implements SyncUseCaseInterface<EditorIdentifier> {
|
||||
export class GetDefaultEditorIdentifier implements SyncUseCaseInterface<string> {
|
||||
constructor(private preferences: PreferenceServiceInterface, private items: ItemManagerInterface) {}
|
||||
|
||||
execute(currentTag?: SNTag): Result<EditorIdentifier> {
|
||||
execute(currentTag?: SNTag): Result<string> {
|
||||
if (currentTag) {
|
||||
const editorIdentifier = currentTag?.preferences?.editorIdentifier
|
||||
if (editorIdentifier) {
|
||||
@@ -25,7 +25,7 @@ export class GetDefaultEditorIdentifier implements SyncUseCaseInterface<EditorId
|
||||
return Result.ok(matchingEditor.identifier)
|
||||
}
|
||||
|
||||
return Result.ok(FeatureIdentifier.PlainEditor)
|
||||
return Result.ok(NativeFeatureIdentifier.TYPES.PlainEditor)
|
||||
}
|
||||
|
||||
thirdPartyComponentsForArea(area: ComponentArea): ComponentInterface[] {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import {
|
||||
FeatureIdentifier,
|
||||
NativeFeatureIdentifier,
|
||||
FindNativeFeature,
|
||||
IframeComponentFeatureDescription,
|
||||
UIFeatureDescriptionTypes,
|
||||
@@ -21,7 +21,7 @@ import { GetFeatureUrl } from './GetFeatureUrl'
|
||||
|
||||
const desktopExtHost = 'http://localhost:123'
|
||||
|
||||
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: FeatureIdentifier) => {
|
||||
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: string) => {
|
||||
return new UIFeature(FindNativeFeature<F>(identifier)!)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ const thirdPartyFeature = () => {
|
||||
local_url: 'sn://Extensions/non-native-identifier/dist/index.html',
|
||||
hosted_url: 'https://example.com/component',
|
||||
package_info: {
|
||||
identifier: 'non-native-identifier' as FeatureIdentifier,
|
||||
identifier: 'non-native-identifier',
|
||||
expires_at: new Date().getTime(),
|
||||
availableInRoles: [],
|
||||
} as unknown as jest.Mocked<ComponentPackageInfo>,
|
||||
@@ -75,7 +75,9 @@ describe('GetFeatureUrl', () => {
|
||||
})
|
||||
|
||||
it('returns native path for native component', () => {
|
||||
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.MarkdownProEditor)!
|
||||
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||
)!
|
||||
const url = usecase.execute(feature)
|
||||
expect(url).toEqual(
|
||||
`${desktopExtHost}/components/${feature.featureIdentifier}/${feature.asFeatureDescription.index_path}`,
|
||||
@@ -84,7 +86,7 @@ describe('GetFeatureUrl', () => {
|
||||
|
||||
it('returns native path for deprecated native component', () => {
|
||||
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
FeatureIdentifier.DeprecatedBoldEditor,
|
||||
NativeFeatureIdentifier.TYPES.DeprecatedBoldEditor,
|
||||
)!
|
||||
const url = usecase.execute(feature)
|
||||
expect(url).toEqual(
|
||||
@@ -122,7 +124,9 @@ describe('GetFeatureUrl', () => {
|
||||
})
|
||||
|
||||
it('returns native path for native feature', () => {
|
||||
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(FeatureIdentifier.MarkdownProEditor)
|
||||
const feature = nativeFeatureAsUIFeature<IframeComponentFeatureDescription>(
|
||||
NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||
)
|
||||
const url = usecase.execute(feature)
|
||||
expect(url).toEqual(
|
||||
`http://localhost/components/assets/${feature.featureIdentifier}/${feature.asFeatureDescription.index_path}`,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ContentType } from '@standardnotes/domain-core'
|
||||
import {
|
||||
ComponentAction,
|
||||
ComponentPermission,
|
||||
FeatureIdentifier,
|
||||
NativeFeatureIdentifier,
|
||||
FindNativeFeature,
|
||||
UIFeatureDescriptionTypes,
|
||||
} from '@standardnotes/features'
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
SyncServiceInterface,
|
||||
} from '@standardnotes/services'
|
||||
|
||||
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: FeatureIdentifier) => {
|
||||
const nativeFeatureAsUIFeature = <F extends UIFeatureDescriptionTypes>(identifier: string) => {
|
||||
return new UIFeature(FindNativeFeature<F>(identifier)!)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('RunWithPermissionsUseCase', () => {
|
||||
|
||||
expect(
|
||||
usecase.areRequestedPermissionsValid(
|
||||
nativeFeatureAsUIFeature(FeatureIdentifier.MarkdownProEditor),
|
||||
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.MarkdownProEditor),
|
||||
permissions,
|
||||
),
|
||||
).toEqual(true)
|
||||
@@ -59,7 +59,7 @@ describe('RunWithPermissionsUseCase', () => {
|
||||
|
||||
expect(
|
||||
usecase.areRequestedPermissionsValid(
|
||||
nativeFeatureAsUIFeature(FeatureIdentifier.MarkdownProEditor),
|
||||
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.MarkdownProEditor),
|
||||
permissions,
|
||||
),
|
||||
).toEqual(false)
|
||||
@@ -75,7 +75,7 @@ describe('RunWithPermissionsUseCase', () => {
|
||||
|
||||
expect(
|
||||
usecase.areRequestedPermissionsValid(
|
||||
nativeFeatureAsUIFeature(FeatureIdentifier.MarkdownProEditor),
|
||||
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.MarkdownProEditor),
|
||||
permissions,
|
||||
),
|
||||
).toEqual(false)
|
||||
@@ -91,7 +91,7 @@ describe('RunWithPermissionsUseCase', () => {
|
||||
|
||||
expect(
|
||||
usecase.areRequestedPermissionsValid(
|
||||
nativeFeatureAsUIFeature(FeatureIdentifier.MarkdownProEditor),
|
||||
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.MarkdownProEditor),
|
||||
permissions,
|
||||
),
|
||||
).toEqual(false)
|
||||
@@ -107,7 +107,7 @@ describe('RunWithPermissionsUseCase', () => {
|
||||
|
||||
expect(
|
||||
usecase.areRequestedPermissionsValid(
|
||||
nativeFeatureAsUIFeature(FeatureIdentifier.DeprecatedFileSafe),
|
||||
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.DeprecatedFileSafe),
|
||||
permissions,
|
||||
),
|
||||
).toEqual(false)
|
||||
@@ -127,7 +127,7 @@ describe('RunWithPermissionsUseCase', () => {
|
||||
|
||||
expect(
|
||||
usecase.areRequestedPermissionsValid(
|
||||
nativeFeatureAsUIFeature(FeatureIdentifier.DeprecatedFileSafe),
|
||||
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.DeprecatedFileSafe),
|
||||
permissions,
|
||||
),
|
||||
).toEqual(true)
|
||||
@@ -147,7 +147,7 @@ describe('RunWithPermissionsUseCase', () => {
|
||||
|
||||
expect(
|
||||
usecase.areRequestedPermissionsValid(
|
||||
nativeFeatureAsUIFeature(FeatureIdentifier.DeprecatedBoldEditor),
|
||||
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.DeprecatedBoldEditor),
|
||||
permissions,
|
||||
),
|
||||
).toEqual(true)
|
||||
@@ -166,7 +166,10 @@ describe('RunWithPermissionsUseCase', () => {
|
||||
]
|
||||
|
||||
expect(
|
||||
usecase.areRequestedPermissionsValid(nativeFeatureAsUIFeature(FeatureIdentifier.PlusEditor), permissions),
|
||||
usecase.areRequestedPermissionsValid(
|
||||
nativeFeatureAsUIFeature(NativeFeatureIdentifier.TYPES.PlusEditor),
|
||||
permissions,
|
||||
),
|
||||
).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
ComponentAction,
|
||||
ComponentFeatureDescription,
|
||||
ComponentPermission,
|
||||
FeatureIdentifier,
|
||||
FindNativeFeature,
|
||||
} from '@standardnotes/features'
|
||||
import { ComponentInterface, ComponentMutator, PermissionDialog, UIFeature } from '@standardnotes/models'
|
||||
@@ -228,7 +227,7 @@ export class RunWithPermissionsUseCase {
|
||||
}
|
||||
|
||||
private findUIFeature(identifier: string): UIFeature<ComponentFeatureDescription> | undefined {
|
||||
const nativeFeature = FindNativeFeature<ComponentFeatureDescription>(identifier as FeatureIdentifier)
|
||||
const nativeFeature = FindNativeFeature<ComponentFeatureDescription>(identifier)
|
||||
if (nativeFeature) {
|
||||
return new UIFeature(nativeFeature)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ItemInterface, SNFeatureRepo } from '@standardnotes/models'
|
||||
import { SyncService } from '../Sync/SyncService'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { FeaturesService } from '@Lib/Services/Features'
|
||||
import { RoleName, ContentType } from '@standardnotes/domain-core'
|
||||
import { FeatureIdentifier, GetFeatures } from '@standardnotes/features'
|
||||
import { RoleName, ContentType, Uuid } from '@standardnotes/domain-core'
|
||||
import { NativeFeatureIdentifier, GetFeatures } from '@standardnotes/features'
|
||||
import { WebSocketsService } from '../Api/WebsocketsService'
|
||||
import { SettingsService } from '../Settings'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
@@ -44,6 +44,7 @@ describe('FeaturesService', () => {
|
||||
let roles: string[]
|
||||
let items: ItemInterface[]
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
let featureService: FeaturesService
|
||||
|
||||
const createService = () => {
|
||||
return new FeaturesService(
|
||||
@@ -118,23 +119,81 @@ describe('FeaturesService', () => {
|
||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
internalEventBus.publish = jest.fn()
|
||||
internalEventBus.addEventHandler = jest.fn()
|
||||
|
||||
featureService = new FeaturesService(
|
||||
storageService,
|
||||
itemManager,
|
||||
mutator,
|
||||
subscriptions,
|
||||
apiService,
|
||||
webSocketsService,
|
||||
settingsService,
|
||||
userService,
|
||||
syncService,
|
||||
alertService,
|
||||
sessionManager,
|
||||
crypto,
|
||||
internalEventBus,
|
||||
)
|
||||
})
|
||||
|
||||
describe('experimental features', () => {
|
||||
it('enables/disables an experimental feature', async () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(GetFeatures())
|
||||
|
||||
const featuresService = createService()
|
||||
featuresService.getExperimentalFeatures = jest.fn().mockReturnValue([FeatureIdentifier.PlusEditor])
|
||||
featuresService.initializeFromDisk()
|
||||
featureService.getExperimentalFeatures = jest.fn().mockReturnValue([NativeFeatureIdentifier.TYPES.PlusEditor])
|
||||
featureService.initializeFromDisk()
|
||||
|
||||
featuresService.enableExperimentalFeature(FeatureIdentifier.PlusEditor)
|
||||
featureService.enableExperimentalFeature(NativeFeatureIdentifier.TYPES.PlusEditor)
|
||||
|
||||
expect(featuresService.isExperimentalFeatureEnabled(FeatureIdentifier.PlusEditor)).toEqual(true)
|
||||
expect(featureService.isExperimentalFeatureEnabled(NativeFeatureIdentifier.TYPES.PlusEditor)).toEqual(true)
|
||||
|
||||
featuresService.disableExperimentalFeature(FeatureIdentifier.PlusEditor)
|
||||
featureService.disableExperimentalFeature(NativeFeatureIdentifier.TYPES.PlusEditor)
|
||||
|
||||
expect(featuresService.isExperimentalFeatureEnabled(FeatureIdentifier.PlusEditor)).toEqual(false)
|
||||
expect(featureService.isExperimentalFeatureEnabled(NativeFeatureIdentifier.TYPES.PlusEditor)).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasFirstPartyOnlineSubscription', () => {
|
||||
it('should be true if signed into first party server and has online subscription', () => {
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(featureService.hasFirstPartyOnlineSubscription()).toEqual(true)
|
||||
})
|
||||
|
||||
it('should not be true if not signed into first party server', () => {
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(featureService.hasFirstPartyOnlineSubscription()).toEqual(false)
|
||||
})
|
||||
|
||||
it('should not be true if no online subscription', () => {
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(false)
|
||||
|
||||
expect(featureService.hasFirstPartyOnlineSubscription()).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasPaidAnyPartyOnlineOrOfflineSubscription', () => {
|
||||
it('should return true if onlineRolesIncludePaidSubscription', () => {
|
||||
featureService.onlineRolesIncludePaidSubscription = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||
})
|
||||
|
||||
it('should return true if hasOfflineRepo', () => {
|
||||
featureService.hasOfflineRepo = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||
})
|
||||
|
||||
it('should return true if hasFirstPartyOnlineSubscription', () => {
|
||||
featureService.hasFirstPartyOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -148,40 +207,40 @@ describe('FeaturesService', () => {
|
||||
describe('updateRoles()', () => {
|
||||
it('setRoles should notify event if roles changed', async () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
|
||||
const mock = (featuresService['notifyEvent'] = jest.fn())
|
||||
featureService.initializeFromDisk()
|
||||
|
||||
const mock = (featureService['notifyEvent'] = jest.fn())
|
||||
|
||||
const newRoles = [...roles, RoleName.NAMES.PlusUser]
|
||||
featuresService.setOnlineRoles(newRoles)
|
||||
featureService.setOnlineRoles(newRoles)
|
||||
|
||||
expect(mock.mock.calls[0][0]).toEqual(FeaturesEvent.UserRolesChanged)
|
||||
})
|
||||
|
||||
it('should notify of subscription purchase', async () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
|
||||
const spy = jest.spyOn(featuresService, 'notifyEvent' as never)
|
||||
featureService.initializeFromDisk()
|
||||
|
||||
const spy = jest.spyOn(featureService, 'notifyEvent' as never)
|
||||
|
||||
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
||||
await featuresService.updateOnlineRolesWithNewValues(newRoles)
|
||||
await featureService.updateOnlineRolesWithNewValues(newRoles)
|
||||
|
||||
expect(spy.mock.calls[1][0]).toEqual(FeaturesEvent.DidPurchaseSubscription)
|
||||
})
|
||||
|
||||
it('should not notify of subscription purchase on initial roles load after sign in', async () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
featuresService['onlineRoles'] = []
|
||||
|
||||
const spy = jest.spyOn(featuresService, 'notifyEvent' as never)
|
||||
featureService.initializeFromDisk()
|
||||
featureService['onlineRoles'] = []
|
||||
|
||||
const spy = jest.spyOn(featureService, 'notifyEvent' as never)
|
||||
|
||||
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
||||
await featuresService.updateOnlineRolesWithNewValues(newRoles)
|
||||
await featureService.updateOnlineRolesWithNewValues(newRoles)
|
||||
|
||||
const triggeredEvents = spy.mock.calls.map((call) => call[0])
|
||||
expect(triggeredEvents).not.toContain(FeaturesEvent.DidPurchaseSubscription)
|
||||
@@ -189,11 +248,11 @@ describe('FeaturesService', () => {
|
||||
|
||||
it('saves new roles to storage if a role has been added', async () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
|
||||
featureService.initializeFromDisk()
|
||||
|
||||
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
||||
await featuresService.updateOnlineRolesWithNewValues(newRoles)
|
||||
await featureService.updateOnlineRolesWithNewValues(newRoles)
|
||||
expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.UserRoles, newRoles)
|
||||
})
|
||||
|
||||
@@ -201,134 +260,162 @@ describe('FeaturesService', () => {
|
||||
const newRoles = [RoleName.NAMES.CoreUser]
|
||||
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateOnlineRolesWithNewValues(newRoles)
|
||||
|
||||
featureService.initializeFromDisk()
|
||||
await featureService.updateOnlineRolesWithNewValues(newRoles)
|
||||
|
||||
expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.UserRoles, newRoles)
|
||||
})
|
||||
|
||||
it('role-based feature status', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SuperEditor)).toBe(FeatureStatus.Entitled)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.MidnightTheme).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.Entitled)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SuperEditor).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.Entitled)
|
||||
})
|
||||
|
||||
it('feature status with no paid role', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(false)
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.NoUserSubscription)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.PlusEditor)).toBe(FeatureStatus.NoUserSubscription)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SheetsEditor)).toBe(FeatureStatus.NoUserSubscription)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.MidnightTheme).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.NoUserSubscription)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.PlusEditor).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.NoUserSubscription)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SheetsEditor).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.NoUserSubscription)
|
||||
})
|
||||
|
||||
it('role-based features while not signed into first party server', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SuperEditor)).toBe(FeatureStatus.NoUserSubscription)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SuperEditor).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.NoUserSubscription)
|
||||
})
|
||||
|
||||
it('third party feature status', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
itemManager.getDisplayableComponents = jest
|
||||
.fn()
|
||||
.mockReturnValue([{ identifier: 'third-party-theme' }, { identifier: 'third-party-editor', isExpired: true }])
|
||||
.mockReturnValue([
|
||||
{ uuid: '00000000-0000-0000-0000-000000000001' },
|
||||
{ uuid: '00000000-0000-0000-0000-000000000002', isExpired: true },
|
||||
])
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus('third-party-theme' as FeatureIdentifier)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus('third-party-editor' as FeatureIdentifier)).toBe(
|
||||
expect(featureService.getFeatureStatus(Uuid.create('00000000-0000-0000-0000-000000000001').getValue())).toBe(
|
||||
FeatureStatus.Entitled,
|
||||
)
|
||||
expect(featureService.getFeatureStatus(Uuid.create('00000000-0000-0000-0000-000000000002').getValue())).toBe(
|
||||
FeatureStatus.InCurrentPlanButExpired,
|
||||
)
|
||||
expect(featuresService.getFeatureStatus('missing-feature-identifier' as FeatureIdentifier)).toBe(
|
||||
expect(featureService.getFeatureStatus(Uuid.create('00000000-0000-0000-0000-000000000003').getValue())).toBe(
|
||||
FeatureStatus.NoUserSubscription,
|
||||
)
|
||||
})
|
||||
|
||||
it('feature status should be not entitled if no account or offline repo', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.NoUserSubscription)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(
|
||||
FeatureStatus.NoUserSubscription,
|
||||
)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.MidnightTheme).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.NoUserSubscription)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.TokenVaultEditor).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.NoUserSubscription)
|
||||
})
|
||||
|
||||
it('feature status for offline subscription', async () => {
|
||||
const featuresService = createService()
|
||||
featureService.hasFirstPartyOfflineSubscription = jest.fn().mockReturnValue(true)
|
||||
featureService.setOfflineRoles([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
featuresService.hasFirstPartyOfflineSubscription = jest.fn().mockReturnValue(true)
|
||||
featuresService.setOfflineRoles([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(FeatureStatus.Entitled)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.MidnightTheme).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.Entitled)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.TokenVaultEditor).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.Entitled)
|
||||
})
|
||||
|
||||
it('feature status for deprecated feature and no subscription', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(false)
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.DeprecatedFileSafe as FeatureIdentifier)).toBe(
|
||||
FeatureStatus.NoUserSubscription,
|
||||
)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.DeprecatedFileSafe).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.NoUserSubscription)
|
||||
})
|
||||
|
||||
it('feature status for deprecated feature with subscription', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.DeprecatedFileSafe as FeatureIdentifier)).toBe(
|
||||
FeatureStatus.Entitled,
|
||||
)
|
||||
expect(
|
||||
featureService.getFeatureStatus(
|
||||
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.DeprecatedFileSafe).getValue(),
|
||||
),
|
||||
).toBe(FeatureStatus.Entitled)
|
||||
})
|
||||
|
||||
it('has paid subscription', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(featuresService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toBeFalsy
|
||||
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toBeFalsy
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
expect(featuresService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||
})
|
||||
|
||||
it('has paid subscription should be true if offline repo and signed into third party server', async () => {
|
||||
const featuresService = createService()
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
|
||||
featuresService.hasOfflineRepo = jest.fn().mockReturnValue(true)
|
||||
featureService.hasOfflineRepo = jest.fn().mockReturnValue(true)
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||
|
||||
expect(featuresService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||
expect(featureService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -343,8 +430,7 @@ describe('FeaturesService', () => {
|
||||
},
|
||||
} as never)
|
||||
|
||||
const featuresService = createService()
|
||||
await featuresService.migrateFeatureRepoToUserSetting([extensionRepoItem])
|
||||
await featureService.migrateFeatureRepoToUserSetting([extensionRepoItem])
|
||||
expect(settingsService.updateSetting).toHaveBeenCalledWith(
|
||||
SettingName.create(SettingName.NAMES.ExtensionKey).getValue(),
|
||||
extensionKey,
|
||||
@@ -369,8 +455,7 @@ describe('FeaturesService', () => {
|
||||
const installUrl = 'http://example.com'
|
||||
crypto.base64Decode = jest.fn().mockReturnValue(installUrl)
|
||||
|
||||
const featuresService = createService()
|
||||
const result = await featuresService.downloadRemoteThirdPartyFeature(installUrl)
|
||||
const result = await featureService.downloadRemoteThirdPartyFeature(installUrl)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
@@ -389,17 +474,14 @@ describe('FeaturesService', () => {
|
||||
const installUrl = 'http://example.com'
|
||||
crypto.base64Decode = jest.fn().mockReturnValue(installUrl)
|
||||
|
||||
const featuresService = createService()
|
||||
const result = await featuresService.downloadRemoteThirdPartyFeature(installUrl)
|
||||
const result = await featureService.downloadRemoteThirdPartyFeature(installUrl)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('sortRolesByHierarchy', () => {
|
||||
it('should sort given roles according to role hierarchy', () => {
|
||||
const featuresService = createService()
|
||||
|
||||
const sortedRoles = featuresService.rolesBySorting([
|
||||
const sortedRoles = featureService.rolesBySorting([
|
||||
RoleName.NAMES.ProUser,
|
||||
RoleName.NAMES.CoreUser,
|
||||
RoleName.NAMES.PlusUser,
|
||||
@@ -411,50 +493,42 @@ describe('FeaturesService', () => {
|
||||
|
||||
describe('hasMinimumRole', () => {
|
||||
it('should be false if core user checks for plus role', async () => {
|
||||
const featuresService = createService()
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.CoreUser])
|
||||
|
||||
const hasPlusUserRole = featuresService.hasMinimumRole(RoleName.NAMES.PlusUser)
|
||||
const hasPlusUserRole = featureService.hasMinimumRole(RoleName.NAMES.PlusUser)
|
||||
|
||||
expect(hasPlusUserRole).toBe(false)
|
||||
})
|
||||
|
||||
it('should be false if plus user checks for pro role', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.PlusUser, RoleName.NAMES.CoreUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.PlusUser, RoleName.NAMES.CoreUser])
|
||||
|
||||
const hasProUserRole = featuresService.hasMinimumRole(RoleName.NAMES.ProUser)
|
||||
const hasProUserRole = featureService.hasMinimumRole(RoleName.NAMES.ProUser)
|
||||
|
||||
expect(hasProUserRole).toBe(false)
|
||||
})
|
||||
|
||||
it('should be true if pro user checks for core user', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
const hasCoreUserRole = featuresService.hasMinimumRole(RoleName.NAMES.CoreUser)
|
||||
const hasCoreUserRole = featureService.hasMinimumRole(RoleName.NAMES.CoreUser)
|
||||
|
||||
expect(hasCoreUserRole).toBe(true)
|
||||
})
|
||||
|
||||
it('should be true if pro user checks for pro user', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
subscriptions.hasOnlineSubscription = jest.fn().mockReturnValue(true)
|
||||
|
||||
await featuresService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
||||
await featureService.updateOnlineRolesWithNewValues([RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
const hasProUserRole = featuresService.hasMinimumRole(RoleName.NAMES.ProUser)
|
||||
const hasProUserRole = featureService.hasMinimumRole(RoleName.NAMES.ProUser)
|
||||
|
||||
expect(hasProUserRole).toBe(true)
|
||||
})
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { MigrateFeatureRepoToUserSettingUseCase } from './UseCase/MigrateFeatureRepoToUserSetting'
|
||||
import { arraysEqual, removeFromArray, lastElement } from '@standardnotes/utils'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { RoleName, ContentType } from '@standardnotes/domain-core'
|
||||
import { RoleName, ContentType, Uuid } from '@standardnotes/domain-core'
|
||||
import { PROD_OFFLINE_FEATURES_URL } from '../../Hosts'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { WebSocketsService } from '../Api/WebsocketsService'
|
||||
import { WebSocketsServiceEvent } from '../Api/WebSocketsServiceEvent'
|
||||
import { TRUSTED_CUSTOM_EXTENSIONS_HOSTS, TRUSTED_FEATURE_HOSTS } from '@Lib/Hosts'
|
||||
import { UserRolesChangedEvent } from '@standardnotes/domain-events'
|
||||
import { ExperimentalFeatures, FindNativeFeature, FeatureIdentifier } from '@standardnotes/features'
|
||||
import { ExperimentalFeatures, FindNativeFeature, NativeFeatureIdentifier } from '@standardnotes/features'
|
||||
import {
|
||||
SNFeatureRepo,
|
||||
FeatureRepoContent,
|
||||
@@ -64,7 +64,7 @@ export class FeaturesService
|
||||
{
|
||||
private onlineRoles: string[] = []
|
||||
private offlineRoles: string[] = []
|
||||
private enabledExperimentalFeatures: FeatureIdentifier[] = []
|
||||
private enabledExperimentalFeatures: string[] = []
|
||||
|
||||
private getFeatureStatusUseCase = new GetFeatureStatusUseCase(this.items)
|
||||
|
||||
@@ -136,40 +136,47 @@ export class FeaturesService
|
||||
)
|
||||
}
|
||||
|
||||
public initializeFromDisk(): void {
|
||||
initializeFromDisk(): void {
|
||||
this.onlineRoles = this.storage.getValue<string[]>(StorageKey.UserRoles, undefined, [])
|
||||
|
||||
this.offlineRoles = this.storage.getValue<string[]>(StorageKey.OfflineUserRoles, undefined, [])
|
||||
|
||||
this.enabledExperimentalFeatures = this.storage.getValue(StorageKey.ExperimentalFeatures, undefined, [])
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === ApiServiceEvent.MetaReceived) {
|
||||
if (!this.sync) {
|
||||
this.log('Handling events interrupted. Sync service is not yet initialized.', event)
|
||||
return
|
||||
switch (event.type) {
|
||||
case ApiServiceEvent.MetaReceived: {
|
||||
if (!this.sync) {
|
||||
this.log('Handling events interrupted. Sync service is not yet initialized.', event)
|
||||
return
|
||||
}
|
||||
|
||||
const { userRoles } = event.payload as MetaReceivedData
|
||||
void this.updateOnlineRolesWithNewValues(userRoles.map((role) => role.name))
|
||||
break
|
||||
}
|
||||
|
||||
const { userRoles } = event.payload as MetaReceivedData
|
||||
void this.updateOnlineRolesWithNewValues(userRoles.map((role) => role.name))
|
||||
}
|
||||
|
||||
if (event.type === ApplicationEvent.ApplicationStageChanged) {
|
||||
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
|
||||
if (stage === ApplicationStage.FullSyncCompleted_13) {
|
||||
if (!this.hasFirstPartyOnlineSubscription()) {
|
||||
const offlineRepo = this.getOfflineRepo()
|
||||
|
||||
if (offlineRepo) {
|
||||
void this.downloadOfflineRoles(offlineRepo)
|
||||
case ApplicationEvent.ApplicationStageChanged: {
|
||||
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
|
||||
switch (stage) {
|
||||
case ApplicationStage.StorageDecrypted_09: {
|
||||
this.initializeFromDisk()
|
||||
break
|
||||
}
|
||||
case ApplicationStage.FullSyncCompleted_13: {
|
||||
if (!this.hasFirstPartyOnlineSubscription()) {
|
||||
const offlineRepo = this.getOfflineRepo()
|
||||
if (offlineRepo) {
|
||||
void this.downloadOfflineRoles(offlineRepo)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enableExperimentalFeature(identifier: FeatureIdentifier): void {
|
||||
public enableExperimentalFeature(identifier: string): void {
|
||||
this.enabledExperimentalFeatures.push(identifier)
|
||||
|
||||
void this.storage.setValue(StorageKey.ExperimentalFeatures, this.enabledExperimentalFeatures)
|
||||
@@ -177,7 +184,7 @@ export class FeaturesService
|
||||
void this.notifyEvent(FeaturesEvent.FeaturesAvailabilityChanged)
|
||||
}
|
||||
|
||||
public disableExperimentalFeature(identifier: FeatureIdentifier): void {
|
||||
public disableExperimentalFeature(identifier: string): void {
|
||||
removeFromArray(this.enabledExperimentalFeatures, identifier)
|
||||
|
||||
void this.storage.setValue(StorageKey.ExperimentalFeatures, this.enabledExperimentalFeatures)
|
||||
@@ -195,7 +202,7 @@ export class FeaturesService
|
||||
void this.notifyEvent(FeaturesEvent.FeaturesAvailabilityChanged)
|
||||
}
|
||||
|
||||
public toggleExperimentalFeature(identifier: FeatureIdentifier): void {
|
||||
public toggleExperimentalFeature(identifier: string): void {
|
||||
if (this.isExperimentalFeatureEnabled(identifier)) {
|
||||
this.disableExperimentalFeature(identifier)
|
||||
} else {
|
||||
@@ -203,19 +210,19 @@ export class FeaturesService
|
||||
}
|
||||
}
|
||||
|
||||
public getExperimentalFeatures(): FeatureIdentifier[] {
|
||||
public getExperimentalFeatures(): string[] {
|
||||
return ExperimentalFeatures
|
||||
}
|
||||
|
||||
public isExperimentalFeature(featureId: FeatureIdentifier): boolean {
|
||||
public isExperimentalFeature(featureId: string): boolean {
|
||||
return this.getExperimentalFeatures().includes(featureId)
|
||||
}
|
||||
|
||||
public getEnabledExperimentalFeatures(): FeatureIdentifier[] {
|
||||
public getEnabledExperimentalFeatures(): string[] {
|
||||
return this.enabledExperimentalFeatures
|
||||
}
|
||||
|
||||
public isExperimentalFeatureEnabled(featureId: FeatureIdentifier): boolean {
|
||||
public isExperimentalFeatureEnabled(featureId: string): boolean {
|
||||
return this.enabledExperimentalFeatures.includes(featureId)
|
||||
}
|
||||
|
||||
@@ -302,10 +309,10 @@ export class FeaturesService
|
||||
}
|
||||
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription(): boolean {
|
||||
return this.onlineRolesIncludePaidSubscription() || this.hasOfflineRepo()
|
||||
return this.onlineRolesIncludePaidSubscription() || this.hasOfflineRepo() || this.hasFirstPartyOnlineSubscription()
|
||||
}
|
||||
|
||||
private hasFirstPartyOnlineSubscription(): boolean {
|
||||
hasFirstPartyOnlineSubscription(): boolean {
|
||||
return this.sessions.isSignedIntoFirstPartyServer() && this.subscriptions.hasOnlineSubscription()
|
||||
}
|
||||
|
||||
@@ -364,12 +371,13 @@ export class FeaturesService
|
||||
}
|
||||
|
||||
public isThirdPartyFeature(identifier: string): boolean {
|
||||
const isNativeFeature = !!FindNativeFeature(identifier as FeatureIdentifier)
|
||||
const isNativeFeature = !!FindNativeFeature(identifier)
|
||||
return !isNativeFeature
|
||||
}
|
||||
|
||||
onlineRolesIncludePaidSubscription(): boolean {
|
||||
const unpaidRoles = [RoleName.NAMES.CoreUser]
|
||||
|
||||
return this.onlineRoles.some((role) => !unpaidRoles.includes(role))
|
||||
}
|
||||
|
||||
@@ -392,7 +400,7 @@ export class FeaturesService
|
||||
}
|
||||
|
||||
public getFeatureStatus(
|
||||
featureId: FeatureIdentifier,
|
||||
featureId: NativeFeatureIdentifier | Uuid,
|
||||
options: { inContextOfItem?: DecryptedItemInterface } = {},
|
||||
): FeatureStatus {
|
||||
return this.getFeatureStatusUseCase.execute({
|
||||
|
||||
@@ -1,28 +1,23 @@
|
||||
import { FeatureIdentifier } from '@standardnotes/features'
|
||||
import { NativeFeatureIdentifier } from '@standardnotes/features'
|
||||
import { FeatureStatus, ItemManagerInterface } from '@standardnotes/services'
|
||||
import { GetFeatureStatusUseCase } from './GetFeatureStatus'
|
||||
import { ComponentInterface, DecryptedItemInterface } from '@standardnotes/models'
|
||||
|
||||
jest.mock('@standardnotes/features', () => ({
|
||||
FeatureIdentifier: {
|
||||
DarkTheme: 'darkTheme',
|
||||
},
|
||||
FindNativeFeature: jest.fn(),
|
||||
}))
|
||||
|
||||
import { FindNativeFeature } from '@standardnotes/features'
|
||||
import { Subscription } from '@standardnotes/responses'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
describe('GetFeatureStatusUseCase', () => {
|
||||
let items: jest.Mocked<ItemManagerInterface>
|
||||
let usecase: GetFeatureStatusUseCase
|
||||
let findNativeFeature: jest.Mock<any, any>
|
||||
|
||||
beforeEach(() => {
|
||||
items = {
|
||||
getDisplayableComponents: jest.fn(),
|
||||
} as unknown as jest.Mocked<ItemManagerInterface>
|
||||
usecase = new GetFeatureStatusUseCase(items)
|
||||
;(FindNativeFeature as jest.Mock).mockReturnValue(undefined)
|
||||
findNativeFeature = jest.fn()
|
||||
usecase.findNativeFeature = findNativeFeature
|
||||
findNativeFeature.mockReturnValue(undefined)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -33,7 +28,7 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
it('should return entitled for free features', () => {
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: FeatureIdentifier.DarkTheme,
|
||||
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.DarkTheme).getValue(),
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||
firstPartyOnlineSubscription: undefined,
|
||||
firstPartyRoles: undefined,
|
||||
@@ -44,11 +39,11 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
|
||||
describe('deprecated features', () => {
|
||||
it('should return entitled for deprecated paid features if any subscription is active', () => {
|
||||
;(FindNativeFeature as jest.Mock).mockReturnValue({ deprecated: true })
|
||||
findNativeFeature.mockReturnValue({ deprecated: true })
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'deprecatedFeature',
|
||||
featureId: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: true,
|
||||
firstPartyOnlineSubscription: undefined,
|
||||
firstPartyRoles: undefined,
|
||||
@@ -57,11 +52,11 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
})
|
||||
|
||||
it('should return NoUserSubscription for deprecated paid features if no subscription is active', () => {
|
||||
;(FindNativeFeature as jest.Mock).mockReturnValue({ deprecated: true })
|
||||
findNativeFeature.mockReturnValue({ deprecated: true })
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'deprecatedFeature',
|
||||
featureId: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||
firstPartyOnlineSubscription: undefined,
|
||||
firstPartyRoles: undefined,
|
||||
@@ -72,11 +67,11 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
|
||||
describe('native features', () => {
|
||||
it('should return Entitled if the context item belongs to a shared vault and user does not have subscription', () => {
|
||||
;(FindNativeFeature as jest.Mock).mockReturnValue({ deprecated: false })
|
||||
findNativeFeature.mockReturnValue({ deprecated: false })
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'nativeFeature',
|
||||
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||
firstPartyOnlineSubscription: undefined,
|
||||
firstPartyRoles: undefined,
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||
@@ -86,11 +81,11 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
})
|
||||
|
||||
it('should return NoUserSubscription if the context item does not belong to a shared vault and user does not have subscription', () => {
|
||||
;(FindNativeFeature as jest.Mock).mockReturnValue({ deprecated: false })
|
||||
findNativeFeature.mockReturnValue({ deprecated: false })
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'nativeFeature',
|
||||
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||
firstPartyOnlineSubscription: undefined,
|
||||
firstPartyRoles: undefined,
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||
@@ -100,11 +95,11 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
})
|
||||
|
||||
it('should return NoUserSubscription for native features without subscription and roles', () => {
|
||||
;(FindNativeFeature as jest.Mock).mockReturnValue({ deprecated: false })
|
||||
findNativeFeature.mockReturnValue({ deprecated: false })
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'nativeFeature',
|
||||
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||
firstPartyOnlineSubscription: undefined,
|
||||
firstPartyRoles: undefined,
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||
@@ -113,14 +108,14 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
})
|
||||
|
||||
it('should return NotInCurrentPlan for native features with roles not in available roles', () => {
|
||||
;(FindNativeFeature as jest.Mock).mockReturnValue({
|
||||
findNativeFeature.mockReturnValue({
|
||||
deprecated: false,
|
||||
availableInRoles: ['notInRole'],
|
||||
})
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'nativeFeature',
|
||||
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||
firstPartyOnlineSubscription: undefined,
|
||||
firstPartyRoles: { online: ['inRole'] },
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||
@@ -129,14 +124,14 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
})
|
||||
|
||||
it('should return Entitled for native features with roles in available roles and active subscription', () => {
|
||||
;(FindNativeFeature as jest.Mock).mockReturnValue({
|
||||
findNativeFeature.mockReturnValue({
|
||||
deprecated: false,
|
||||
availableInRoles: ['inRole'],
|
||||
})
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'nativeFeature',
|
||||
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||
firstPartyOnlineSubscription: {
|
||||
endsAt: new Date(Date.now() + 10000).getTime(),
|
||||
} as jest.Mocked<Subscription>,
|
||||
@@ -147,14 +142,14 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
})
|
||||
|
||||
it('should return InCurrentPlanButExpired for native features with roles in available roles and expired subscription', () => {
|
||||
;(FindNativeFeature as jest.Mock).mockReturnValue({
|
||||
findNativeFeature.mockReturnValue({
|
||||
deprecated: false,
|
||||
availableInRoles: ['inRole'],
|
||||
})
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'nativeFeature',
|
||||
featureId: NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.AutobiographyTheme).getValue(),
|
||||
firstPartyOnlineSubscription: {
|
||||
endsAt: new Date(Date.now() - 10000).getTime(),
|
||||
} as jest.Mocked<Subscription>,
|
||||
@@ -168,7 +163,7 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
describe('third party features', () => {
|
||||
it('should return Entitled for third-party features', () => {
|
||||
const mockComponent = {
|
||||
identifier: 'thirdPartyFeature',
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
isExpired: false,
|
||||
} as unknown as jest.Mocked<ComponentInterface>
|
||||
|
||||
@@ -176,7 +171,7 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'thirdPartyFeature',
|
||||
featureId: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||
firstPartyOnlineSubscription: undefined,
|
||||
firstPartyRoles: undefined,
|
||||
@@ -189,7 +184,7 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'nonExistingThirdPartyFeature',
|
||||
featureId: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||
firstPartyOnlineSubscription: undefined,
|
||||
firstPartyRoles: undefined,
|
||||
@@ -199,7 +194,7 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
|
||||
it('should return InCurrentPlanButExpired for expired third-party features', () => {
|
||||
const mockComponent = {
|
||||
identifier: 'thirdPartyFeature',
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
isExpired: true,
|
||||
} as unknown as jest.Mocked<ComponentInterface>
|
||||
|
||||
@@ -207,7 +202,7 @@ describe('GetFeatureStatusUseCase', () => {
|
||||
|
||||
expect(
|
||||
usecase.execute({
|
||||
featureId: 'thirdPartyFeature',
|
||||
featureId: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: false,
|
||||
firstPartyOnlineSubscription: undefined,
|
||||
firstPartyRoles: undefined,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AnyFeatureDescription, FeatureIdentifier, FindNativeFeature } from '@standardnotes/features'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
import { AnyFeatureDescription, NativeFeatureIdentifier, FindNativeFeature } from '@standardnotes/features'
|
||||
import { DecryptedItemInterface } from '@standardnotes/models'
|
||||
import { Subscription } from '@standardnotes/responses'
|
||||
import { FeatureStatus, ItemManagerInterface } from '@standardnotes/services'
|
||||
@@ -8,20 +9,19 @@ export class GetFeatureStatusUseCase {
|
||||
constructor(private items: ItemManagerInterface) {}
|
||||
|
||||
execute(dto: {
|
||||
featureId: FeatureIdentifier | string
|
||||
featureId: NativeFeatureIdentifier | Uuid
|
||||
firstPartyOnlineSubscription: Subscription | undefined
|
||||
firstPartyRoles: { online: string[] } | { offline: string[] } | undefined
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: boolean
|
||||
inContextOfItem?: DecryptedItemInterface
|
||||
}): FeatureStatus {
|
||||
if (this.isFreeFeature(dto.featureId as FeatureIdentifier)) {
|
||||
if (this.isFreeFeature(dto.featureId)) {
|
||||
return FeatureStatus.Entitled
|
||||
}
|
||||
|
||||
const nativeFeature = FindNativeFeature(dto.featureId as FeatureIdentifier)
|
||||
|
||||
const nativeFeature = this.findNativeFeature(dto.featureId)
|
||||
if (!nativeFeature) {
|
||||
return this.getThirdPartyFeatureStatus(dto.featureId as string)
|
||||
return this.getThirdPartyFeatureStatus(dto.featureId)
|
||||
}
|
||||
|
||||
if (nativeFeature.deprecated) {
|
||||
@@ -39,6 +39,10 @@ export class GetFeatureStatusUseCase {
|
||||
})
|
||||
}
|
||||
|
||||
findNativeFeature(featureId: NativeFeatureIdentifier | Uuid): AnyFeatureDescription | undefined {
|
||||
return FindNativeFeature(featureId.value)
|
||||
}
|
||||
|
||||
private getDeprecatedNativeFeatureStatus(dto: {
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription: boolean
|
||||
nativeFeature: AnyFeatureDescription
|
||||
@@ -95,8 +99,8 @@ export class GetFeatureStatusUseCase {
|
||||
return FeatureStatus.Entitled
|
||||
}
|
||||
|
||||
private getThirdPartyFeatureStatus(featureId: string): FeatureStatus {
|
||||
const component = this.items.getDisplayableComponents().find((candidate) => candidate.identifier === featureId)
|
||||
private getThirdPartyFeatureStatus(uuid: Uuid): FeatureStatus {
|
||||
const component = this.items.getDisplayableComponents().find((candidate) => candidate.uuid === uuid.value)
|
||||
|
||||
if (!component) {
|
||||
return FeatureStatus.NoUserSubscription
|
||||
@@ -109,7 +113,9 @@ export class GetFeatureStatusUseCase {
|
||||
return FeatureStatus.Entitled
|
||||
}
|
||||
|
||||
private isFreeFeature(featureId: FeatureIdentifier) {
|
||||
return [FeatureIdentifier.DarkTheme, FeatureIdentifier.PlainEditor].includes(featureId)
|
||||
private isFreeFeature(featureId: NativeFeatureIdentifier) {
|
||||
return [NativeFeatureIdentifier.TYPES.DarkTheme, NativeFeatureIdentifier.TYPES.PlainEditor].includes(
|
||||
featureId.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ import {
|
||||
UserKeyPairChangedEventData,
|
||||
InternalFeatureService,
|
||||
InternalFeature,
|
||||
ApplicationEvent,
|
||||
ApplicationStageChangedEventPayload,
|
||||
ApplicationStage,
|
||||
} from '@standardnotes/services'
|
||||
import { Base64String, PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import {
|
||||
@@ -112,8 +115,17 @@ export class SessionManager
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === ApiServiceEvent.SessionRefreshed) {
|
||||
this.httpService.setSession((event.payload as SessionRefreshedData).session)
|
||||
switch (event.type) {
|
||||
case ApiServiceEvent.SessionRefreshed:
|
||||
this.httpService.setSession((event.payload as SessionRefreshedData).session)
|
||||
break
|
||||
|
||||
case ApplicationEvent.ApplicationStageChanged: {
|
||||
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
|
||||
if (stage === ApplicationStage.StorageDecrypted_09) {
|
||||
await this.initializeFromDisk()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +154,7 @@ export class SessionManager
|
||||
this.apiService.setUser(user)
|
||||
}
|
||||
|
||||
async initializeFromDisk(): Promise<void> {
|
||||
private async initializeFromDisk(): Promise<void> {
|
||||
this.memoizeUser(this.storage.getValue(StorageKey.User))
|
||||
|
||||
if (!this.user) {
|
||||
|
||||
@@ -145,7 +145,7 @@ describe('migrations', () => {
|
||||
content_type: ContentType.TYPES.Component,
|
||||
content: FillItemContent({
|
||||
package_info: {
|
||||
identifier: FeatureIdentifier.MarkdownProEditor,
|
||||
identifier: NativeFeatureIdentifier.TYPES.MarkdownProEditor,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
@@ -170,7 +170,7 @@ describe('migrations', () => {
|
||||
content_type: ContentType.TYPES.Component,
|
||||
content: FillItemContent({
|
||||
package_info: {
|
||||
identifier: FeatureIdentifier.DeprecatedBoldEditor,
|
||||
identifier: NativeFeatureIdentifier.TYPES.DeprecatedBoldEditor,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -369,9 +369,9 @@ describe('app models', () => {
|
||||
editorIdentifier: 'foo-editor',
|
||||
})
|
||||
|
||||
expect(this.application.componentManager.editorForNote(note).uniqueIdentifier).to.equal(component.uuid)
|
||||
expect(this.application.componentManager.editorForNote(note).uniqueIdentifier.value).to.equal(component.uuid)
|
||||
|
||||
const duplicate = await this.application.mutator.duplicateItem(note, true)
|
||||
expect(this.application.componentManager.editorForNote(duplicate).uniqueIdentifier).to.equal(component.uuid)
|
||||
expect(this.application.componentManager.editorForNote(duplicate).uniqueIdentifier.value).to.equal(component.uuid)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user