chore: legacy fixes (#2343)
This commit is contained in:
@@ -9,16 +9,22 @@ export function findDefaultItemsKey(itemsKeys: ItemsKeyInterface[]): ItemsKeyInt
|
|||||||
return key.isDefault
|
return key.isDefault
|
||||||
})
|
})
|
||||||
|
|
||||||
if (defaultKeys.length > 1) {
|
if (defaultKeys.length === 0) {
|
||||||
/**
|
return undefined
|
||||||
* Prioritize one that is synced, as neverSynced keys will likely be deleted after
|
|
||||||
* DownloadFirst sync.
|
|
||||||
*/
|
|
||||||
const syncedKeys = defaultKeys.filter((key) => !key.neverSynced)
|
|
||||||
if (syncedKeys.length > 0) {
|
|
||||||
return syncedKeys[0]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultKeys[0]
|
if (defaultKeys.length === 1) {
|
||||||
|
return defaultKeys[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prioritize one that is synced, as neverSynced keys will likely be deleted after
|
||||||
|
* DownloadFirst sync.
|
||||||
|
*/
|
||||||
|
const syncedKeys = defaultKeys.filter((key) => !key.neverSynced)
|
||||||
|
if (syncedKeys.length > 0) {
|
||||||
|
return syncedKeys[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { DeinitMode } from './DeinitMode'
|
|||||||
import { DeinitSource } from './DeinitSource'
|
import { DeinitSource } from './DeinitSource'
|
||||||
import { UserClientInterface } from '../User/UserClientInterface'
|
import { UserClientInterface } from '../User/UserClientInterface'
|
||||||
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
|
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
|
||||||
|
import { User } from '@standardnotes/responses'
|
||||||
|
|
||||||
export interface ApplicationInterface {
|
export interface ApplicationInterface {
|
||||||
deinit(mode: DeinitMode, source: DeinitSource): void
|
deinit(mode: DeinitMode, source: DeinitSource): void
|
||||||
@@ -57,6 +58,8 @@ export interface ApplicationInterface {
|
|||||||
contentType: ContentType | ContentType[],
|
contentType: ContentType | ContentType[],
|
||||||
stream: ItemStream<I>,
|
stream: ItemStream<I>,
|
||||||
): () => void
|
): () => void
|
||||||
|
|
||||||
|
getUser(): User | undefined
|
||||||
hasAccount(): boolean
|
hasAccount(): boolean
|
||||||
|
|
||||||
importData(data: BackupFile, awaitSync?: boolean): Promise<ImportDataReturnType>
|
importData(data: BackupFile, awaitSync?: boolean): Promise<ImportDataReturnType>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ describe('IntegrityService', () => {
|
|||||||
uuid: '1-2-3',
|
uuid: '1-2-3',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
source: 5,
|
source: "AfterDownloadFirst",
|
||||||
},
|
},
|
||||||
type: 'IntegrityCheckCompleted',
|
type: 'IntegrityCheckCompleted',
|
||||||
},
|
},
|
||||||
@@ -90,7 +90,7 @@ describe('IntegrityService', () => {
|
|||||||
{
|
{
|
||||||
payload: {
|
payload: {
|
||||||
rawPayloads: [],
|
rawPayloads: [],
|
||||||
source: 5,
|
source: "AfterDownloadFirst",
|
||||||
},
|
},
|
||||||
type: 'IntegrityCheckCompleted',
|
type: 'IntegrityCheckCompleted',
|
||||||
},
|
},
|
||||||
@@ -140,7 +140,7 @@ describe('IntegrityService', () => {
|
|||||||
{
|
{
|
||||||
payload: {
|
payload: {
|
||||||
rawPayloads: [],
|
rawPayloads: [],
|
||||||
source: 5,
|
source: "AfterDownloadFirst",
|
||||||
},
|
},
|
||||||
type: 'IntegrityCheckCompleted',
|
type: 'IntegrityCheckCompleted',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ export enum SyncMode {
|
|||||||
/**
|
/**
|
||||||
* Performs a standard sync, uploading any dirty items and retrieving items.
|
* Performs a standard sync, uploading any dirty items and retrieving items.
|
||||||
*/
|
*/
|
||||||
Default = 1,
|
Default = 'Default',
|
||||||
/**
|
/**
|
||||||
* The first sync for an account, where we first want to download all remote items first
|
* The first sync for an account, where we first want to download all remote items first
|
||||||
* before uploading any dirty items. This allows a consumer, for example, to download
|
* before uploading any dirty items. This allows a consumer, for example, to download
|
||||||
* all data to see if user has an items key, and if not, only then create a new one.
|
* all data to see if user has an items key, and if not, only then create a new one.
|
||||||
*/
|
*/
|
||||||
DownloadFirst = 2,
|
DownloadFirst = 'DownloadFirst',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
|
|
||||||
export enum SyncSource {
|
export enum SyncSource {
|
||||||
External = 1,
|
External = 'External',
|
||||||
SpawnQueue = 2,
|
SpawnQueue = 'SpawnQueue',
|
||||||
ResolveQueue = 3,
|
ResolveQueue = 'ResolveQueue',
|
||||||
MoreDirtyItems = 4,
|
MoreDirtyItems = 'MoreDirtyItems',
|
||||||
AfterDownloadFirst = 5,
|
DownloadFirst = 'DownloadFirst',
|
||||||
IntegrityCheck = 6,
|
AfterDownloadFirst = 'AfterDownloadFirst',
|
||||||
ResolveOutOfSync = 7,
|
IntegrityCheck = 'IntegrityCheck',
|
||||||
|
ResolveOutOfSync = 'ResolveOutOfSync',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,7 +432,15 @@ export class SNSyncService
|
|||||||
})
|
})
|
||||||
|
|
||||||
await this.payloadManager.emitPayloads(payloads, PayloadEmitSource.LocalChanged)
|
await this.payloadManager.emitPayloads(payloads, PayloadEmitSource.LocalChanged)
|
||||||
await this.persistPayloads(payloads)
|
|
||||||
|
/**
|
||||||
|
* When signing into an 003 account (or an account that is not the latest), the temporary items key will be 004
|
||||||
|
* and will not match user account version, triggering a key not found exception. This error resolves once the
|
||||||
|
* download first sync completes and the correct key is downloaded. We suppress any persistence
|
||||||
|
* exceptions here to avoid showing an error to the user.
|
||||||
|
*/
|
||||||
|
const hidePersistErrorDueToWaitingOnKeyDownload = true
|
||||||
|
await this.persistPayloads(payloads, { throwError: !hidePersistErrorDueToWaitingOnKeyDownload })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -579,7 +587,8 @@ export class SNSyncService
|
|||||||
|
|
||||||
const payloadsNeedingSave = this.popPayloadsNeedingPreSyncSave(decryptedPayloads)
|
const payloadsNeedingSave = this.popPayloadsNeedingPreSyncSave(decryptedPayloads)
|
||||||
|
|
||||||
await this.persistPayloads(payloadsNeedingSave)
|
const hidePersistErrorDueToWaitingOnKeyDownload = options.mode === SyncMode.DownloadFirst
|
||||||
|
await this.persistPayloads(payloadsNeedingSave, { throwError: !hidePersistErrorDueToWaitingOnKeyDownload })
|
||||||
|
|
||||||
if (options.onPresyncSave) {
|
if (options.onPresyncSave) {
|
||||||
options.onPresyncSave()
|
options.onPresyncSave()
|
||||||
@@ -1336,14 +1345,19 @@ export class SNSyncService
|
|||||||
await this.persistPayloads(payloads)
|
await this.persistPayloads(payloads)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async persistPayloads(payloads: FullyFormedPayloadInterface[]) {
|
public async persistPayloads(
|
||||||
|
payloads: FullyFormedPayloadInterface[],
|
||||||
|
options: { throwError: boolean } = { throwError: true },
|
||||||
|
) {
|
||||||
if (payloads.length === 0 || this.dealloced) {
|
if (payloads.length === 0 || this.dealloced) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.storageService.savePayloads(payloads).catch((error) => {
|
return this.storageService.savePayloads(payloads).catch((error) => {
|
||||||
void this.notifyEvent(SyncEvent.DatabaseWriteError, error)
|
if (options.throwError) {
|
||||||
SNLog.error(error)
|
void this.notifyEvent(SyncEvent.DatabaseWriteError, error)
|
||||||
|
SNLog.error(error)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ describe('app models', () => {
|
|||||||
const epoch = new Date(0)
|
const epoch = new Date(0)
|
||||||
expect(item.serverUpdatedAt - epoch).to.equal(0)
|
expect(item.serverUpdatedAt - epoch).to.equal(0)
|
||||||
expect(item.created_at - epoch).to.be.above(0)
|
expect(item.created_at - epoch).to.be.above(0)
|
||||||
expect(new Date() - item.created_at).to.be.below(5) // < 5ms
|
|
||||||
|
const presentThresholdMs = 10
|
||||||
|
expect(new Date() - item.created_at).to.be.below(presentThresholdMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handles delayed mapping', async function () {
|
it('handles delayed mapping', async function () {
|
||||||
|
|||||||
43
packages/web/src/javascripts/Application/DevMode.ts
Normal file
43
packages/web/src/javascripts/Application/DevMode.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { InternalFeature, InternalFeatureService } from '@standardnotes/snjs'
|
||||||
|
import { WebApplicationInterface } from '@standardnotes/ui-services'
|
||||||
|
|
||||||
|
export class DevMode {
|
||||||
|
constructor(private application: WebApplicationInterface) {
|
||||||
|
InternalFeatureService.get().enableFeature(InternalFeature.Vaults)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Valid only when running a mock event publisher on port 3124 */
|
||||||
|
async purchaseMockSubscription() {
|
||||||
|
const subscriptionId = 2000
|
||||||
|
const email = this.application.getUser()?.email
|
||||||
|
const response = await fetch('http://localhost:3124/events', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
eventType: 'SUBSCRIPTION_PURCHASED',
|
||||||
|
eventPayload: {
|
||||||
|
userEmail: email,
|
||||||
|
subscriptionId: subscriptionId,
|
||||||
|
subscriptionName: 'PRO_PLAN',
|
||||||
|
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
offline: false,
|
||||||
|
discountCode: null,
|
||||||
|
limitedDiscountPurchased: false,
|
||||||
|
newSubscriber: true,
|
||||||
|
totalActiveSubscriptionsCount: 1,
|
||||||
|
userRegisteredAt: 1,
|
||||||
|
billingFrequency: 12,
|
||||||
|
payAmount: 59.0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Failed to publish mocked event: ${response.status} ${response.statusText}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,18 +51,21 @@ import { FeatureName } from '@/Controllers/FeatureName'
|
|||||||
import { ItemGroupController } from '@/Components/NoteView/Controller/ItemGroupController'
|
import { ItemGroupController } from '@/Components/NoteView/Controller/ItemGroupController'
|
||||||
import { VisibilityObserver } from './VisibilityObserver'
|
import { VisibilityObserver } from './VisibilityObserver'
|
||||||
import { MomentsService } from '@/Controllers/Moments/MomentsService'
|
import { MomentsService } from '@/Controllers/Moments/MomentsService'
|
||||||
import { purchaseMockSubscription } from '@/Utils/Dev/PurchaseMockSubscription'
|
import { DevMode } from './DevMode'
|
||||||
|
|
||||||
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
|
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
|
||||||
|
|
||||||
export class WebApplication extends SNApplication implements WebApplicationInterface {
|
export class WebApplication extends SNApplication implements WebApplicationInterface {
|
||||||
private webServices!: WebServices
|
public readonly itemControllerGroup: ItemGroupController
|
||||||
private webEventObservers: WebEventObserver[] = []
|
|
||||||
public itemControllerGroup: ItemGroupController
|
|
||||||
private mobileWebReceiver?: MobileWebReceiver
|
|
||||||
private androidBackHandler?: AndroidBackHandler
|
|
||||||
public readonly routeService: RouteServiceInterface
|
public readonly routeService: RouteServiceInterface
|
||||||
private visibilityObserver?: VisibilityObserver
|
|
||||||
|
private readonly webServices!: WebServices
|
||||||
|
private readonly webEventObservers: WebEventObserver[] = []
|
||||||
|
private readonly mobileWebReceiver?: MobileWebReceiver
|
||||||
|
private readonly androidBackHandler?: AndroidBackHandler
|
||||||
|
private readonly visibilityObserver?: VisibilityObserver
|
||||||
|
|
||||||
|
public readonly devMode?: DevMode
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
deviceInterface: WebOrDesktopDevice,
|
deviceInterface: WebOrDesktopDevice,
|
||||||
@@ -91,6 +94,10 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
|||||||
u2fAuthenticatorVerificationPromptFunction: startAuthentication,
|
u2fAuthenticatorVerificationPromptFunction: startAuthentication,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (isDev) {
|
||||||
|
this.devMode = new DevMode(this)
|
||||||
|
}
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
dealloced: observable,
|
dealloced: observable,
|
||||||
})
|
})
|
||||||
@@ -152,7 +159,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
|||||||
;(service as { application?: WebApplication }).application = undefined
|
;(service as { application?: WebApplication }).application = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
this.webServices = {} as WebServices
|
;(this.webServices as unknown) = undefined
|
||||||
|
|
||||||
this.itemControllerGroup.deinit()
|
this.itemControllerGroup.deinit()
|
||||||
;(this.itemControllerGroup as unknown) = undefined
|
;(this.itemControllerGroup as unknown) = undefined
|
||||||
@@ -165,7 +172,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
|||||||
|
|
||||||
if (this.visibilityObserver) {
|
if (this.visibilityObserver) {
|
||||||
this.visibilityObserver.deinit()
|
this.visibilityObserver.deinit()
|
||||||
this.visibilityObserver = undefined
|
;(this.visibilityObserver as unknown) = undefined
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error while deiniting application', error)
|
console.error('Error while deiniting application', error)
|
||||||
@@ -458,12 +465,4 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
|||||||
generateUUID(): string {
|
generateUUID(): string {
|
||||||
return this.options.crypto.generateUUID()
|
return this.options.crypto.generateUUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
dev__purchaseMockSubscription() {
|
|
||||||
if (!isDev) {
|
|
||||||
throw new Error('This method is only available in dev mode')
|
|
||||||
}
|
|
||||||
|
|
||||||
void purchaseMockSubscription(this.getUser()?.email as string, 2000)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import PreferencesViewWrapper from '@/Components/Preferences/PreferencesViewWrap
|
|||||||
import ChallengeModal from '@/Components/ChallengeModal/ChallengeModal'
|
import ChallengeModal from '@/Components/ChallengeModal/ChallengeModal'
|
||||||
import NotesContextMenu from '@/Components/NotesContextMenu/NotesContextMenu'
|
import NotesContextMenu from '@/Components/NotesContextMenu/NotesContextMenu'
|
||||||
import PurchaseFlowWrapper from '@/Components/PurchaseFlow/PurchaseFlowWrapper'
|
import PurchaseFlowWrapper from '@/Components/PurchaseFlow/PurchaseFlowWrapper'
|
||||||
import { FunctionComponent, useCallback, useEffect, useMemo, useState, lazy } from 'react'
|
import { FunctionComponent, useCallback, useEffect, useMemo, useState, lazy, useRef } from 'react'
|
||||||
import RevisionHistoryModal from '@/Components/RevisionHistoryModal/RevisionHistoryModal'
|
import RevisionHistoryModal from '@/Components/RevisionHistoryModal/RevisionHistoryModal'
|
||||||
import PremiumModalProvider from '@/Hooks/usePremiumModal'
|
import PremiumModalProvider from '@/Hooks/usePremiumModal'
|
||||||
import ConfirmSignoutContainer from '@/Components/ConfirmSignoutModal/ConfirmSignoutModal'
|
import ConfirmSignoutContainer from '@/Components/ConfirmSignoutModal/ConfirmSignoutModal'
|
||||||
@@ -44,6 +44,9 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
|||||||
const [needsUnlock, setNeedsUnlock] = useState(true)
|
const [needsUnlock, setNeedsUnlock] = useState(true)
|
||||||
const [challenges, setChallenges] = useState<Challenge[]>([])
|
const [challenges, setChallenges] = useState<Challenge[]>([])
|
||||||
|
|
||||||
|
const currentWriteErrorDialog = useRef<Promise<void> | null>(null)
|
||||||
|
const currentLoadErrorDialog = useRef<Promise<void> | null>(null)
|
||||||
|
|
||||||
const viewControllerManager = application.getViewControllerManager()
|
const viewControllerManager = application.getViewControllerManager()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -120,13 +123,25 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
|||||||
} else if (eventName === ApplicationEvent.Launched) {
|
} else if (eventName === ApplicationEvent.Launched) {
|
||||||
onAppLaunch()
|
onAppLaunch()
|
||||||
} else if (eventName === ApplicationEvent.LocalDatabaseReadError) {
|
} else if (eventName === ApplicationEvent.LocalDatabaseReadError) {
|
||||||
alertDialog({
|
if (!currentLoadErrorDialog.current) {
|
||||||
text: 'Unable to load local database. Please restart the app and try again.',
|
alertDialog({
|
||||||
}).catch(console.error)
|
text: 'Unable to load local database. Please restart the app and try again.',
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
currentLoadErrorDialog.current = null
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
} else if (eventName === ApplicationEvent.LocalDatabaseWriteError) {
|
} else if (eventName === ApplicationEvent.LocalDatabaseWriteError) {
|
||||||
alertDialog({
|
if (!currentWriteErrorDialog.current) {
|
||||||
text: 'Unable to write to local database. Please restart the app and try again.',
|
currentWriteErrorDialog.current = alertDialog({
|
||||||
}).catch(console.error)
|
text: 'Unable to write to local database. Please restart the app and try again.',
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
currentWriteErrorDialog.current = null
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
} else if (eventName === ApplicationEvent.BiometricsSoftLockEngaged) {
|
} else if (eventName === ApplicationEvent.BiometricsSoftLockEngaged) {
|
||||||
setNeedsUnlock(true)
|
setNeedsUnlock(true)
|
||||||
} else if (eventName === ApplicationEvent.BiometricsSoftLockDisengaged) {
|
} else if (eventName === ApplicationEvent.BiometricsSoftLockDisengaged) {
|
||||||
|
|||||||
@@ -45,17 +45,17 @@ const READY_PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
|
|||||||
{ id: 'help-feedback', label: 'Help & feedback', icon: 'help' },
|
{ id: 'help-feedback', label: 'Help & feedback', icon: 'help' },
|
||||||
]
|
]
|
||||||
|
|
||||||
if (featureTrunkVaultsEnabled()) {
|
|
||||||
PREFERENCES_MENU_ITEMS.splice(3, 0, { id: 'vaults', label: 'Vaults', icon: 'safe-square' })
|
|
||||||
READY_PREFERENCES_MENU_ITEMS.splice(3, 0, { id: 'vaults', label: 'Vaults', icon: 'safe-square' })
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PreferencesMenu {
|
export class PreferencesMenu {
|
||||||
private _selectedPane: PreferenceId = 'account'
|
private _selectedPane: PreferenceId = 'account'
|
||||||
private _menu: PreferencesMenuItem[]
|
private _menu: PreferencesMenuItem[]
|
||||||
private _extensionLatestVersions: PackageProvider = new PackageProvider(new Map())
|
private _extensionLatestVersions: PackageProvider = new PackageProvider(new Map())
|
||||||
|
|
||||||
constructor(private application: WebApplication, private readonly _enableUnfinishedFeatures: boolean) {
|
constructor(private application: WebApplication, private readonly _enableUnfinishedFeatures: boolean) {
|
||||||
|
if (featureTrunkVaultsEnabled()) {
|
||||||
|
PREFERENCES_MENU_ITEMS.splice(3, 0, { id: 'vaults', label: 'Vaults', icon: 'safe-square' })
|
||||||
|
READY_PREFERENCES_MENU_ITEMS.splice(3, 0, { id: 'vaults', label: 'Vaults', icon: 'safe-square' })
|
||||||
|
}
|
||||||
|
|
||||||
this._menu = this._enableUnfinishedFeatures ? PREFERENCES_MENU_ITEMS : READY_PREFERENCES_MENU_ITEMS
|
this._menu = this._enableUnfinishedFeatures ? PREFERENCES_MENU_ITEMS : READY_PREFERENCES_MENU_ITEMS
|
||||||
|
|
||||||
this.loadLatestVersions()
|
this.loadLatestVersions()
|
||||||
|
|||||||
@@ -62,12 +62,12 @@ describe('LinkingController', () => {
|
|||||||
alerts: {} as jest.Mocked<WebApplication['alerts']>,
|
alerts: {} as jest.Mocked<WebApplication['alerts']>,
|
||||||
sync: {} as jest.Mocked<WebApplication['sync']>,
|
sync: {} as jest.Mocked<WebApplication['sync']>,
|
||||||
mutator: {} as jest.Mocked<WebApplication['mutator']>,
|
mutator: {} as jest.Mocked<WebApplication['mutator']>,
|
||||||
|
itemControllerGroup: {} as jest.Mocked<WebApplication['itemControllerGroup']>,
|
||||||
} as unknown as jest.Mocked<WebApplication>
|
} as unknown as jest.Mocked<WebApplication>
|
||||||
|
|
||||||
application.getPreference = jest.fn()
|
application.getPreference = jest.fn()
|
||||||
application.addSingleEventObserver = jest.fn()
|
application.addSingleEventObserver = jest.fn()
|
||||||
application.streamItems = jest.fn()
|
application.streamItems = jest.fn()
|
||||||
application.itemControllerGroup = {} as jest.Mocked<WebApplication['itemControllerGroup']>
|
|
||||||
application.sync.sync = jest.fn()
|
application.sync.sync = jest.fn()
|
||||||
|
|
||||||
Object.defineProperty(application, 'items', { value: {} as jest.Mocked<ItemManagerInterface> })
|
Object.defineProperty(application, 'items', { value: {} as jest.Mocked<ItemManagerInterface> })
|
||||||
|
|||||||
@@ -197,11 +197,13 @@ export class LinkingController extends AbstractViewController {
|
|||||||
const linkNoteAndFile = async (note: SNNote, file: FileItem) => {
|
const linkNoteAndFile = async (note: SNNote, file: FileItem) => {
|
||||||
const updatedFile = await this.application.mutator.associateFileWithNote(file, note)
|
const updatedFile = await this.application.mutator.associateFileWithNote(file, note)
|
||||||
|
|
||||||
if (updatedFile && featureTrunkVaultsEnabled()) {
|
if (featureTrunkVaultsEnabled()) {
|
||||||
const noteVault = this.application.vaults.getItemVault(note)
|
if (updatedFile) {
|
||||||
const fileVault = this.application.vaults.getItemVault(updatedFile)
|
const noteVault = this.application.vaults.getItemVault(note)
|
||||||
if (noteVault && !fileVault) {
|
const fileVault = this.application.vaults.getItemVault(updatedFile)
|
||||||
await this.application.vaults.moveItemToVault(noteVault, file)
|
if (noteVault && !fileVault) {
|
||||||
|
await this.application.vaults.moveItemToVault(noteVault, file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
/** Valid only when running a mock event publisher on port 3124 */
|
|
||||||
export async function purchaseMockSubscription(email: string, subscriptionId: number) {
|
|
||||||
const response = await fetch('http://localhost:3124/events', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
eventType: 'SUBSCRIPTION_PURCHASED',
|
|
||||||
eventPayload: {
|
|
||||||
userEmail: email,
|
|
||||||
subscriptionId: subscriptionId,
|
|
||||||
subscriptionName: 'PRO_PLAN',
|
|
||||||
subscriptionExpiresAt: (new Date().getTime() + 3_600_000) * 1_000,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
offline: false,
|
|
||||||
discountCode: null,
|
|
||||||
limitedDiscountPurchased: false,
|
|
||||||
newSubscriber: true,
|
|
||||||
totalActiveSubscriptionsCount: 1,
|
|
||||||
userRegisteredAt: 1,
|
|
||||||
billingFrequency: 12,
|
|
||||||
payAmount: 59.0,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error(`Failed to publish mocked event: ${response.status} ${response.statusText}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user