Move indexer and mail search to mail-app

This commit is contained in:
wrd 2024-08-20 18:03:03 +02:00 committed by and
parent f04bf16bd3
commit f99bd69ff0
89 changed files with 1464 additions and 704 deletions

View file

@ -98,7 +98,7 @@ async function buildWebPart({ stage, host, version, domainConfigs, app }) {
const tsConfig = isCalendarBuild ? "tsconfig-calendar-app.json" : "tsconfig.json"
const buildDir = isCalendarBuild ? "build-calendar-app" : "build"
const entryFile = isCalendarBuild ? "src/calendar-app/calendar-app.ts" : "src/mail-app/app.ts"
const workerFile = isCalendarBuild ? "src/calendar-app/calendar-worker.ts" : "src/mail-app/mail-worker.ts"
const workerFile = isCalendarBuild ? "src/calendar-app/workerUtils/worker/calendar-worker.ts" : "src/mail-app/workerUtils/worker/mail-worker.ts"
await runStep("Web: Assets", async () => {
await prepareAssets(stage, host, version, domainConfigs, buildDir)

View file

@ -229,7 +229,12 @@ export function getChunkName(moduleId, { getModuleInfo }) {
return "wasm"
} else if (moduleId.includes("wasm-fallback")) {
return "wasm-fallback"
} else if (isIn("src/common/native/worker")) {
} else if (
isIn("src/common/native/worker") ||
isIn("src/mail-app/workerUtils/worker") ||
isIn("src/calendar-app/worker") ||
isIn("src/mail-app/workerUtils/offline")
) {
return "worker"
} else if (isIn("src/common/native/common")) {
return "native-common"
@ -280,7 +285,7 @@ export function getChunkName(moduleId, { getModuleInfo }) {
} else if (isIn("src/common/api/worker/facades/lazy")) {
// things that are not used for login and are generally accessed occasionally
return "worker-lazy"
} else if (isIn("src/common/api/worker/search")) {
} else if (isIn("src/common/api/worker/search") || isIn("src/mail-app/workerUtils/index")) {
// things related to indexer or search
return "worker-search"
} else if (isIn("src/common/api/worker/Urlifier") || isIn("libs/linkify") || isIn("libs/linkify-html")) {

View file

@ -33,7 +33,7 @@ export async function buildWebapp({ version, stage, host, measure, minify, proje
const tsConfig = isCalendarApp ? "tsconfig-calendar-app.json" : "tsconfig.json"
const buildDir = isCalendarApp ? "build-calendar-app" : "build"
const entryFile = isCalendarApp ? "src/calendar-app/calendar-app.ts" : "src/mail-app/app.ts"
const workerFile = isCalendarApp ? "src/calendar-app/calendar-worker.ts" : "src/mail-app/mail-worker.ts"
const workerFile = isCalendarApp ? "src/calendar-app/workerUtils/worker/calendar-worker.ts" : "src/mail-app/workerUtils/worker/mail-worker.ts"
console.log("Building app", app)

View file

@ -83,7 +83,7 @@ import("../mail-app/translations/en.js")
setupNavShortcuts()
// this needs to stay after client.init
windowFacade.init(calendarLocator.logins, calendarLocator.indexerFacade, calendarLocator.connectivityModel)
windowFacade.init(calendarLocator.logins, calendarLocator.connectivityModel, null)
if (isDesktop()) {
import("../common/native/main/UpdatePrompt.js").then(({ registerForUpdates }) => registerForUpdates(calendarLocator.desktopSettingsFacade))
}

View file

@ -396,17 +396,8 @@ export class CalendarSearchBar implements Component<CalendarSearchBarAttrs> {
}
if (this.isQuickSearch()) {
if (safeLimit && hasMoreResults(safeResult) && safeResult.results.length < safeLimit) {
calendarLocator.searchFacade.getMoreSearchResults(safeResult, safeLimit - safeResult.results.length).then((moreResults) => {
if (calendarLocator.search.isNewSearch(query, moreResults.restriction)) {
return
} else {
this.loadAndDisplayResult(query, moreResults, limit)
}
})
} else {
this.showResultsInOverlay(safeResult)
}
// Calendar does not have a quick search bar, so this has been taken out
// but this is left in case this changes in the future
} else {
// instances will be displayed as part of the list of the search view, when the search view is displayed
searchRouter.routeTo(query, safeResult.restriction)

View file

@ -2,7 +2,6 @@ import stream from "mithril/stream"
import Stream from "mithril/stream"
import type { SearchRestriction, SearchResult } from "../../../../common/api/worker/search/SearchTypes"
import { arrayEquals, assertNonNull, assertNotNull, incrementMonth, isSameTypeRef, lazyAsync, tokenize } from "@tutao/tutanota-utils"
import type { SearchFacade } from "../../../../common/api/worker/search/SearchFacade"
import { assertMainOrNode } from "../../../../common/api/common/Env"
import { listIdPart } from "../../../../common/api/common/utils/EntityUtils.js"
import { IProgressMonitor } from "../../../../common/api/common/utils/ProgressMonitor.js"
@ -22,24 +21,24 @@ export class CalendarSearchModel {
// we store this as a reference to the currently running search. if we don't, we only have the last result's query info
// to compare against incoming new queries
lastQueryString: Stream<string | null>
private _lastQuery: SearchQuery | null
_lastSearchPromise: Promise<SearchResult | void>
private lastQuery: SearchQuery | null
private lastSearchPromise: Promise<SearchResult | void>
cancelSignal: Stream<boolean>
constructor(private readonly calendarModel: lazyAsync<CalendarEventsRepository>) {
this.result = stream()
this.lastQueryString = stream<string | null>("")
this._lastQuery = null
this._lastSearchPromise = Promise.resolve()
this.lastQuery = null
this.lastSearchPromise = Promise.resolve()
this.cancelSignal = stream(false)
}
async search(searchQuery: SearchQuery, progressTracker: ProgressTracker): Promise<SearchResult | void> {
if (this._lastQuery && searchQueryEquals(searchQuery, this._lastQuery)) {
return this._lastSearchPromise
if (this.lastQuery && searchQueryEquals(searchQuery, this.lastQuery)) {
return this.lastSearchPromise
}
this._lastQuery = searchQuery
this.lastQuery = searchQuery
const { query, restriction, minSuggestionCount, maxResults } = searchQuery
this.lastQueryString(query)
let result = this.result()
@ -63,7 +62,7 @@ export class CalendarSearchModel {
moreResultsEntries: [],
}
this.result(result)
this._lastSearchPromise = Promise.resolve(result)
this.lastSearchPromise = Promise.resolve(result)
} else {
// we interpret restriction.start as the start of the first day of the first month we want to search
// restriction.end is the end of the last day of the last month we want to search
@ -94,8 +93,8 @@ export class CalendarSearchModel {
if (this.cancelSignal()) {
this.result(calendarResult)
this._lastSearchPromise = Promise.resolve(calendarResult)
return this._lastSearchPromise
this.lastSearchPromise = Promise.resolve(calendarResult)
return this.lastSearchPromise
}
await calendarModel.loadMonthsIfNeeded(daysInMonths, monitor, this.cancelSignal)
@ -113,8 +112,8 @@ export class CalendarSearchModel {
if (this.cancelSignal()) {
this.result(calendarResult)
this._lastSearchPromise = Promise.resolve(calendarResult)
return this._lastSearchPromise
this.lastSearchPromise = Promise.resolve(calendarResult)
return this.lastSearchPromise
}
if (tokens.length > 0) {
@ -163,23 +162,23 @@ export class CalendarSearchModel {
if (this.cancelSignal()) {
this.result(calendarResult)
this._lastSearchPromise = Promise.resolve(calendarResult)
return this._lastSearchPromise
this.lastSearchPromise = Promise.resolve(calendarResult)
return this.lastSearchPromise
}
}
}
}
this.result(calendarResult)
this._lastSearchPromise = Promise.resolve(calendarResult)
this.lastSearchPromise = Promise.resolve(calendarResult)
}
return this._lastSearchPromise
return this.lastSearchPromise
}
isNewSearch(query: string, restriction: SearchRestriction): boolean {
let isNew = false
let lastQuery = this._lastQuery
let lastQuery = this.lastQuery
if (lastQuery == null) {
isNew = true
} else if (lastQuery.query !== query) {

View file

@ -15,7 +15,7 @@ import { KindaCalendarRow } from "../../gui/CalendarRow.js"
assertMainOrNode()
export class SearchResultListEntry {
export class CalendarSearchResultListEntry {
constructor(readonly entry: CalendarEvent) {}
get _id(): IdTuple {
@ -24,14 +24,14 @@ export class SearchResultListEntry {
}
export interface CalendarSearchListViewAttrs {
listModel: ListModel<SearchResultListEntry>
onSingleSelection: (item: SearchResultListEntry) => unknown
listModel: ListModel<CalendarSearchResultListEntry>
onSingleSelection: (item: CalendarSearchResultListEntry) => unknown
isFreeAccount: boolean
cancelCallback: () => unknown | null
}
export class CalendarSearchListView implements Component<CalendarSearchListViewAttrs> {
private listModel: ListModel<SearchResultListEntry>
private listModel: ListModel<CalendarSearchResultListEntry>
constructor({ attrs }: Vnode<CalendarSearchListViewAttrs>) {
this.listModel = attrs.listModel
@ -57,14 +57,14 @@ export class CalendarSearchListView implements Component<CalendarSearchListViewA
onRetryLoading: () => {
attrs.listModel?.retryLoading()
},
onSingleSelection: (item: SearchResultListEntry) => {
onSingleSelection: (item: CalendarSearchResultListEntry) => {
attrs.listModel?.onSingleSelection(item)
attrs.onSingleSelection(item)
},
onSingleTogglingMultiselection: (item: SearchResultListEntry) => {
onSingleTogglingMultiselection: (item: CalendarSearchResultListEntry) => {
attrs.listModel.onSingleInclusiveSelection(item, styles.isSingleColumnLayout())
},
onRangeSelectionTowards: (item: SearchResultListEntry) => {
onRangeSelectionTowards: (item: CalendarSearchResultListEntry) => {
attrs.listModel.selectRangeTowards(item)
},
onStopLoading() {
@ -74,10 +74,10 @@ export class CalendarSearchListView implements Component<CalendarSearchListViewA
attrs.listModel.stopLoading()
},
} satisfies ListAttrs<SearchResultListEntry, SearchResultListRow>)
} satisfies ListAttrs<CalendarSearchResultListEntry, SearchResultListRow>)
}
private readonly calendarRenderConfig: RenderConfig<SearchResultListEntry, SearchResultListRow> = {
private readonly calendarRenderConfig: RenderConfig<CalendarSearchResultListEntry, SearchResultListRow> = {
itemHeight: size.list_row_height,
multiselectionAllowed: MultiselectMode.Disabled,
swipe: null,
@ -89,14 +89,14 @@ export class CalendarSearchListView implements Component<CalendarSearchListViewA
}
}
export class SearchResultListRow implements VirtualRow<SearchResultListEntry> {
export class SearchResultListRow implements VirtualRow<CalendarSearchResultListEntry> {
top: number
// set from List
domElement: HTMLElement | null = null
// this is our own entry which we need for some reason (probably easier to deal with than a lot of sum type entries)
private _entity: SearchResultListEntry | null = null
get entity(): SearchResultListEntry | null {
private _entity: CalendarSearchResultListEntry | null = null
get entity(): CalendarSearchResultListEntry | null {
return this._entity
}
@ -107,7 +107,7 @@ export class SearchResultListRow implements VirtualRow<SearchResultListEntry> {
this.top = 0
}
update(entry: SearchResultListEntry, selected: boolean, isInMultiSelect: boolean): void {
update(entry: CalendarSearchResultListEntry, selected: boolean, isInMultiSelect: boolean): void {
this._delegate.domElement = this.domElement!
this._entity = entry

View file

@ -1,5 +1,5 @@
import { ListModel } from "../../../../common/misc/ListModel.js"
import { SearchResultListEntry } from "./CalendarSearchListView.js"
import { CalendarSearchResultListEntry } from "./CalendarSearchListView.js"
import { SearchRestriction, SearchResult } from "../../../../common/api/worker/search/SearchTypes.js"
import { EntityEventsListener, EventController } from "../../../../common/api/main/EventController.js"
import { CalendarEvent, CalendarEventTypeRef, Contact, Mail, MailTypeRef } from "../../../../common/api/entities/tutanota/TypeRefs.js"
@ -27,7 +27,6 @@ import { createRestriction, decodeCalendarSearchKey, encodeCalendarSearchKey, ge
import Stream from "mithril/stream"
import stream from "mithril/stream"
import { getStartOfTheWeekOffsetForUser } from "../../../../common/calendar/date/CalendarUtils.js"
import { SearchFacade } from "../../../../common/api/worker/search/SearchFacade.js"
import { LoginController } from "../../../../common/api/main/LoginController.js"
import { EntityClient } from "../../../../common/api/common/EntityClient.js"
import { containsEventOfType, EntityUpdateData, getEventOfType, isUpdateForTypeRef } from "../../../../common/api/common/utils/EntityUpdateUtils.js"
@ -39,11 +38,10 @@ import { ListAutoSelectBehavior } from "../../../../common/misc/DeviceConfig.js"
import { ProgrammingError } from "../../../../common/api/common/error/ProgrammingError.js"
import { SearchRouter } from "../../../../common/search/view/SearchRouter.js"
import { locator } from "../../../../common/api/main/CommonLocator.js"
import { SearchResultListEntry } from "../../../../mail-app/search/view/SearchListView.js"
const SEARCH_PAGE_SIZE = 100
export type SearchableTypes = Mail | Contact | CalendarEvent
export enum PaidFunctionResult {
Success,
PaidSubscriptionNeeded,
@ -52,7 +50,7 @@ export enum PaidFunctionResult {
export type ConfirmCallback = () => Promise<boolean>
export class CalendarSearchViewModel {
listModel: ListModel<SearchResultListEntry>
listModel: ListModel<CalendarSearchResultListEntry>
// Contains load more results even when searchModel doesn't.
// Load more should probably be moved to the model to update it's result stream.
@ -78,7 +76,6 @@ export class CalendarSearchViewModel {
constructor(
readonly router: SearchRouter,
private readonly search: CalendarSearchModel,
private readonly searchFacade: SearchFacade,
private readonly logins: LoginController,
private readonly entityClient: EntityClient,
private readonly eventController: EventController,
@ -224,7 +221,7 @@ export class CalendarSearchViewModel {
if (args.id != null) {
const { start, id } = decodeCalendarSearchKey(args.id)
this.loadAndSelectIfNeeded(id, ({ entry }: SearchResultListEntry) => {
this.loadAndSelectIfNeeded(id, ({ entry }: CalendarSearchResultListEntry) => {
entry = entry as CalendarEvent
return id === getElementId(entry) && start === entry.startTime.getTime()
})
@ -384,21 +381,20 @@ export class CalendarSearchViewModel {
.filter(assertIsEntity2(CalendarEventTypeRef))
}
private onListStateChange(newState: ListState<SearchResultListEntry>) {
private onListStateChange(newState: ListState<CalendarSearchResultListEntry>) {
this.updateSearchUrl()
this.updateUi()
}
private createList(): ListModel<SearchResultListEntry> {
private createList(): ListModel<CalendarSearchResultListEntry> {
// since we recreate the list every time we set a new result object,
// we bind the value of result for the lifetime of this list model
// at this point
// note in case of refactor: the fact that the list updates the URL every time it changes
// its state is a major source of complexity and makes everything very order-dependent
return new ListModel<SearchResultListEntry>({
fetch: async (lastFetchedEntity: SearchResultListEntry, count: number) => {
return new ListModel<CalendarSearchResultListEntry>({
fetch: async (lastFetchedEntity: CalendarSearchResultListEntry, count: number) => {
const startId = lastFetchedEntity == null ? GENERATED_MAX_ID : getElementId(lastFetchedEntity)
const lastResult = this._searchResult
if (lastResult !== this._searchResult) {
console.warn("got a fetch request for outdated results object, ignoring")
@ -411,7 +407,7 @@ export class CalendarSearchViewModel {
}
const { items, newSearchResult } = await this.loadSearchResults(lastResult, startId, count)
const entries = items.map((instance) => new SearchResultListEntry(instance))
const entries = items.map((instance) => new CalendarSearchResultListEntry(instance))
const complete = !hasMoreResults(newSearchResult)
return { items: entries, complete }
@ -425,7 +421,7 @@ export class CalendarSearchViewModel {
if (id) {
return this.entityClient
.load(lastResult.restriction.type, id)
.then((entity) => new SearchResultListEntry(entity))
.then((entity) => new CalendarSearchResultListEntry(entity))
.catch(
ofClass(NotFoundError, (_) => {
return null
@ -435,7 +431,7 @@ export class CalendarSearchViewModel {
return null
}
},
sortCompare: (o1: SearchResultListEntry, o2: SearchResultListEntry) =>
sortCompare: (o1: CalendarSearchResultListEntry, o2: CalendarSearchResultListEntry) =>
downcast(o1.entry).startTime.getTime() - downcast(o2.entry).startTime.getTime(),
autoSelectBehavior: () => ListAutoSelectBehavior.OLDER,
})
@ -464,7 +460,7 @@ export class CalendarSearchViewModel {
startId: Id,
count: number,
): Promise<{ items: CalendarEvent[]; newSearchResult: SearchResult }> {
const updatedResult = hasMoreResults(currentResult) ? await this.searchFacade.getMoreSearchResults(currentResult, count) : currentResult
const updatedResult = currentResult
// we need to override global reference for other functions
this._searchResult = updatedResult

View file

@ -32,9 +32,6 @@ import { WhitelabelSettingsViewer } from "../../../common/settings/whitelabel/Wh
import { SubscriptionViewer } from "../../../common/subscription/SubscriptionViewer.js"
import { PaymentViewer } from "../../../common/subscription/PaymentViewer.js"
import { ReferralSettingsViewer } from "../../../common/settings/ReferralSettingsViewer.js"
import { GroupDetailsView } from "../../../common/settings/groups/GroupDetailsView.js"
import { TemplateDetailsViewer } from "../../../mail-app/settings/TemplateDetailsViewer.js"
import { KnowledgeBaseSettingsDetailsViewer } from "../../../mail-app/settings/KnowledgeBaseListView.js"
import { NavButton, NavButtonAttrs, NavButtonColor } from "../../../common/gui/base/NavButton.js"
import { CustomerInfoTypeRef, CustomerTypeRef, User } from "../../../common/api/entities/sys/TypeRefs.js"
import { Dialog } from "../../../common/gui/base/Dialog.js"

View file

@ -20,8 +20,6 @@ import { CalendarFacade } from "../common/api/worker/facades/lazy/CalendarFacade
import { MailFacade } from "../common/api/worker/facades/lazy/MailFacade.js"
import { ShareFacade } from "../common/api/worker/facades/lazy/ShareFacade.js"
import { CounterFacade } from "../common/api/worker/facades/lazy/CounterFacade.js"
import { Indexer } from "../common/api/worker/search/Indexer.js"
import { SearchFacade } from "../common/api/worker/search/SearchFacade.js"
import { BookingFacade } from "../common/api/worker/facades/lazy/BookingFacade.js"
import { MailAddressFacade } from "../common/api/worker/facades/lazy/MailAddressFacade.js"
import { BlobFacade } from "../common/api/worker/facades/lazy/BlobFacade.js"
@ -42,7 +40,6 @@ import { InterWindowEventFacadeSendDispatcher } from "../common/native/common/ge
import { ExposedCacheStorage } from "../common/api/worker/rest/DefaultEntityRestCache.js"
import { WorkerFacade } from "../common/api/worker/facades/WorkerFacade.js"
import { PageContextLoginListener } from "../common/api/main/PageContextLoginListener.js"
import { WorkerRandomizer } from "../common/api/worker/WorkerImpl.js"
import { WebsocketConnectivityModel } from "../common/misc/WebsocketConnectivityModel.js"
import { OperationProgressTracker } from "../common/api/main/OperationProgressTracker.js"
import { InfoMessageHandler } from "../common/gui/InfoMessageHandler.js"
@ -111,6 +108,8 @@ import type { ParsedEvent } from "../common/calendar/import/CalendarImporter.js"
import { ExternalCalendarFacade } from "../common/native/common/generatedipc/ExternalCalendarFacade.js"
import { locator } from "../common/api/main/CommonLocator.js"
import m from "mithril"
import { DbError } from "../common/api/common/error/DbError.js"
import { WorkerRandomizer } from "../common/api/worker/workerInterfaces.js"
assertMainOrNode()
@ -137,8 +136,6 @@ class CalendarLocator {
mailFacade!: MailFacade
shareFacade!: ShareFacade
counterFacade!: CounterFacade
indexerFacade!: Indexer
searchFacade!: SearchFacade
bookingFacade!: BookingFacade
mailAddressFacade!: MailAddressFacade
blobFacade!: BlobFacade
@ -216,7 +213,6 @@ class CalendarLocator {
return new CalendarSearchViewModel(
searchRouter,
this.search,
this.searchFacade,
this.logins,
this.entityClient,
this.eventController,
@ -235,7 +231,6 @@ class CalendarLocator {
return new CalendarSearchViewModel(
searchRouter,
this.search,
this.searchFacade,
this.logins,
this.entityClient,
this.eventController,
@ -428,12 +423,12 @@ class CalendarLocator {
}
async ownMailAddressNameChanger(): Promise<MailAddressNameChanger> {
const { OwnMailAddressNameChanger } = await import("../mail-app/settings/mailaddress/OwnMailAddressNameChanger.js")
const { OwnMailAddressNameChanger } = await import("../common/settings/mailaddress/OwnMailAddressNameChanger.js")
return new OwnMailAddressNameChanger(this.mailboxModel, this.entityClient)
}
async adminNameChanger(mailGroupId: Id, userId: Id): Promise<MailAddressNameChanger> {
const { AnotherUserMailAddressNameChanger } = await import("../mail-app/settings/mailaddress/AnotherUserMailAddressNameChanger.js")
const { AnotherUserMailAddressNameChanger } = await import("../common/settings/mailaddress/AnotherUserMailAddressNameChanger.js")
return new AnotherUserMailAddressNameChanger(this.mailAddressFacade, mailGroupId, userId)
}
@ -453,7 +448,10 @@ class CalendarLocator {
const { NoopCredentialRemovalHandler, AppsCredentialRemovalHandler } = await import("../common/login/CredentialRemovalHandler.js")
return isBrowser()
? new NoopCredentialRemovalHandler()
: new AppsCredentialRemovalHandler(this.indexerFacade, this.pushService, this.configFacade, null)
: new AppsCredentialRemovalHandler(this.pushService, this.configFacade, async () => {
// nothing needs to be specifically done for the calendar app right now.
noOp()
})
}
async loginViewModelFactory(): Promise<lazy<LoginViewModel>> {
@ -526,8 +524,6 @@ class CalendarLocator {
mailFacade,
shareFacade,
counterFacade,
indexerFacade,
searchFacade,
bookingFacade,
mailAddressFacade,
blobFacade,
@ -553,8 +549,6 @@ class CalendarLocator {
this.mailFacade = mailFacade
this.shareFacade = shareFacade
this.counterFacade = counterFacade
this.indexerFacade = indexerFacade
this.searchFacade = searchFacade
this.bookingFacade = bookingFacade
this.mailAddressFacade = mailAddressFacade
this.blobFacade = blobFacade
@ -707,7 +701,9 @@ class CalendarLocator {
: new FileControllerNative(blobFacade, guiDownload, this.nativeInterfaces.fileApp)
const { ContactModel } = await import("../common/contactsFunctionality/ContactModel.js")
this.contactModel = new ContactModel(this.searchFacade, this.entityClient, this.logins, this.eventController)
this.contactModel = new ContactModel(this.entityClient, this.logins, this.eventController, () => {
throw new DbError("Calendar cannot search for contacts through db")
})
this.appStorePaymentPicker = new AppStorePaymentPicker()
// THEME

View file

@ -0,0 +1,216 @@
import type { Commands } from "../../../common/api/common/threading/MessageDispatcher.js"
import { errorToObj, MessageDispatcher, Request } from "../../../common/api/common/threading/MessageDispatcher.js"
import { NotAuthenticatedError } from "../../../common/api/common/error/RestError.js"
import { ProgrammingError } from "../../../common/api/common/error/ProgrammingError.js"
import { initLocator, locator, resetLocator } from "./CalendarWorkerLocator.js"
import { assertWorkerOrNode, isMainOrNode } from "../../../common/api/common/Env.js"
import type { BrowserData } from "../../../common/misc/ClientConstants.js"
import { DelayedImpls, exposeLocalDelayed, exposeRemote } from "../../../common/api/common/WorkerProxy.js"
import { random } from "@tutao/tutanota-crypto"
import type { NativeInterface } from "../../../common/native/common/NativeInterface.js"
import { WebWorkerTransport } from "../../../common/api/common/threading/Transport.js"
import { CommonWorkerInterface, MainInterface } from "../../../common/api/worker/workerInterfaces.js"
import { CryptoError } from "@tutao/tutanota-crypto/error.js"
assertWorkerOrNode()
type WorkerRequest = Request<WorkerRequestType>
export class CalendarWorkerImpl implements NativeInterface {
private readonly _scope: DedicatedWorkerGlobalScope
private readonly _dispatcher: MessageDispatcher<MainRequestType, WorkerRequestType>
constructor(self: DedicatedWorkerGlobalScope) {
this._scope = self
this._dispatcher = new MessageDispatcher(new WebWorkerTransport(this._scope), this.queueCommands(this.exposedInterface), "worker-main")
}
async init(browserData: BrowserData): Promise<void> {
await initLocator(this, browserData)
const workerScope = this._scope
// only register oncaught error handler if we are in the *real* worker scope
// Otherwise uncaught error handler might end up in an infinite loop for test cases.
if (workerScope && !isMainOrNode()) {
workerScope.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => {
this.sendError(event.reason)
})
// @ts-ignore
workerScope.onerror = (e: string | Event, source, lineno, colno, error) => {
console.error("workerImpl.onerror", e, source, lineno, colno, error)
if (error instanceof Error) {
this.sendError(error)
} else {
// @ts-ignore
const err = new Error(e)
// @ts-ignore
err.lineNumber = lineno
// @ts-ignore
err.columnNumber = colno
// @ts-ignore
err.fileName = source
this.sendError(err)
}
return true
}
}
}
get exposedInterface(): DelayedImpls<CommonWorkerInterface> {
return {
async loginFacade() {
return locator.login
},
async customerFacade() {
return locator.customer()
},
async giftCardFacade() {
return locator.giftCards()
},
async groupManagementFacade() {
return locator.groupManagement()
},
async configFacade() {
return locator.configFacade()
},
async calendarFacade() {
return locator.calendar()
},
async mailFacade() {
return locator.mail()
},
async shareFacade() {
return locator.share()
},
async cacheManagementFacade() {
return locator.cacheManagement()
},
async counterFacade() {
return locator.counters()
},
async bookingFacade() {
return locator.booking()
},
async mailAddressFacade() {
return locator.mailAddress()
},
async blobAccessTokenFacade() {
return locator.blobAccessToken
},
async blobFacade() {
return locator.blob()
},
async userManagementFacade() {
return locator.userManagement()
},
async recoverCodeFacade() {
return locator.recoverCode()
},
async restInterface() {
return locator.cache
},
async serviceExecutor() {
return locator.serviceExecutor
},
async cryptoFacade() {
return locator.crypto
},
async cacheStorage() {
return locator.cacheStorage
},
async sqlCipherFacade() {
return locator.sqlCipherFacade
},
async random() {
return {
async generateRandomNumber(nbrOfBytes: number) {
return random.generateRandomNumber(nbrOfBytes)
},
}
},
async eventBus() {
return locator.eventBusClient
},
async entropyFacade() {
return locator.entropyFacade
},
async workerFacade() {
return locator.workerFacade
},
async contactFacade() {
return locator.contactFacade()
},
}
}
queueCommands(exposedWorker: DelayedImpls<CommonWorkerInterface>): Commands<WorkerRequestType> {
return {
setup: async (message) => {
console.error("WorkerImpl: setup was called after bootstrap! message: ", message)
},
testEcho: (message) =>
Promise.resolve({
msg: ">>> " + message.args[0].msg,
}),
testError: (message) => {
const errorTypes = {
ProgrammingError,
CryptoError,
NotAuthenticatedError,
}
// @ts-ignore
let ErrorType = errorTypes[message.args[0].errorType]
return Promise.reject(new ErrorType(`wtf: ${message.args[0].errorType}`))
},
reset: (message: WorkerRequest) => {
return resetLocator()
},
restRequest: (message: WorkerRequest) => {
// Rest Requests only come from the admin client, not needed here
return Promise.reject(new Error(`restRequest is not implemented for Calendar Worker`))
},
facade: exposeLocalDelayed<DelayedImpls<CommonWorkerInterface>, WorkerRequestType>(exposedWorker),
}
}
invokeNative(requestType: string, args: ReadonlyArray<unknown>): Promise<any> {
return this._dispatcher.postRequest(new Request("execNative", [requestType, args]))
}
getMainInterface(): MainInterface {
return exposeRemote<MainInterface>((request) => this._dispatcher.postRequest(request))
}
sendError(e: Error): Promise<void> {
return this._dispatcher.postRequest(new Request("error", [errorToObj(e)]))
}
}

View file

@ -0,0 +1,456 @@
import { CacheInfo, LoginFacade, LoginListener } from "../../../common/api/worker/facades/LoginFacade.js"
import type { EntityRestInterface } from "../../../common/api/worker/rest/EntityRestClient.js"
import { EntityRestClient } from "../../../common/api/worker/rest/EntityRestClient.js"
import type { UserManagementFacade } from "../../../common/api/worker/facades/lazy/UserManagementFacade.js"
import { CacheStorage, DefaultEntityRestCache, EntityRestCache } from "../../../common/api/worker/rest/DefaultEntityRestCache.js"
import type { GroupManagementFacade } from "../../../common/api/worker/facades/lazy/GroupManagementFacade.js"
import type { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade.js"
import type { MailAddressFacade } from "../../../common/api/worker/facades/lazy/MailAddressFacade.js"
import type { CustomerFacade } from "../../../common/api/worker/facades/lazy/CustomerFacade.js"
import type { CounterFacade } from "../../../common/api/worker/facades/lazy/CounterFacade.js"
import { EventBusClient } from "../../../common/api/worker/EventBusClient.js"
import { assertWorkerOrNode, getWebsocketBaseUrl, isAndroidApp, isBrowser, isIOSApp, isOfflineStorageAvailable } from "../../../common/api/common/Env.js"
import { Const } from "../../../common/api/common/TutanotaConstants.js"
import type { BrowserData } from "../../../common/misc/ClientConstants.js"
import type { CalendarFacade } from "../../../common/api/worker/facades/lazy/CalendarFacade.js"
import type { ShareFacade } from "../../../common/api/worker/facades/lazy/ShareFacade.js"
import { RestClient } from "../../../common/api/worker/rest/RestClient.js"
import { SuspensionHandler } from "../../../common/api/worker/SuspensionHandler.js"
import { EntityClient } from "../../../common/api/common/EntityClient.js"
import type { GiftCardFacade } from "../../../common/api/worker/facades/lazy/GiftCardFacade.js"
import type { ConfigurationDatabase } from "../../../common/api/worker/facades/lazy/ConfigurationDatabase.js"
import { DeviceEncryptionFacade } from "../../../common/api/worker/facades/DeviceEncryptionFacade.js"
import type { NativeInterface } from "../../../common/native/common/NativeInterface.js"
import { NativeFileApp } from "../../../common/native/common/FileApp.js"
import { AesApp } from "../../../common/native/worker/AesApp.js"
import type { RsaImplementation } from "../../../common/api/worker/crypto/RsaImplementation.js"
import { createRsaImplementation } from "../../../common/api/worker/crypto/RsaImplementation.js"
import { CryptoFacade } from "../../../common/api/worker/crypto/CryptoFacade.js"
import { InstanceMapper } from "../../../common/api/worker/crypto/InstanceMapper.js"
import { SleepDetector } from "../../../common/api/worker/utils/SleepDetector.js"
import { SchedulerImpl } from "../../../common/api/common/utils/Scheduler.js"
import { NoZoneDateProvider } from "../../../common/api/common/utils/NoZoneDateProvider.js"
import { LateInitializedCacheStorageImpl } from "../../../common/api/worker/rest/CacheStorageProxy.js"
import { IServiceExecutor } from "../../../common/api/common/ServiceRequest.js"
import { ServiceExecutor } from "../../../common/api/worker/rest/ServiceExecutor.js"
import type { BookingFacade } from "../../../common/api/worker/facades/lazy/BookingFacade.js"
import type { BlobFacade } from "../../../common/api/worker/facades/lazy/BlobFacade.js"
import { UserFacade } from "../../../common/api/worker/facades/UserFacade.js"
import { OfflineStorage } from "../../../common/api/worker/offline/OfflineStorage.js"
import { OFFLINE_STORAGE_MIGRATIONS, OfflineStorageMigrator } from "../../../common/api/worker/offline/OfflineStorageMigrator.js"
import { modelInfos } from "../../../common/api/common/EntityFunctions.js"
import { FileFacadeSendDispatcher } from "../../../common/native/common/generatedipc/FileFacadeSendDispatcher.js"
import { NativePushFacadeSendDispatcher } from "../../../common/native/common/generatedipc/NativePushFacadeSendDispatcher.js"
import { NativeCryptoFacadeSendDispatcher } from "../../../common/native/common/generatedipc/NativeCryptoFacadeSendDispatcher.js"
import { random } from "@tutao/tutanota-crypto"
import { ExportFacadeSendDispatcher } from "../../../common/native/common/generatedipc/ExportFacadeSendDispatcher.js"
import { lazyAsync, lazyMemoized, noOp } from "@tutao/tutanota-utils"
import { InterWindowEventFacadeSendDispatcher } from "../../../common/native/common/generatedipc/InterWindowEventFacadeSendDispatcher.js"
import { SqlCipherFacadeSendDispatcher } from "../../../common/native/common/generatedipc/SqlCipherFacadeSendDispatcher.js"
import { EntropyFacade } from "../../../common/api/worker/facades/EntropyFacade.js"
import { BlobAccessTokenFacade } from "../../../common/api/worker/facades/BlobAccessTokenFacade.js"
import { OwnerEncSessionKeysUpdateQueue } from "../../../common/api/worker/crypto/OwnerEncSessionKeysUpdateQueue.js"
import { EventBusEventCoordinator } from "../../../common/api/worker/EventBusEventCoordinator.js"
import { WorkerFacade } from "../../../common/api/worker/facades/WorkerFacade.js"
import { SqlCipherFacade } from "../../../common/native/common/generatedipc/SqlCipherFacade.js"
import { Challenge } from "../../../common/api/entities/sys/TypeRefs.js"
import { LoginFailReason } from "../../../common/api/main/PageContextLoginListener.js"
import { SessionType } from "../../../common/api/common/SessionType.js"
import { Argon2idFacade, NativeArgon2idFacade, WASMArgon2idFacade } from "../../../common/api/worker/facades/Argon2idFacade.js"
import { DomainConfigProvider } from "../../../common/api/common/DomainConfigProvider.js"
import { KyberFacade, NativeKyberFacade, WASMKyberFacade } from "../../../common/api/worker/facades/KyberFacade.js"
import { PQFacade } from "../../../common/api/worker/facades/PQFacade.js"
import { PdfWriter } from "../../../common/api/worker/pdf/PdfWriter.js"
import { ContactFacade } from "../../../common/api/worker/facades/lazy/ContactFacade.js"
import { KeyLoaderFacade } from "../../../common/api/worker/facades/KeyLoaderFacade.js"
import { KeyRotationFacade } from "../../../common/api/worker/facades/KeyRotationFacade.js"
import { KeyCache } from "../../../common/api/worker/facades/KeyCache.js"
import { cryptoWrapper } from "../../../common/api/worker/crypto/CryptoWrapper.js"
import { RecoverCodeFacade } from "../../../common/api/worker/facades/lazy/RecoverCodeFacade.js"
import { CacheManagementFacade } from "../../../common/api/worker/facades/lazy/CacheManagementFacade.js"
import { CalendarWorkerImpl } from "./CalendarWorkerImpl.js"
import { CalendarOfflineCleaner } from "../../offline/CalendarOfflineCleaner.js"
import type { QueuedBatch } from "../../../common/api/worker/EventQueue.js"
import { Credentials } from "../../../common/misc/credentials/Credentials.js"
assertWorkerOrNode()
export type CalendarWorkerLocatorType = {
// network & encryption
restClient: RestClient
serviceExecutor: IServiceExecutor
crypto: CryptoFacade
instanceMapper: InstanceMapper
cacheStorage: CacheStorage
cache: EntityRestInterface
cachingEntityClient: EntityClient
eventBusClient: EventBusClient
rsa: RsaImplementation
kyberFacade: KyberFacade
pqFacade: PQFacade
entropyFacade: EntropyFacade
blobAccessToken: BlobAccessTokenFacade
keyCache: KeyCache
keyLoader: KeyLoaderFacade
keyRotation: KeyRotationFacade
// login
user: UserFacade
login: LoginFacade
// domains
blob: lazyAsync<BlobFacade>
mail: lazyAsync<MailFacade>
calendar: lazyAsync<CalendarFacade>
counters: lazyAsync<CounterFacade>
Const: Record<string, any>
// management facades
groupManagement: lazyAsync<GroupManagementFacade>
userManagement: lazyAsync<UserManagementFacade>
recoverCode: lazyAsync<RecoverCodeFacade>
customer: lazyAsync<CustomerFacade>
giftCards: lazyAsync<GiftCardFacade>
mailAddress: lazyAsync<MailAddressFacade>
booking: lazyAsync<BookingFacade>
share: lazyAsync<ShareFacade>
cacheManagement: lazyAsync<CacheManagementFacade>
// misc & native
configFacade: lazyAsync<ConfigurationDatabase>
deviceEncryptionFacade: DeviceEncryptionFacade
native: NativeInterface
workerFacade: WorkerFacade
sqlCipherFacade: SqlCipherFacade
pdfWriter: lazyAsync<PdfWriter>
// used to cache between resets
_worker: CalendarWorkerImpl
_browserData: BrowserData
//contact
contactFacade: lazyAsync<ContactFacade>
}
export const locator: CalendarWorkerLocatorType = {} as any
export async function initLocator(worker: CalendarWorkerImpl, browserData: BrowserData) {
locator._worker = worker
locator._browserData = browserData
locator.keyCache = new KeyCache()
locator.user = new UserFacade(locator.keyCache)
locator.workerFacade = new WorkerFacade()
const dateProvider = new NoZoneDateProvider()
const mainInterface = worker.getMainInterface()
const suspensionHandler = new SuspensionHandler(mainInterface.infoMessageHandler, self)
locator.instanceMapper = new InstanceMapper()
locator.rsa = await createRsaImplementation(worker)
const domainConfig = new DomainConfigProvider().getCurrentDomainConfig()
locator.restClient = new RestClient(suspensionHandler, domainConfig)
locator.serviceExecutor = new ServiceExecutor(locator.restClient, locator.user, locator.instanceMapper, () => locator.crypto)
locator.entropyFacade = new EntropyFacade(locator.user, locator.serviceExecutor, random, () => locator.keyLoader)
locator.blobAccessToken = new BlobAccessTokenFacade(locator.serviceExecutor, dateProvider, locator.user)
const entityRestClient = new EntityRestClient(locator.user, locator.restClient, () => locator.crypto, locator.instanceMapper, locator.blobAccessToken)
locator.native = worker
locator.booking = lazyMemoized(async () => {
const { BookingFacade } = await import("../../../common/api/worker/facades/lazy/BookingFacade.js")
return new BookingFacade(locator.serviceExecutor)
})
let offlineStorageProvider
if (isOfflineStorageAvailable()) {
locator.sqlCipherFacade = new SqlCipherFacadeSendDispatcher(locator.native)
offlineStorageProvider = async () => {
return new OfflineStorage(
locator.sqlCipherFacade,
new InterWindowEventFacadeSendDispatcher(worker),
dateProvider,
new OfflineStorageMigrator(OFFLINE_STORAGE_MIGRATIONS, modelInfos),
new CalendarOfflineCleaner(),
)
}
} else {
offlineStorageProvider = async () => null
}
locator.pdfWriter = async () => {
const { PdfWriter } = await import("../../../common/api/worker/pdf/PdfWriter.js")
return new PdfWriter(new TextEncoder(), undefined)
}
const maybeUninitializedStorage = new LateInitializedCacheStorageImpl(async (error: Error) => {
await worker.sendError(error)
}, offlineStorageProvider)
locator.cacheStorage = maybeUninitializedStorage
const fileApp = new NativeFileApp(new FileFacadeSendDispatcher(worker), new ExportFacadeSendDispatcher(worker))
locator.cache = new DefaultEntityRestCache(entityRestClient, maybeUninitializedStorage)
locator.cachingEntityClient = new EntityClient(locator.cache)
const nonCachingEntityClient = new EntityClient(entityRestClient)
locator.cacheManagement = lazyMemoized(async () => {
const { CacheManagementFacade } = await import("../../../common/api/worker/facades/lazy/CacheManagementFacade.js")
return new CacheManagementFacade(locator.user, locator.cachingEntityClient, locator.cache as DefaultEntityRestCache)
})
if (isIOSApp() || isAndroidApp()) {
locator.kyberFacade = new NativeKyberFacade(new NativeCryptoFacadeSendDispatcher(worker))
} else {
locator.kyberFacade = new WASMKyberFacade()
}
locator.pqFacade = new PQFacade(locator.kyberFacade)
locator.keyLoader = new KeyLoaderFacade(locator.keyCache, locator.user, locator.cachingEntityClient, locator.cacheManagement)
locator.crypto = new CryptoFacade(
locator.user,
locator.cachingEntityClient,
locator.restClient,
locator.rsa,
locator.serviceExecutor,
locator.instanceMapper,
new OwnerEncSessionKeysUpdateQueue(locator.user, locator.serviceExecutor),
locator.pqFacade,
locator.cache as DefaultEntityRestCache,
locator.keyLoader,
)
locator.recoverCode = lazyMemoized(async () => {
const { RecoverCodeFacade } = await import("../../../common/api/worker/facades/lazy/RecoverCodeFacade.js")
return new RecoverCodeFacade(locator.user, locator.cachingEntityClient, locator.login, locator.keyLoader)
})
locator.share = lazyMemoized(async () => {
const { ShareFacade } = await import("../../../common/api/worker/facades/lazy/ShareFacade.js")
return new ShareFacade(locator.user, locator.crypto, locator.serviceExecutor, locator.cachingEntityClient, locator.keyLoader)
})
locator.counters = lazyMemoized(async () => {
const { CounterFacade } = await import("../../../common/api/worker/facades/lazy/CounterFacade.js")
return new CounterFacade(locator.serviceExecutor)
})
locator.groupManagement = lazyMemoized(async () => {
const { GroupManagementFacade } = await import("../../../common/api/worker/facades/lazy/GroupManagementFacade.js")
return new GroupManagementFacade(
locator.user,
await locator.counters(),
locator.cachingEntityClient,
locator.serviceExecutor,
locator.pqFacade,
locator.keyLoader,
await locator.cacheManagement(),
)
})
locator.keyRotation = new KeyRotationFacade(
locator.cachingEntityClient,
locator.keyLoader,
locator.pqFacade,
locator.serviceExecutor,
cryptoWrapper,
locator.recoverCode,
locator.user,
locator.crypto,
locator.share,
locator.groupManagement,
)
const loginListener: LoginListener = {
onFullLoginSuccess(sessionType: SessionType, cacheInfo: CacheInfo, credentials: Credentials): Promise<void> {
return mainInterface.loginListener.onFullLoginSuccess(sessionType, cacheInfo, credentials)
},
onLoginFailure(reason: LoginFailReason): Promise<void> {
return mainInterface.loginListener.onLoginFailure(reason)
},
onSecondFactorChallenge(sessionId: IdTuple, challenges: ReadonlyArray<Challenge>, mailAddress: string | null): Promise<void> {
return mainInterface.loginListener.onSecondFactorChallenge(sessionId, challenges, mailAddress)
},
}
let argon2idFacade: Argon2idFacade
if (!isBrowser()) {
argon2idFacade = new NativeArgon2idFacade(new NativeCryptoFacadeSendDispatcher(worker))
} else {
argon2idFacade = new WASMArgon2idFacade()
}
locator.deviceEncryptionFacade = new DeviceEncryptionFacade()
const { DatabaseKeyFactory } = await import("../../../common/misc/credentials/DatabaseKeyFactory.js")
locator.login = new LoginFacade(
locator.restClient,
/**
* we don't want to try to use the cache in the login facade, because it may not be available (when no user is logged in)
*/
new EntityClient(locator.cache),
loginListener,
locator.instanceMapper,
locator.crypto,
locator.keyRotation,
maybeUninitializedStorage,
locator.serviceExecutor,
locator.user,
locator.blobAccessToken,
locator.entropyFacade,
new DatabaseKeyFactory(locator.deviceEncryptionFacade),
argon2idFacade,
nonCachingEntityClient,
async (error: Error) => {
await worker.sendError(error)
},
)
locator.userManagement = lazyMemoized(async () => {
const { UserManagementFacade } = await import("../../../common/api/worker/facades/lazy/UserManagementFacade.js")
return new UserManagementFacade(
locator.user,
await locator.groupManagement(),
await locator.counters(),
locator.cachingEntityClient,
locator.serviceExecutor,
mainInterface.operationProgressTracker,
locator.login,
locator.pqFacade,
locator.keyLoader,
await locator.recoverCode(),
)
})
locator.customer = lazyMemoized(async () => {
const { CustomerFacade } = await import("../../../common/api/worker/facades/lazy/CustomerFacade.js")
return new CustomerFacade(
locator.user,
await locator.groupManagement(),
await locator.userManagement(),
await locator.counters(),
locator.rsa,
locator.cachingEntityClient,
locator.serviceExecutor,
await locator.booking(),
locator.crypto,
mainInterface.operationProgressTracker,
locator.pdfWriter,
locator.pqFacade,
locator.keyLoader,
await locator.recoverCode(),
)
})
const aesApp = new AesApp(new NativeCryptoFacadeSendDispatcher(worker), random)
locator.blob = lazyMemoized(async () => {
const { BlobFacade } = await import("../../../common/api/worker/facades/lazy/BlobFacade.js")
return new BlobFacade(
locator.user,
locator.serviceExecutor,
locator.restClient,
suspensionHandler,
fileApp,
aesApp,
locator.instanceMapper,
locator.crypto,
locator.blobAccessToken,
locator.cache as DefaultEntityRestCache,
)
})
locator.mail = lazyMemoized(async () => {
const { MailFacade } = await import("../../../common/api/worker/facades/lazy/MailFacade.js")
return new MailFacade(
locator.user,
locator.cachingEntityClient,
locator.crypto,
locator.serviceExecutor,
await locator.blob(),
fileApp,
locator.login,
locator.keyLoader,
)
})
const nativePushFacade = new NativePushFacadeSendDispatcher(worker)
locator.calendar = lazyMemoized(async () => {
const { CalendarFacade } = await import("../../../common/api/worker/facades/lazy/CalendarFacade.js")
return new CalendarFacade(
locator.user,
await locator.groupManagement(),
locator.cache as DefaultEntityRestCache,
nonCachingEntityClient, // without cache
nativePushFacade,
mainInterface.operationProgressTracker,
locator.instanceMapper,
locator.serviceExecutor,
locator.crypto,
mainInterface.infoMessageHandler,
)
})
locator.mailAddress = lazyMemoized(async () => {
const { MailAddressFacade } = await import("../../../common/api/worker/facades/lazy/MailAddressFacade.js")
return new MailAddressFacade(
locator.user,
await locator.groupManagement(),
locator.serviceExecutor,
nonCachingEntityClient, // without cache
)
})
const scheduler = new SchedulerImpl(dateProvider, self, self)
locator.configFacade = lazyMemoized(async () => {
const { ConfigurationDatabase } = await import("../../../common/api/worker/facades/lazy/ConfigurationDatabase.js")
return new ConfigurationDatabase(locator.keyLoader, locator.user)
})
const eventBusCoordinator = new EventBusEventCoordinator(
mainInterface.wsConnectivityListener,
locator.mail,
locator.user,
locator.cachingEntityClient,
mainInterface.eventController,
locator.configFacade,
locator.keyRotation,
locator.cacheManagement,
async (error: Error) => {
await worker.sendError(error)
},
(queuedBatch: QueuedBatch[]) => noOp,
)
locator.eventBusClient = new EventBusClient(
eventBusCoordinator,
locator.cache as EntityRestCache,
locator.user,
locator.cachingEntityClient,
locator.instanceMapper,
(path) => new WebSocket(getWebsocketBaseUrl(domainConfig) + path),
new SleepDetector(scheduler, dateProvider),
mainInterface.progressTracker,
)
locator.login.init(locator.eventBusClient)
locator.Const = Const
locator.giftCards = lazyMemoized(async () => {
const { GiftCardFacade } = await import("../../../common/api/worker/facades/lazy/GiftCardFacade.js")
return new GiftCardFacade(locator.user, await locator.customer(), locator.serviceExecutor, locator.crypto, locator.keyLoader)
})
locator.contactFacade = lazyMemoized(async () => {
const { ContactFacade } = await import("../../../common/api/worker/facades/lazy/ContactFacade.js")
return new ContactFacade(new EntityClient(locator.cache))
})
}
export async function resetLocator(): Promise<void> {
await locator.login.resetSession()
await initLocator(locator._worker, locator._browserData)
}
if (typeof self !== "undefined") {
;(self as unknown as WorkerGlobalScope).locator = locator // export in worker scope
}
/*
* @returns true if webassembly is supported
*/
export function isWebAssemblySupported() {
return typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function"
}

View file

@ -1,6 +1,5 @@
import { WorkerImpl } from "../common/api/worker/WorkerImpl"
import { Logger, replaceNativeLogger } from "../common/api/common/Logger.js"
import { CalendarOfflineCleaner } from "./offline/CalendarOfflineCleaner.js"
import { Logger, replaceNativeLogger } from "../../../common/api/common/Logger.js"
import { CalendarWorkerImpl } from "./CalendarWorkerImpl.js"
/**
* Receives the first message from the client and initializes the WorkerImpl to receive all future messages. Sends a response to the client on this first message.
@ -21,8 +20,8 @@ self.onmessage = function (msg) {
}
// @ts-ignore
const workerImpl = new WorkerImpl(typeof self !== "undefined" ? self : null)
await workerImpl.init(browserData, new CalendarOfflineCleaner())
const workerImpl = new CalendarWorkerImpl(typeof self !== "undefined" ? self : null)
await workerImpl.init(browserData)
workerImpl.exposedInterface.entropyFacade().then((entropyFacade) => entropyFacade.addEntropy(initialRandomizerEntropy))
self.postMessage({
id: data.id,

View file

@ -8,7 +8,6 @@ import { SystemPermissionHandler } from "../../native/main/SystemPermissionHandl
import { SecondFactorHandler } from "../../misc/2fa/SecondFactorHandler.js"
import { PageContextLoginListener } from "./PageContextLoginListener.js"
import { NewsModel } from "../../misc/news/NewsModel.js"
import { SearchModel } from "../../../mail-app/search/model/SearchModel.js"
import { InfoMessageHandler } from "../../gui/InfoMessageHandler.js"
import { SettingsFacade } from "../../native/common/generatedipc/SettingsFacade.js"
import { DesktopSystemFacade } from "../../native/common/generatedipc/DesktopSystemFacade.js"
@ -22,8 +21,6 @@ import type { CalendarFacade } from "../worker/facades/lazy/CalendarFacade.js"
import type { MailFacade } from "../worker/facades/lazy/MailFacade.js"
import type { ShareFacade } from "../worker/facades/lazy/ShareFacade.js"
import type { CounterFacade } from "../worker/facades/lazy/CounterFacade.js"
import type { Indexer } from "../worker/search/Indexer.js"
import type { SearchFacade } from "../worker/search/SearchFacade.js"
import type { BookingFacade } from "../worker/facades/lazy/BookingFacade.js"
import type { MailAddressFacade } from "../worker/facades/lazy/MailAddressFacade.js"
import type { BlobFacade } from "../worker/facades/lazy/BlobFacade.js"
@ -34,7 +31,6 @@ import { IServiceExecutor } from "../common/ServiceRequest.js"
import { CryptoFacade } from "../worker/crypto/CryptoFacade.js"
import { ExposedCacheStorage } from "../worker/rest/DefaultEntityRestCache.js"
import { WorkerFacade } from "../worker/facades/WorkerFacade.js"
import { WorkerRandomizer } from "../worker/WorkerImpl.js"
import { WebsocketConnectivityModel } from "../../misc/WebsocketConnectivityModel.js"
import type { MailboxDetail, MailboxModel } from "../../mailFunctionality/MailboxModel.js"
import { EventController } from "./EventController.js"
@ -51,8 +47,6 @@ import { MailAddressTableModel } from "../../settings/mailaddress/MailAddressTab
import { GroupInfo } from "../entities/sys/TypeRefs.js"
import { lazy } from "@tutao/tutanota-utils"
import { Router } from "../../gui/ScopedRouter.js"
import type { IMailLocator } from "../../../mail-app/mailLocator.js"
import type { ICalendarLocator } from "../../../calendar-app/calendarLocator.js"
import { NativeInterfaceMain } from "../../native/main/NativeInterfaceMain.js"
import { CommonSystemFacade } from "../../native/common/generatedipc/CommonSystemFacade.js"
import { ThemeFacade } from "../../native/common/generatedipc/ThemeFacade.js"
@ -69,9 +63,10 @@ import { CalendarEventModel, CalendarOperation } from "../../../calendar-app/cal
import { CalendarEventPreviewViewModel } from "../../../calendar-app/calendar/gui/eventpopup/CalendarEventPreviewViewModel.js"
import { RecipientsModel } from "./RecipientsModel.js"
import { ThemeController } from "../../gui/ThemeController.js"
import { CalendarSearchModel } from "../../../calendar-app/calendar/search/model/CalendarSearchModel.js"
import { MobilePaymentsFacade } from "../../native/common/generatedipc/MobilePaymentsFacade.js"
import { AppStorePaymentPicker } from "../../misc/AppStorePaymentPicker.js"
import { WorkerRandomizer } from "../worker/workerInterfaces.js"
import { CommonSearchModel } from "../../search/CommonSearchModel.js"
export interface CommonLocator {
worker: WorkerClient
@ -84,7 +79,7 @@ export interface CommonLocator {
secondFactorHandler: SecondFactorHandler
loginListener: PageContextLoginListener
newsModel: NewsModel
search: SearchModel | CalendarSearchModel
search: CommonSearchModel
infoMessageHandler: InfoMessageHandler
desktopSettingsFacade: SettingsFacade
desktopSystemFacade: DesktopSystemFacade
@ -100,8 +95,6 @@ export interface CommonLocator {
mailFacade: MailFacade
shareFacade: ShareFacade
counterFacade: CounterFacade
indexerFacade: Indexer
searchFacade: SearchFacade
bookingFacade: BookingFacade
mailAddressFacade: MailAddressFacade
blobFacade: BlobFacade
@ -177,6 +170,6 @@ export let locator: CommonLocator = new Proxy<CommonLocator>({} as unknown as Co
},
})
export function initCommonLocator(loc: IMailLocator | ICalendarLocator) {
export function initCommonLocator(loc: CommonLocator) {
locator = loc
}

View file

@ -7,7 +7,7 @@ import { PartialRecipient, Recipient, RecipientType } from "../common/recipients
import { BoundedExecutor, LazyLoaded } from "@tutao/tutanota-utils"
import { Contact, ContactTypeRef } from "../entities/tutanota/TypeRefs"
import { cleanMailAddress } from "../common/utils/CommonCalendarUtils.js"
import { createNewContact, isTutanotaMailAddress } from "../../mailFunctionality/SharedMailUtils.js"
import { createNewContact, isTutaMailAddress } from "../../mailFunctionality/SharedMailUtils.js"
/**
* A recipient that can be resolved to obtain contact and recipient type
@ -102,7 +102,7 @@ class ResolvableRecipientImpl implements ResolvableRecipient {
private readonly entityClient: EntityClient,
resolveMode: ResolveMode,
) {
if (isTutanotaMailAddress(arg.address) || arg.type === RecipientType.INTERNAL) {
if (isTutaMailAddress(arg.address) || arg.type === RecipientType.INTERNAL) {
this.initialType = RecipientType.INTERNAL
this._address = cleanMailAddress(arg.address)
} else if (arg.type) {

View file

@ -6,12 +6,12 @@ import { client } from "../../misc/ClientDetector"
import type { DeferredObject } from "@tutao/tutanota-utils"
import { defer, downcast } from "@tutao/tutanota-utils"
import { handleUncaughtError } from "../../misc/ErrorHandler"
import type { MainInterface, WorkerInterface } from "../worker/WorkerImpl"
import { DelayedImpls, exposeLocalDelayed, exposeRemote } from "../common/WorkerProxy"
import type { RestClient } from "../worker/rest/RestClient"
import { EntropyDataChunk } from "../worker/facades/EntropyFacade.js"
import { objToError } from "../common/utils/ErrorUtils.js"
import { CommonLocator } from "./CommonLocator.js"
import { CommonWorkerInterface, MainInterface } from "../worker/workerInterfaces.js"
assertMainOrNode()
@ -109,8 +109,8 @@ export class WorkerClient {
}
}
getWorkerInterface(): WorkerInterface {
return exposeRemote<WorkerInterface>(async (request) => this._postRequest(request))
getWorkerInterface(): CommonWorkerInterface {
return exposeRemote<CommonWorkerInterface>(async (request) => this._postRequest(request))
}
restRequest(...args: Parameters<RestClient["request"]>): Promise<any | null> {

View file

@ -10,10 +10,8 @@ import {
} from "../entities/sys/TypeRefs.js"
import { ReportedMailFieldMarker } from "../entities/tutanota/TypeRefs.js"
import { WebsocketConnectivityListener } from "../../misc/WebsocketConnectivityModel.js"
import { WorkerImpl } from "./WorkerImpl.js"
import { isAdminClient, isTest } from "../common/Env.js"
import { MailFacade } from "./facades/lazy/MailFacade.js"
import type { Indexer } from "./search/Indexer.js"
import { UserFacade } from "./facades/UserFacade.js"
import { EntityClient } from "../common/EntityClient.js"
import { AccountType, OperationType } from "../common/TutanotaConstants.js"
@ -23,20 +21,21 @@ import { ExposedEventController } from "../main/EventController.js"
import { ConfigurationDatabase } from "./facades/lazy/ConfigurationDatabase.js"
import { KeyRotationFacade } from "./facades/KeyRotationFacade.js"
import { CacheManagementFacade } from "./facades/lazy/CacheManagementFacade.js"
import type { QueuedBatch } from "./EventQueue.js"
/** A bit of glue to distribute event bus events across the app. */
export class EventBusEventCoordinator implements EventBusListener {
constructor(
private readonly worker: WorkerImpl,
private readonly connectivityListener: WebsocketConnectivityListener,
private readonly mailFacade: lazyAsync<MailFacade>,
private readonly indexer: lazyAsync<Indexer>,
private readonly userFacade: UserFacade,
private readonly entityClient: EntityClient,
private readonly eventController: ExposedEventController,
private readonly configurationDatabase: lazyAsync<ConfigurationDatabase>,
private readonly keyRotationFacade: KeyRotationFacade,
private readonly cacheManagementFacade: lazyAsync<CacheManagementFacade>,
private readonly sendError: (error: Error) => Promise<void>,
private readonly appSpecificBatchHandling: (queuedBatch: QueuedBatch[]) => void,
) {}
onWebsocketStateChanged(state: WsConnectionState) {
@ -53,9 +52,7 @@ export class EventBusEventCoordinator implements EventBusListener {
const queuedBatch = { groupId, batchId, events }
const configurationDatabase = await this.configurationDatabase()
await configurationDatabase.onEntityEventsReceived(queuedBatch)
const indexer = await this.indexer()
indexer.addBatchesToQueue([queuedBatch])
indexer.startProcessing()
this.appSpecificBatchHandling([queuedBatch])
}
}
@ -67,7 +64,7 @@ export class EventBusEventCoordinator implements EventBusListener {
}
onError(tutanotaError: Error) {
this.worker.sendError(tutanotaError)
this.sendError(tutanotaError)
}
onLeaderStatusChanged(leaderStatus: WebsocketLeaderStatus) {

View file

@ -54,7 +54,6 @@ import { assertWorkerOrNode, isAdminClient } from "../../common/Env"
import { ConnectMode, EventBusClient } from "../EventBusClient"
import { EntityRestClient, typeRefToPath } from "../rest/EntityRestClient"
import { AccessExpiredError, ConnectionError, LockedError, NotAuthenticatedError, NotFoundError, SessionExpiredError } from "../../common/error/RestError"
import type { WorkerImpl } from "../WorkerImpl"
import { CancelledError } from "../../common/error/CancelledError"
import { RestClient } from "../rest/RestClient"
import { EntityClient } from "../../common/EntityClient"
@ -179,7 +178,6 @@ export class LoginFacade {
asyncLoginState: AsyncLoginState = { state: "idle" }
constructor(
readonly worker: WorkerImpl,
private readonly restClient: RestClient,
private readonly entityClient: EntityClient,
private readonly loginListener: LoginListener,
@ -199,6 +197,7 @@ export class LoginFacade {
private readonly databaseKeyFactory: DatabaseKeyFactory,
private readonly argon2idFacade: Argon2idFacade,
private readonly noncachingEntityClient: EntityClient,
private readonly sendError: (error: Error) => Promise<void>,
) {}
init(eventBusClient: EventBusClient) {
@ -417,7 +416,6 @@ export class LoginFacade {
throw new Error("user already logged in")
}
console.log("login external worker")
const userPassphraseKey = await this.deriveUserPassphraseKey({ kdfType, passphrase, salt })
// the verifier is always sent as url parameter, so it must be url encoded
const authVerifier = createAuthVerifierAsBase64Url(userPassphraseKey)
@ -629,7 +627,7 @@ export class LoginFacade {
await this.loginListener.onLoginFailure(LoginFailReason.SessionExpired)
} else {
this.asyncLoginState = { state: "failed", credentials, cacheInfo }
if (!(e instanceof ConnectionError)) await this.worker.sendError(e)
if (!(e instanceof ConnectionError)) await this.sendError(e)
await this.loginListener.onLoginFailure(LoginFailReason.Error)
}
}

View file

@ -1,14 +1,9 @@
import { ElementEntity, ListElementEntity, SomeEntity, TypeModel } from "../../common/EntityTypes.js"
import {
constructMailSetEntryId,
CUSTOM_MIN_ID,
DEFAULT_MAILSET_ENTRY_CUSTOM_CUTOFF_TIMESTAMP,
elementIdPart,
firstBiggerThanSecond,
GENERATED_MIN_ID,
getElementId,
listIdPart,
timestampToGeneratedId,
} from "../../common/utils/EntityUtils.js"
import { CacheStorage, expandId, ExposedCacheStorage, LastUpdateTime } from "../rest/DefaultEntityRestCache.js"
import * as cborg from "cborg"
@ -20,9 +15,7 @@ import {
base64ToBase64Ext,
base64ToBase64Url,
base64UrlToBase64,
DAY_IN_MILLIS,
getTypeId,
groupByAndMap,
groupByAndMapUniquely,
mapNullable,
splitInChunks,
@ -30,20 +23,11 @@ import {
} from "@tutao/tutanota-utils"
import { isDesktop, isOfflineStorageAvailable, isTest } from "../../common/Env.js"
import { modelInfos, resolveTypeReference } from "../../common/EntityFunctions.js"
import { AccountType, OFFLINE_STORAGE_DEFAULT_TIME_RANGE_DAYS } from "../../common/TutanotaConstants.js"
import { DateProvider } from "../../common/DateProvider.js"
import { TokenOrNestedTokens } from "cborg/interface"
import {
CalendarEventTypeRef,
FileTypeRef,
MailBoxTypeRef,
MailDetailsBlobTypeRef,
MailDetailsDraftTypeRef,
MailFolderTypeRef,
MailSetEntryTypeRef,
MailTypeRef,
} from "../../entities/tutanota/TypeRefs.js"
import { UserTypeRef } from "../../entities/sys/TypeRefs.js"
import { OfflineStorageMigrator } from "./OfflineStorageMigrator.js"
import { CustomCacheHandlerMap, CustomCalendarEventCacheHandler } from "../rest/CustomCacheHandler.js"
import { EntityRestClient } from "../rest/EntityRestClient.js"
@ -53,8 +37,6 @@ import { FormattedQuery, SqlValue, TaggedSqlValue, untagSqlObject } from "./SqlV
import { AssociationType, Cardinality, Type as TypeId, ValueType } from "../../common/EntityConstants.js"
import { OutOfSyncError } from "../../common/error/OutOfSyncError.js"
import { sql, SqlFragment } from "./Sql.js"
import { isDraft, isSpamOrTrashFolder } from "../../../../mail-app/mail/model/MailUtils.js"
import { FolderSystem } from "../../common/mail/FolderSystem.js"
/**
* this is the value of SQLITE_MAX_VARIABLE_NUMBER in sqlite3.c

View file

@ -3,7 +3,6 @@ import { ProgrammingError } from "../../common/error/ProgrammingError"
import { ListElementEntity, SomeEntity } from "../../common/EntityTypes"
import { TypeRef } from "@tutao/tutanota-utils"
import { OfflineStorage, OfflineStorageInitArgs } from "../offline/OfflineStorage.js"
import { WorkerImpl } from "../WorkerImpl"
import { EphemeralCacheStorage, EphemeralStorageInitArgs } from "./EphemeralCacheStorage"
import { EntityRestClient } from "./EntityRestClient.js"
import { CustomCacheHandlerMap } from "./CustomCacheHandler.js"
@ -46,7 +45,7 @@ type SomeStorage = OfflineStorage | EphemeralCacheStorage
export class LateInitializedCacheStorageImpl implements CacheStorageLateInitializer, CacheStorage {
private _inner: SomeStorage | null = null
constructor(private readonly worker: WorkerImpl, private readonly offlineStorageProvider: () => Promise<null | OfflineStorage>) {}
constructor(private readonly sendError: (error: Error) => Promise<void>, private readonly offlineStorageProvider: () => Promise<null | OfflineStorage>) {}
private get inner(): CacheStorage {
if (this._inner == null) {
@ -88,7 +87,7 @@ export class LateInitializedCacheStorageImpl implements CacheStorageLateInitiali
} catch (e) {
// Precaution in case something bad happens to offline database. We want users to still be able to log in.
console.error("Error while initializing offline cache storage", e)
this.worker.sendError(e)
this.sendError(e)
}
}
// both "else" case and fallback for unavailable storage and error cases

View file

@ -14,7 +14,7 @@ import { typeModels as tutanotaTypeModels } from "../../entities/tutanota/TypeMo
import type { GroupMembership, User } from "../../entities/sys/TypeRefs.js"
import type { TypeModel } from "../../common/EntityTypes"
import { isTest } from "../../common/Env"
import { aes256EncryptSearchIndexEntry, Aes256Key, aesDecrypt, unauthenticatedAesDecrypt } from "@tutao/tutanota-crypto"
import { aes256EncryptSearchIndexEntry, Aes256Key, unauthenticatedAesDecrypt } from "@tutao/tutanota-crypto"
export function encryptIndexKeyBase64(key: Aes256Key, indexKey: string, dbIv: Uint8Array): Base64 {
return uint8ArrayToBase64(encryptIndexKeyUint8Array(key, indexKey, dbIv))

View file

@ -0,0 +1,79 @@
import { EventBusClient } from "./EventBusClient.js"
import { LoginFacade, LoginListener } from "./facades/LoginFacade.js"
import { WebsocketConnectivityListener } from "../../misc/WebsocketConnectivityModel.js"
import { ExposedProgressTracker } from "../main/ProgressTracker.js"
import { ExposedEventController } from "../main/EventController.js"
import { ExposedOperationProgressTracker } from "../main/OperationProgressTracker.js"
import { InfoMessageHandler } from "../../gui/InfoMessageHandler.js"
import { CustomerFacade } from "./facades/lazy/CustomerFacade.js"
import { GiftCardFacade } from "./facades/lazy/GiftCardFacade.js"
import { GroupManagementFacade } from "./facades/lazy/GroupManagementFacade.js"
import { ConfigurationDatabase } from "./facades/lazy/ConfigurationDatabase.js"
import { CalendarFacade } from "./facades/lazy/CalendarFacade.js"
import { MailFacade } from "./facades/lazy/MailFacade.js"
import { ShareFacade } from "./facades/lazy/ShareFacade.js"
import { CacheManagementFacade } from "./facades/lazy/CacheManagementFacade.js"
import { CounterFacade } from "./facades/lazy/CounterFacade.js"
import { BookingFacade } from "./facades/lazy/BookingFacade.js"
import { MailAddressFacade } from "./facades/lazy/MailAddressFacade.js"
import { BlobAccessTokenFacade } from "./facades/BlobAccessTokenFacade.js"
import { BlobFacade } from "./facades/lazy/BlobFacade.js"
import { UserManagementFacade } from "./facades/lazy/UserManagementFacade.js"
import { RecoverCodeFacade } from "./facades/lazy/RecoverCodeFacade.js"
import { EntityRestInterface } from "./rest/EntityRestClient.js"
import { IServiceExecutor } from "../common/ServiceRequest.js"
import { CryptoFacade } from "./crypto/CryptoFacade.js"
import { ExposedCacheStorage } from "./rest/DefaultEntityRestCache.js"
import { SqlCipherFacade } from "../../native/common/generatedipc/SqlCipherFacade.js"
import { EntropyFacade } from "./facades/EntropyFacade.js"
import { WorkerFacade } from "./facades/WorkerFacade.js"
import { ContactFacade } from "./facades/lazy/ContactFacade.js"
export interface WorkerRandomizer {
generateRandomNumber(numBytes: number): Promise<number>
}
export interface ExposedEventBus {
tryReconnect: EventBusClient["tryReconnect"]
close: EventBusClient["close"]
}
/** Interface for the "main"/webpage context of the app, interface for the worker client. */
export interface MainInterface {
readonly loginListener: LoginListener
readonly wsConnectivityListener: WebsocketConnectivityListener
readonly progressTracker: ExposedProgressTracker
readonly eventController: ExposedEventController
readonly operationProgressTracker: ExposedOperationProgressTracker
readonly infoMessageHandler: InfoMessageHandler
}
/** Interface of the facades exposed by the worker, basically interface for the worker itself */
export interface CommonWorkerInterface {
readonly loginFacade: LoginFacade
readonly customerFacade: CustomerFacade
readonly giftCardFacade: GiftCardFacade
readonly groupManagementFacade: GroupManagementFacade
readonly configFacade: ConfigurationDatabase
readonly calendarFacade: CalendarFacade
readonly mailFacade: MailFacade
readonly shareFacade: ShareFacade
readonly cacheManagementFacade: CacheManagementFacade
readonly counterFacade: CounterFacade
readonly bookingFacade: BookingFacade
readonly mailAddressFacade: MailAddressFacade
readonly blobAccessTokenFacade: BlobAccessTokenFacade
readonly blobFacade: BlobFacade
readonly userManagementFacade: UserManagementFacade
readonly recoverCodeFacade: RecoverCodeFacade
readonly restInterface: EntityRestInterface
readonly serviceExecutor: IServiceExecutor
readonly cryptoFacade: CryptoFacade
readonly cacheStorage: ExposedCacheStorage
readonly sqlCipherFacade: SqlCipherFacade
readonly random: WorkerRandomizer
readonly eventBus: ExposedEventBus
readonly entropyFacade: EntropyFacade
readonly workerFacade: WorkerFacade
readonly contactFacade: ContactFacade
}

View file

@ -11,18 +11,17 @@ import {
import { getFirstOrThrow, isNotNull, LazyLoaded, ofClass, promiseMap } from "@tutao/tutanota-utils"
import Stream from "mithril/stream"
import stream from "mithril/stream"
import { SearchFacade } from "../api/worker/search/SearchFacade.js"
import { EntityClient, loadMultipleFromLists } from "../api/common/EntityClient.js"
import { LoginController } from "../api/main/LoginController.js"
import { EntityEventsListener, EventController } from "../api/main/EventController.js"
import { LoginIncompleteError } from "../api/common/error/LoginIncompleteError.js"
import { cleanMailAddress } from "../api/common/utils/CommonCalendarUtils.js"
import { createRestriction, SearchCategoryTypes } from "../../mail-app/search/model/SearchUtils.js"
import { DbError } from "../api/common/error/DbError.js"
import { compareOldestFirst, getEtId } from "../api/common/utils/EntityUtils.js"
import { NotAuthorizedError, NotFoundError } from "../api/common/error/RestError.js"
import { ShareCapability } from "../api/common/TutanotaConstants.js"
import { EntityUpdateData } from "../api/common/utils/EntityUpdateUtils.js"
import type { SearchResult } from "../api/worker/search/SearchTypes.js"
assertMainOrNode()
@ -40,10 +39,10 @@ export class ContactModel {
private contactListInfo: Stream<ReadonlyArray<ContactListInfo>> = stream()
constructor(
private readonly searchFacade: SearchFacade,
private readonly entityClient: EntityClient,
private readonly loginController: LoginController,
private readonly eventController: EventController,
private readonly contactSearch: (query: string, field: string, minSuggestionCount: number, maxResults?: number) => Promise<SearchResult>,
) {
this.contactListId = lazyContactListId(loginController, this.entityClient)
this.eventController.addEntityListener(this.entityEventsReceived)
@ -84,11 +83,7 @@ export class ContactModel {
const cleanedMailAddress = cleanMailAddress(mailAddress)
let result
try {
result = await this.searchFacade.search(
'"' + cleanedMailAddress + '"',
createRestriction(SearchCategoryTypes.contact, null, null, "mailAddress", [], null),
0,
)
result = await this.contactSearch('"' + cleanedMailAddress + '"', "mailAddress", 0)
} catch (e) {
// If IndexedDB is not supported or isn't working for some reason we load contacts from the server and
// search manually.
@ -131,7 +126,7 @@ export class ContactModel {
if (!this.loginController.isFullyLoggedIn()) {
throw new LoginIncompleteError("cannot search for contacts as online login is not completed")
}
const result = await this.searchFacade.search(query, createRestriction(SearchCategoryTypes.contact, null, null, field, [], null), minSuggestionCount)
const result = await this.contactSearch(query, field, minSuggestionCount)
return await loadMultipleFromLists(ContactTypeRef, this.entityClient, result.results)
}

View file

@ -1,9 +1,7 @@
import { ExportFacade } from "../native/common/generatedipc/ExportFacade.js"
import { MailBundle } from "../../mail-app/mail/export/Bundler.js"
import { createDataFile, DataFile } from "../api/common/DataFile.js"
import { fileExists } from "./PathUtils.js"
import path from "node:path"
import { MailExportMode } from "../../mail-app/mail/export/Exporter.js"
import { DesktopConfigKey } from "./config/ConfigKeys.js"
import { DesktopConfig } from "./config/DesktopConfig.js"
import { NativeImage } from "electron"
@ -12,6 +10,7 @@ import { Attachment, Email, MessageEditorFormat } from "@tutao/oxmsg"
import { sanitizeFilename } from "../api/common/utils/FileUtils.js"
import { promises as fs } from "node:fs"
import { TempFs } from "./files/TempFs.js"
import { MailBundle, MailExportMode } from "../mailFunctionality/SharedMailUtils.js"
const EXPORT_DIR = "export"

View file

@ -2,9 +2,7 @@ import m from "mithril"
import { show as showNotificationOverlay } from "./base/NotificationOverlay"
import { lang, TranslationKey } from "../misc/LanguageViewModel"
import { assertMainOrNode } from "../api/common/Env"
import { SearchModel } from "../../mail-app/search/model/SearchModel.js"
import { SearchIndexStateInfo } from "../api/worker/search/SearchTypes.js"
import { CalendarSearchModel } from "../../calendar-app/calendar/search/model/CalendarSearchModel.js"
assertMainOrNode()

View file

@ -1,7 +1,5 @@
import { Indexer } from "../api/worker/search/Indexer.js"
import { NativePushServiceApp } from "../native/main/NativePushServiceApp.js"
import { ConfigurationDatabase } from "../api/worker/facades/lazy/ConfigurationDatabase.js"
import { NativeContactsSyncManager } from "../../mail-app/contacts/model/NativeContactsSyncManager.js"
import { CredentialsInfo } from "../native/common/generatedipc/CredentialsInfo.js"
export interface CredentialRemovalHandler {
@ -14,18 +12,16 @@ export class NoopCredentialRemovalHandler implements CredentialRemovalHandler {
export class AppsCredentialRemovalHandler implements CredentialRemovalHandler {
constructor(
private readonly indexer: Indexer,
private readonly pushApp: NativePushServiceApp,
private readonly configFacade: ConfigurationDatabase,
private readonly mobileContactsManager: NativeContactsSyncManager | null,
private readonly appSpecificCredentialRemovalActions: (login: string, userId: string) => Promise<void>,
) {}
async onCredentialsRemoved({ login, userId }: CredentialsInfo) {
await this.indexer.deleteIndex(userId)
await this.pushApp.invalidateAlarmsForUser(userId)
await this.pushApp.removeUserFromNotifications(userId)
await this.configFacade.delete(userId)
await this.mobileContactsManager?.disableSync(userId, login)
await this.appSpecificCredentialRemovalActions(login, userId)
}
}

View file

@ -141,7 +141,7 @@ export class MailboxModel {
if (isUpdateForTypeRef(GroupInfoTypeRef, update)) {
if (update.operation === OperationType.UPDATE) {
await this._init()
m.redraw
m.redraw()
}
} else if (this.logins.getUserController().isUpdateForLoggedInUserInstance(update, eventOwnerGroupId)) {
let newMemberships = this.logins.getUserController().getMailGroupMemberships()

View file

@ -2,14 +2,16 @@ import { assertMainOrNode } from "../api/common/Env.js"
import { CustomerPropertiesTypeRef, GroupInfo, User } from "../api/entities/sys/TypeRefs.js"
import { Contact, createContact, createContactMailAddress, Mail } from "../api/entities/tutanota/TypeRefs.js"
import { fullNameToFirstAndLastName, mailAddressToFirstAndLastName } from "../misc/parsing/MailAddressParser.js"
import { assertNotNull, contains, neverNull } from "@tutao/tutanota-utils"
import { assertNotNull, contains, neverNull, uint8ArrayToBase64 } from "@tutao/tutanota-utils"
import {
ALLOWED_IMAGE_FORMATS,
ContactAddressType,
ConversationType, EncryptionAuthStatus,
getMailFolderType,
GroupType,
MailState,
MAX_ATTACHMENT_SIZE, SYSTEM_GROUP_MAIL_ADDRESS, TUTA_MAIL_ADDRESS_DOMAINS,
SYSTEM_GROUP_MAIL_ADDRESS,
MAX_BASE64_IMAGE_SIZE,
MAX_ATTACHMENT_SIZE, TUTA_MAIL_ADDRESS_DOMAINS,
} from "../api/common/TutanotaConstants.js"
import { UserController } from "../api/main/UserController.js"
import { getEnabledMailAddressesForGroupInfo, getGroupInfoDisplayName } from "../api/common/utils/GroupUtils.js"
@ -18,7 +20,9 @@ import { MailboxDetail } from "./MailboxModel.js"
import { LoginController } from "../api/main/LoginController.js"
import { EntityClient } from "../api/common/EntityClient.js"
import { Attachment } from "./SendMailModel.js"
import { TUTANOTA_MAIL_ADDRESS_DOMAINS } from "../../../.rollup.cache/home/and/dev/repositories/tutanota-3/build/src/common/api/common/TutanotaConstants.js"
import { showFileChooser } from "../file/FileController.js"
import { DataFile } from "../api/common/DataFile.js"
import { Dialog } from "../gui/base/Dialog.js"
assertMainOrNode()
export const LINE_BREAK = "<br>"
@ -209,8 +213,8 @@ export enum RecipientField {
BCC = "bcc",
}
export function isTutanotaMailAddress(mailAddress: string): boolean {
return TUTANOTA_MAIL_ADDRESS_DOMAINS.some((tutaDomain) => mailAddress.endsWith("@" + tutaDomain))
export function isTutaMailAddress(mailAddress: string): boolean {
return TUTA_MAIL_ADDRESS_DOMAINS.some((tutaDomain) => mailAddress.endsWith("@" + tutaDomain))
}
export function hasValidEncryptionAuthForTeamOrSystemMail({ encryptionAuthStatus }: Mail): boolean {
@ -249,3 +253,59 @@ export function isSystemNotification(mail: Mail): boolean {
export function isNoReplyTeamAddress(address: string): boolean {
return address === "no-reply@tutao.de" || address === "no-reply@tutanota.de"
}
export function insertInlineImageB64ClickHandler(ev: Event, handler: ImageHandler) {
showFileChooser(true, ALLOWED_IMAGE_FORMATS).then((files) => {
const tooBig: DataFile[] = []
for (let file of files) {
if (file.size > MAX_BASE64_IMAGE_SIZE) {
tooBig.push(file)
} else {
const b64 = uint8ArrayToBase64(file.data)
const dataUrlString = `data:${file.mimeType};base64,${b64}`
handler.insertImage(dataUrlString, {
style: "max-width: 100%",
})
}
}
if (tooBig.length > 0) {
Dialog.message(() =>
lang.get("tooBigInlineImages_msg", {
"{size}": MAX_BASE64_IMAGE_SIZE / 1024,
}),
)
}
})
}
// .msg export is handled in DesktopFileExport because it uses APIs that can't be loaded web side
export type MailExportMode = "msg" | "eml"
/**
* Used to pass all downloaded mail stuff to the desktop side to be exported as a file
* Ideally this would just be {Mail, Headers, Body, FileReference[]}
* but we can't send Dates over to the native side, so we may as well just extract everything here
*/
export type MailBundleRecipient = {
address: string
name?: string
}
export type MailBundle = {
mailId: IdTuple
subject: string
body: string
sender: MailBundleRecipient
to: MailBundleRecipient[]
cc: MailBundleRecipient[]
bcc: MailBundleRecipient[]
replyTo: MailBundleRecipient[]
isDraft: boolean
isRead: boolean
sentOn: number
// UNIX timestamp
receivedOn: number // UNIX timestamp,
headers: string | null
attachments: DataFile[]
}

View file

@ -26,14 +26,12 @@ import { showSnackBar } from "../gui/base/SnackBar"
import { Credentials, credentialsToUnencrypted } from "./credentials/Credentials"
import { showErrorDialogNotLoggedIn, showErrorNotification } from "./ErrorReporter"
import { CancelledError } from "../api/common/error/CancelledError"
import { getLoginErrorMessage } from "./LoginUtils"
import { SessionType } from "../api/common/SessionType.js"
import { OfflineDbClosedError } from "../api/common/error/OfflineDbClosedError.js"
import { UserTypeRef } from "../api/entities/sys/TypeRefs.js"
import { isOfflineError } from "../api/common/utils/ErrorUtils.js"
import { showRequestPasswordDialog } from "./passwords/PasswordRequestDialog.js"
import { SearchModel } from "../../mail-app/search/model/SearchModel.js"
assertMainOrNode()
@ -124,7 +122,8 @@ export async function handleUncaughtErrorImpl(e: Error) {
}
} else if (e instanceof IndexingNotSupportedError) {
console.log("Indexing not supported", e)
if (search instanceof SearchModel) {
if ("indexingSupported" in search) {
// search can be in two flavours: "SearchModel" and "CalendarSearchModel. Only "SearchModel" has indexing
search.indexingSupported = false
}
} else if (e instanceof QuotaExceededError) {

View file

@ -2,8 +2,8 @@ import { WsConnectionState } from "../api/main/WorkerClient.js"
import stream from "mithril/stream"
import { identity } from "@tutao/tutanota-utils"
import { CloseEventBusOption } from "../api/common/TutanotaConstants.js"
import { ExposedEventBus } from "../api/worker/WorkerImpl.js"
import { WebsocketLeaderStatus } from "../api/entities/sys/TypeRefs.js"
import { ExposedEventBus } from "../api/worker/workerInterfaces.js"
export interface WebsocketConnectivityListener {
updateWebSocketState(wsConnectionState: WsConnectionState): Promise<void>

View file

@ -2,8 +2,8 @@ import m, { Params } from "mithril"
import { assertMainOrNodeBoot, isApp, isElectronClient, isIOSApp, Mode } from "../api/common/Env"
import { lang } from "./LanguageViewModel"
import { client } from "./ClientDetector"
import type { Indexer } from "../api/worker/search/Indexer"
import { remove } from "@tutao/tutanota-utils"
import type { Indexer } from "../../mail-app/workerUtils/index/Indexer.js"
import { noOp, remove } from "@tutao/tutanota-utils"
import { WebsocketConnectivityModel } from "./WebsocketConnectivityModel.js"
import { LoginController } from "../api/main/LoginController.js"
@ -24,6 +24,7 @@ export class WindowFacade {
private _ignoreNextPopstate: boolean = false
private connectivityModel!: WebsocketConnectivityModel
private logins: LoginController | null = null
private appBasedVisibilityChage: (visible: boolean) => void = noOp
constructor() {
this._windowSizeListeners = []
@ -99,7 +100,7 @@ export class WindowFacade {
}
}
init(logins: LoginController, indexerFacade: Indexer, connectivityModel: WebsocketConnectivityModel) {
init(logins: LoginController, connectivityModel: WebsocketConnectivityModel, appBasedVisibilityChage: ((visible: boolean) => void) | null) {
this.logins = logins
if (window.addEventListener && !isApp()) {
@ -108,7 +109,7 @@ export class WindowFacade {
window.addEventListener("unload", (e) => this._onUnload())
}
this.indexerFacade = indexerFacade
this.appBasedVisibilityChage = appBasedVisibilityChage ?? noOp
this.connectivityModel = connectivityModel
if (env.mode === Mode.App || env.mode === Mode.Desktop || env.mode === Mode.Admin) {
@ -257,7 +258,7 @@ export class WindowFacade {
document.addEventListener("visibilitychange", () => {
console.log("Visibility change, hidden: ", document.hidden)
this.indexerFacade?.onVisibilityChanged(!document.hidden)
this.appBasedVisibilityChage(!document.hidden)
if (!document.hidden) {
// On iOS devices the WebSocket close event fires when the app comes back to foreground

View file

@ -1,6 +1,6 @@
import { assertMainOrNode } from "../../api/common/Env"
import { assert } from "@tutao/tutanota-utils"
import { WorkerRandomizer } from "../../api/worker/WorkerImpl"
import { WorkerRandomizer } from "../../api/worker/workerInterfaces.js"
assertMainOrNode()

View file

@ -1,5 +1,4 @@
import { promiseMap } from "@tutao/tutanota-utils"
import type { MailBundle } from "../../../mail-app/mail/export/Bundler"
import { FileReference } from "../../api/common/utils/FileUtils"
import { DataFile } from "../../api/common/DataFile"
import { HttpMethod } from "../../api/common/EntityFunctions"
@ -7,6 +6,7 @@ import { FileFacade } from "./generatedipc/FileFacade.js"
import { ExportFacade } from "./generatedipc/ExportFacade.js"
import { DownloadTaskResponse } from "./generatedipc/DownloadTaskResponse"
import { UploadTaskResponse } from "./generatedipc/UploadTaskResponse"
import { MailBundle } from "../../mailFunctionality/SharedMailUtils.js"
export type FileUri = string

View file

@ -1,3 +1,3 @@
/* generated file, don't edit. */
export { MailBundle } from "../../../../mail-app/mail/export/Bundler.js"
export { MailBundle } from "../../../mailFunctionality/SharedMailUtils.js"

View file

@ -0,0 +1,16 @@
import Stream from "mithril/stream"
import type { SearchRestriction, SearchResult } from "../api/worker/search/SearchTypes.js"
import { SearchQuery } from "../../calendar-app/calendar/search/model/CalendarSearchModel.js"
import { ProgressTracker } from "../api/main/ProgressTracker.js"
export interface CommonSearchModel {
result: Stream<SearchResult | null>
lastQueryString: Stream<string | null>
cancelSignal: Stream<boolean>
search(searchQuery: SearchQuery, progressTracker: ProgressTracker): Promise<SearchResult | void>
isNewSearch(query: string, restriction: SearchRestriction): boolean
sendCancelSignal(): void
}

View file

@ -22,7 +22,8 @@ import { GENERATED_MAX_ID } from "../api/common/utils/EntityUtils.js"
import { locator } from "../api/main/CommonLocator.js"
import { PlanType } from "../api/common/TutanotaConstants.js"
import { getWhitelabelDomainInfo } from "../api/common/utils/CustomerUtils.js"
import { insertInlineImageB64ClickHandler } from "../../mail-app/mail/view/MailViewerUtils.js"
import { insertInlineImageB64ClickHandler } from "../mailFunctionality/SharedMailUtils.js"
export function showAddOrEditNotificationEmailDialog(userController: UserController, selectedNotificationLanguage?: string) {
let existingTemplate: NotificationMailTemplate | undefined = undefined

View file

@ -14,7 +14,7 @@ import { IconButton, IconButtonAttrs } from "../gui/base/IconButton.js"
import { ButtonSize } from "../gui/base/ButtonSize.js"
import { EmailDomainData } from "./mailaddress/MailAddressesUtils.js"
import { BootIcons } from "../gui/base/icons/BootIcons.js"
import { isTutanotaMailAddress } from "../mailFunctionality/SharedMailUtils.js"
import { isTutaMailAddress } from "../mailFunctionality/SharedMailUtils.js"
assertMainOrNode()
@ -177,7 +177,7 @@ export class SelectMailAddressForm implements Component<SelectMailAddressFormAtt
this.onBusyStateChanged(false, onBusyStateChanged)
return
} else if (!isMailAddress(cleanMailAddress, true) || (isTutanotaMailAddress(cleanMailAddress) && cleanUsername.length < 3)) {
} else if (!isMailAddress(cleanMailAddress, true) || (isTutaMailAddress(cleanMailAddress) && cleanUsername.length < 3)) {
this.onValidationFinished(
cleanMailAddress,
{

View file

@ -1,6 +1,5 @@
import { AddressToName, MailAddressNameChanger } from "../../../common/settings/mailaddress/MailAddressTableModel.js"
import { MailAddressFacade } from "../../../common/api/worker/facades/lazy/MailAddressFacade.js"
import { assertNotNull } from "@tutao/tutanota-utils"
import { AddressToName, MailAddressNameChanger } from "./MailAddressTableModel.js"
import { MailAddressFacade } from "../../api/worker/facades/lazy/MailAddressFacade.js"
/**
* A {@link MailAddressNameChanger} intended for admins to set names for aliases bound to user mailboxes.

View file

@ -13,7 +13,7 @@ import { UpgradeRequiredError } from "../../api/main/UpgradeRequiredError.js"
import { IServiceExecutor } from "../../api/common/ServiceRequest.js"
import { getAvailableMatchingPlans } from "../../subscription/SubscriptionUtils.js"
import { EntityUpdateData, isUpdateFor, isUpdateForTypeRef } from "../../api/common/utils/EntityUpdateUtils.js"
import { isTutanotaMailAddress } from "../../mailFunctionality/SharedMailUtils.js"
import { isTutaMailAddress } from "../../mailFunctionality/SharedMailUtils.js"
export enum AddressStatus {
Primary,
@ -99,8 +99,8 @@ export class MailAddressTableModel {
.sort((a, b) => (a.mailAddress > b.mailAddress ? 1 : -1))
.map(({ mailAddress, enabled }) => {
const status =
// O(aliases * TUTANOTA_MAIL_ADDRESS_DOMAINS)
isTutanotaMailAddress(mailAddress) ? (enabled ? AddressStatus.Alias : AddressStatus.DisabledAlias) : AddressStatus.Custom
// O(aliases * TUTA_MAIL_ADDRESS_DOMAINS)
isTutaMailAddress(mailAddress) ? (enabled ? AddressStatus.Alias : AddressStatus.DisabledAlias) : AddressStatus.Custom
return {
name: nameMappings.get(mailAddress) ?? "",

View file

@ -1,7 +1,7 @@
import { AddressToName, MailAddressNameChanger } from "../../../common/settings/mailaddress/MailAddressTableModel.js"
import { MailboxModel } from "../../../common/mailFunctionality/MailboxModel.js"
import { createMailAddressProperties, MailboxProperties } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { EntityClient } from "../../../common/api/common/EntityClient.js"
import { AddressToName, MailAddressNameChanger } from "./MailAddressTableModel.js"
import { MailboxModel } from "../../mailFunctionality/MailboxModel.js"
import { createMailAddressProperties, MailboxProperties } from "../../api/entities/tutanota/TypeRefs.js"
import { EntityClient } from "../../api/common/EntityClient.js"
import { findAndRemove } from "@tutao/tutanota-utils"
/** Name changer for personal mailbox of the currently logged-in user. */

View file

@ -97,7 +97,9 @@ import("./translations/en.js")
const { BottomNav } = await import("./gui/BottomNav.js")
// this needs to stay after client.init
windowFacade.init(mailLocator.logins, mailLocator.indexerFacade, mailLocator.connectivityModel)
windowFacade.init(mailLocator.logins, mailLocator.connectivityModel, (visible) => {
mailLocator.indexerFacade?.onVisibilityChanged(!document.hidden)
})
if (isDesktop()) {
import("../common/native/main/UpdatePrompt.js").then(({ registerForUpdates }) => registerForUpdates(mailLocator.desktopSettingsFacade))
}
@ -126,11 +128,10 @@ import("./translations/en.js")
mailLocator.fileApp.clearFileData().catch((e) => console.log("Failed to clean file data", e))
mailLocator.nativeContactsSyncManager()?.syncContacts()
}
},
async onFullLoginSuccess() {
await mailLocator.mailboxModel.init()
await mailLocator.mailModel.init()
},
async onFullLoginSuccess() {},
}
})

View file

@ -5,40 +5,13 @@ import { MailState } from "../../../common/api/common/TutanotaConstants"
import { getLetId } from "../../../common/api/common/utils/EntityUtils"
import type { HtmlSanitizer } from "../../../common/misc/HtmlSanitizer"
import { promiseMap } from "@tutao/tutanota-utils"
import { DataFile } from "../../../common/api/common/DataFile"
import { FileController } from "../../../common/file/FileController"
import { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade.js"
import { CryptoFacade } from "../../../common/api/worker/crypto/CryptoFacade.js"
import { getDisplayedSender, getMailBodyText, MailAddressAndName } from "../../../common/api/common/CommonMailUtils.js"
import { loadMailDetails } from "../view/MailViewerUtils.js"
import { loadMailHeaders } from "../model/MailUtils.js"
/**
* Used to pass all downloaded mail stuff to the desktop side to be exported as a file
* Ideally this would just be {Mail, Headers, Body, FileReference[]}
* but we can't send Dates over to the native side, so we may as well just extract everything here
*/
export type MailBundleRecipient = {
address: string
name?: string
}
export type MailBundle = {
mailId: IdTuple
subject: string
body: string
sender: MailBundleRecipient
to: MailBundleRecipient[]
cc: MailBundleRecipient[]
bcc: MailBundleRecipient[]
replyTo: MailBundleRecipient[]
isDraft: boolean
isRead: boolean
sentOn: number
// UNIX timestamp
receivedOn: number // UNIX timestamp,
headers: string | null
attachments: DataFile[]
}
import { MailBundle } from "../../../common/mailFunctionality/SharedMailUtils.js"
/**
* Downloads the mail body and the attachments for an email, to prepare for exporting

View file

@ -10,7 +10,6 @@ import {
uint8ArrayToBase64,
} from "@tutao/tutanota-utils"
import { createDataFile, DataFile, getCleanedMimeType } from "../../../common/api/common/DataFile"
import type { MailBundle, MailBundleRecipient } from "./Bundler"
import { makeMailBundle } from "./Bundler"
import { isDesktop } from "../../../common/api/common/Env"
import { sanitizeFilename } from "../../../common/api/common/utils/FileUtils"
@ -22,8 +21,7 @@ import { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade.j
import { OperationId } from "../../../common/api/main/OperationProgressTracker.js"
import { CancelledError } from "../../../common/api/common/error/CancelledError.js"
import { CryptoFacade } from "../../../common/api/worker/crypto/CryptoFacade.js"
// .msg export is handled in DesktopFileExport because it uses APIs that can't be loaded web side
export type MailExportMode = "msg" | "eml"
import { MailBundle, MailBundleRecipient, MailExportMode } from "../../../common/mailFunctionality/SharedMailUtils.js"
export async function generateMailFile(bundle: MailBundle, fileName: string, mode: MailExportMode): Promise<DataFile> {
return mode === "eml" ? mailToEmlFile(bundle, fileName) : locator.fileApp.mailToMsg(bundle, fileName)

View file

@ -0,0 +1,42 @@
//@bundleInto:common
import { Mail, MailFolder } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { MailModel } from "./MailModel.js"
import { FolderSystem } from "../../../common/api/common/mail/FolderSystem.js"
import { MailSetKind } from "../../../common/api/common/TutanotaConstants.js"
export function isSubfolderOfType(system: FolderSystem, folder: MailFolder, type: MailSetKind): boolean {
const systemFolder = system.getSystemFolderByType(type)
return systemFolder != null && system.checkFolderForAncestor(folder, systemFolder._id)
}
export function isDraft(mail: Mail): boolean {
return mail.mailDetailsDraft != null
}
export async function isMailInSpamOrTrash(mail: Mail, mailModel: MailModel): Promise<boolean> {
const folders = await mailModel.getMailboxFoldersForMail(mail)
const mailFolder = folders?.getFolderByMail(mail)
if (folders && mailFolder) {
return isSpamOrTrashFolder(folders, mailFolder)
} else {
return false
}
}
/**
* Returns true if given folder is the {@link MailFolderType.SPAM} or {@link MailFolderType.TRASH} folder, or a descendant of those folders.
*/
export function isSpamOrTrashFolder(system: FolderSystem, folder: MailFolder): boolean {
// not using isOfTypeOrSubfolderOf because checking the type first is cheaper
return (
folder.folderType === MailSetKind.TRASH ||
folder.folderType === MailSetKind.SPAM ||
isSubfolderOfType(system, folder, MailSetKind.TRASH) ||
isSubfolderOfType(system, folder, MailSetKind.SPAM)
)
}
export function isOfTypeOrSubfolderOf(system: FolderSystem, folder: MailFolder, type: MailSetKind): boolean {
return folder.folderType === type || isSubfolderOfType(system, folder, type)
}

View file

@ -35,7 +35,8 @@ import { WebsocketConnectivityModel } from "../../../common/misc/WebsocketConnec
import { EntityClient } from "../../../common/api/common/EntityClient.js"
import { LoginController } from "../../../common/api/main/LoginController.js"
import { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade.js"
import { assertSystemFolderOfType, isSpamOrTrashFolder } from "./MailUtils.js"
import { assertSystemFolderOfType } from "./MailUtils.js"
import { isSpamOrTrashFolder } from "./MailChecks.js"
export class MailModel {
readonly mailboxCounters: Stream<MailboxCounters> = stream({})
@ -422,11 +423,7 @@ export class MailModel {
someNonEmpty = true
}
}
if (
(await this.isEmptyFolder(folder)) &&
mailboxDetail.folders.getCustomFoldersOfParent(folder._id).every((f) => deleted.has(getElementId(f))) &&
!someNonEmpty
) {
if ((await this.isEmptyFolder(folder)) && mailboxDetail.folders.getCustomFoldersOfParent(folder._id).every((f) => deleted.has(getElementId(f))) && !someNonEmpty) {
await this.finallyDeleteCustomMailFolder(folder)
return true
} else {

View file

@ -1,7 +1,7 @@
import { FolderSystem, type IndentedFolder } from "../../../common/api/common/mail/FolderSystem.js"
import { Header, InboxRule, Mail, MailDetails, MailFolder, TutanotaProperties } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { assertNotNull, contains, first, neverNull } from "@tutao/tutanota-utils"
import { getListId } from "../../../common/api/common/utils/EntityUtils.js"
import { assertNotNull, contains, first, isNotEmpty, neverNull } from "@tutao/tutanota-utils"
import { getListId, isSameId } from "../../../common/api/common/utils/EntityUtils.js"
import { MailModel } from "./MailModel.js"
import { lang } from "../../../common/misc/LanguageViewModel.js"
import { UserController } from "../../../common/api/main/UserController.js"
@ -56,39 +56,14 @@ export async function getMoveTargetFolderSystems(foldersModel: MailModel, mails:
}
const folderStructures = foldersModel.folders()
const folderSystem = folderStructures[mailboxDetails.mailbox.folders._id]
return folderSystem.getIndentedList().filter((f: IndentedFolder) => f.folder.mails !== getListId(firstMail))
}
export function isSubfolderOfType(system: FolderSystem, folder: MailFolder, type: MailSetKind): boolean {
const systemFolder = system.getSystemFolderByType(type)
return systemFolder != null && system.checkFolderForAncestor(folder, systemFolder._id)
}
export function isDraft(mail: Mail): boolean {
return mail.mailDetailsDraft != null
}
export async function isMailInSpamOrTrash(mail: Mail, mailModel: MailModel): Promise<boolean> {
const folders = await mailModel.getMailboxFoldersForMail(mail)
const mailFolder = folders?.getFolderByMail(mail)
if (folders && mailFolder) {
return isSpamOrTrashFolder(folders, mailFolder)
} else {
return false
}
}
/**
* Returns true if given folder is the {@link MailFolderType.SPAM} or {@link MailFolderType.TRASH} folder, or a descendant of those folders.
*/
export function isSpamOrTrashFolder(system: FolderSystem, folder: MailFolder): boolean {
// not using isOfTypeOrSubfolderOf because checking the type first is cheaper
return (
folder.folderType === MailSetKind.TRASH ||
folder.folderType === MailSetKind.SPAM ||
isSubfolderOfType(system, folder, MailSetKind.TRASH) ||
isSubfolderOfType(system, folder, MailSetKind.SPAM)
)
return folderSystem.getIndentedList().filter((f: IndentedFolder) => {
if (f.folder.isMailSet && isNotEmpty(firstMail.sets)) {
const folderId = firstMail.sets[0]
return !isSameId(f.folder._id, folderId)
} else {
return f.folder.mails !== getListId(firstMail)
}
})
}
/**
@ -101,10 +76,6 @@ export function assertSystemFolderOfType(system: FolderSystem, type: Omit<MailSe
return assertNotNull(system.getSystemFolderByType(type), "System folder of type does not exist!")
}
export function isOfTypeOrSubfolderOf(system: FolderSystem, folder: MailFolder, type: MailSetKind): boolean {
return folder.folderType === type || isSubfolderOfType(system, folder, type)
}
export function getPathToFolderString(folderSystem: FolderSystem, folder: MailFolder, omitLast = false) {
const folderPath = folderSystem.getPathToFolder(folder._id)
if (omitLast) {

View file

@ -13,7 +13,8 @@ import { EntityUpdateData, isUpdateForTypeRef } from "../../../common/api/common
import { ListAutoSelectBehavior } from "../../../common/misc/DeviceConfig.js"
import { MailModel } from "../model/MailModel.js"
import { isOfTypeOrSubfolderOf } from "../model/MailUtils.js"
import { isOfTypeOrSubfolderOf } from "../model/MailChecks.js"
export type MailViewerViewModelFactory = (options: CreateMailViewerOptions) => MailViewerViewModel

View file

@ -15,7 +15,8 @@ import { groupByAndMap } from "@tutao/tutanota-utils"
import { mailLocator } from "../../mailLocator.js"
import { assertNotNull } from "@tutao/tutanota-utils"
import type { FolderSystem, IndentedFolder } from "../../../common/api/common/mail/FolderSystem.js"
import { getFolderName, getIndentedFolderNameForDropdown, getPathToFolderString, isSpamOrTrashFolder } from "../model/MailUtils.js"
import { getFolderName, getIndentedFolderNameForDropdown, getPathToFolderString } from "../model/MailUtils.js"
import { isSpamOrTrashFolder } from "../model/MailChecks.js"
/**
* Dialog for Edit and Add folder are the same.

View file

@ -17,9 +17,10 @@ import { ButtonSize } from "../../../common/gui/base/ButtonSize.js"
import { MailSetKind } from "../../../common/api/common/TutanotaConstants.js"
import { px, size } from "../../../common/gui/size.js"
import { RowButton } from "../../../common/gui/base/buttons/RowButton.js"
import { getFolderName, isSpamOrTrashFolder, MAX_FOLDER_INDENT_LEVEL } from "../model/MailUtils.js"
import { getFolderIcon } from "./MailGuiUtils.js"
import { MailModel } from "../model/MailModel.js"
import { getFolderName, MAX_FOLDER_INDENT_LEVEL } from "../model/MailUtils.js"
import { getFolderIcon } from "./MailGuiUtils.js"
import { isSpamOrTrashFolder } from "../model/MailChecks.js"
export interface MailFolderViewAttrs {
mailModel: MailModel

View file

@ -30,16 +30,10 @@ import {
hasValidEncryptionAuthForTeamOrSystemMail,
} from "../../../common/mailFunctionality/SharedMailUtils.js"
import { mailLocator } from "../../mailLocator.js"
import {
assertSystemFolderOfType,
getFolderName,
getIndentedFolderNameForDropdown,
getMoveTargetFolderSystems,
isOfTypeOrSubfolderOf,
isSpamOrTrashFolder,
} from "../model/MailUtils.js"
import { assertSystemFolderOfType, getFolderName, getIndentedFolderNameForDropdown, getMoveTargetFolderSystems } from "../model/MailUtils.js"
import { FontIcons } from "../../../common/gui/base/icons/FontIcons.js"
import { ProgrammingError } from "../../../common/api/common/error/ProgrammingError.js"
import { isOfTypeOrSubfolderOf, isSpamOrTrashFolder } from "../model/MailChecks.js"
import type { FolderSystem } from "../../../common/api/common/mail/FolderSystem.js"
export async function showDeleteConfirmationDialog(mails: ReadonlyArray<Mail>): Promise<boolean> {

View file

@ -31,8 +31,9 @@ import { VirtualRow } from "../../../common/gui/base/ListUtils.js"
import { isKeyPressed } from "../../../common/misc/KeyManager.js"
import { ListModel } from "../../../common/misc/ListModel.js"
import { mailLocator } from "../../mailLocator.js"
import { assertSystemFolderOfType, isOfTypeOrSubfolderOf } from "../model/MailUtils.js"
import { assertSystemFolderOfType } from "../model/MailUtils.js"
import { canDoDragAndDropExport } from "./MailViewerUtils.js"
import { isOfTypeOrSubfolderOf } from "../model/MailChecks.js"
assertMainOrNode()

View file

@ -60,8 +60,9 @@ import { getMailboxName } from "../../../common/mailFunctionality/SharedMailUtil
import { BottomNav } from "../../gui/BottomNav.js"
import { mailLocator } from "../../mailLocator.js"
import { showSnackBar } from "../../../common/gui/base/SnackBar.js"
import { getFolderName, isSpamOrTrashFolder } from "../model/MailUtils.js"
import { getFolderName } from "../model/MailUtils.js"
import { canDoDragAndDropExport } from "./MailViewerUtils.js"
import { isSpamOrTrashFolder } from "../model/MailChecks.js"
assertMainOrNode()

View file

@ -46,9 +46,10 @@ import { ListFetchResult } from "../../../common/gui/base/ListUtils.js"
import { EntityUpdateData, isUpdateForTypeRef } from "../../../common/api/common/utils/EntityUpdateUtils.js"
import { EventController } from "../../../common/api/main/EventController.js"
import { MailModel } from "../model/MailModel.js"
import { assertSystemFolderOfType, isOfTypeOrSubfolderOf, isSpamOrTrashFolder, isSubfolderOfType } from "../model/MailUtils.js"
import { assertSystemFolderOfType } from "../model/MailUtils.js"
import { getMailFilterForType, MailFilterType } from "./MailViewerUtils.js"
import { CacheMode } from "../../../common/api/worker/rest/EntityRestClient.js"
import { isOfTypeOrSubfolderOf, isSpamOrTrashFolder, isSubfolderOfType } from "../model/MailChecks.js"
export interface MailOpenedListener {
onEmailOpened(mail: Mail): unknown

View file

@ -1,17 +1,7 @@
import {
ALLOWED_IMAGE_FORMATS,
Keys,
MailReportType,
MailState,
MAX_BASE64_IMAGE_SIZE,
ReplyType,
SYSTEM_GROUP_MAIL_ADDRESS,
} from "../../../common/api/common/TutanotaConstants"
import { assertNotNull, neverNull, ofClass, uint8ArrayToBase64 } from "@tutao/tutanota-utils"
import { Keys, MailReportType, MailState, ReplyType, SYSTEM_GROUP_MAIL_ADDRESS } from "../../../common/api/common/TutanotaConstants"
import { assertNotNull, neverNull, ofClass } from "@tutao/tutanota-utils"
import { InfoLink, lang } from "../../../common/misc/LanguageViewModel"
import { Dialog } from "../../../common/gui/base/Dialog"
import { DataFile } from "../../../common/api/common/DataFile"
import { showFileChooser } from "../../../common/file/FileController.js"
import m from "mithril"
import { Button, ButtonType } from "../../../common/gui/base/Button.js"
import { progressIcon } from "../../../common/gui/base/Icon.js"
@ -34,35 +24,9 @@ import { Mail, MailDetails } from "../../../common/api/entities/tutanota/TypeRef
import { getDisplayedSender } from "../../../common/api/common/CommonMailUtils.js"
import { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade.js"
import { isDraft } from "../model/MailUtils.js"
import { ListFilter } from "../../../common/misc/ListModel.js"
import { isDesktop } from "../../../common/api/common/Env.js"
export function insertInlineImageB64ClickHandler(ev: Event, handler: ImageHandler) {
showFileChooser(true, ALLOWED_IMAGE_FORMATS).then((files) => {
const tooBig: DataFile[] = []
for (let file of files) {
if (file.size > MAX_BASE64_IMAGE_SIZE) {
tooBig.push(file)
} else {
const b64 = uint8ArrayToBase64(file.data)
const dataUrlString = `data:${file.mimeType};base64,${b64}`
handler.insertImage(dataUrlString, {
style: "max-width: 100%",
})
}
}
if (tooBig.length > 0) {
Dialog.message(() =>
lang.get("tooBigInlineImages_msg", {
"{size}": MAX_BASE64_IMAGE_SIZE / 1024,
}),
)
}
})
}
import { isDraft } from "../model/MailChecks.js"
export async function showHeaderDialog(headersPromise: Promise<string | null>) {
let state: { state: "loading" } | { state: "loaded"; headers: string | null } = { state: "loading" }

View file

@ -22,8 +22,8 @@ import { CalendarFacade } from "../common/api/worker/facades/lazy/CalendarFacade
import { MailFacade } from "../common/api/worker/facades/lazy/MailFacade.js"
import { ShareFacade } from "../common/api/worker/facades/lazy/ShareFacade.js"
import { CounterFacade } from "../common/api/worker/facades/lazy/CounterFacade.js"
import { Indexer } from "../common/api/worker/search/Indexer.js"
import { SearchFacade } from "../common/api/worker/search/SearchFacade.js"
import { Indexer } from "./workerUtils/index/Indexer.js"
import { SearchFacade } from "./workerUtils/index/SearchFacade.js"
import { BookingFacade } from "../common/api/worker/facades/lazy/BookingFacade.js"
import { MailAddressFacade } from "../common/api/worker/facades/lazy/MailAddressFacade.js"
import { BlobFacade } from "../common/api/worker/facades/lazy/BlobFacade.js"
@ -44,7 +44,6 @@ import { InterWindowEventFacadeSendDispatcher } from "../common/native/common/ge
import { ExposedCacheStorage } from "../common/api/worker/rest/DefaultEntityRestCache.js"
import { WorkerFacade } from "../common/api/worker/facades/WorkerFacade.js"
import { PageContextLoginListener } from "../common/api/main/PageContextLoginListener.js"
import { WorkerRandomizer } from "../common/api/worker/WorkerImpl.js"
import { WebsocketConnectivityModel } from "../common/misc/WebsocketConnectivityModel.js"
import { OperationProgressTracker } from "../common/api/main/OperationProgressTracker.js"
import { InfoMessageHandler } from "../common/gui/InfoMessageHandler.js"
@ -117,12 +116,16 @@ import { AppStorePaymentPicker } from "../common/misc/AppStorePaymentPicker.js"
import { MAIL_PREFIX } from "../common/misc/RouteChange.js"
import { getDisplayedSender } from "../common/api/common/CommonMailUtils.js"
import { MailModel } from "./mail/model/MailModel.js"
import { AppType } from "../common/misc/ClientConstants.js"
import type { ParsedEvent } from "../common/calendar/import/CalendarImporter.js"
import { assertSystemFolderOfType } from "./mail/model/MailUtils.js"
import { WorkerRandomizer } from "../common/api/worker/workerInterfaces.js"
import { SearchCategoryTypes } from "./search/model/SearchUtils.js"
import { WorkerInterface } from "./workerUtils/worker/WorkerImpl.js"
import { isMailInSpamOrTrash } from "./mail/model/MailChecks.js"
import type { ContactImporter } from "./contacts/ContactImporter.js"
import { ExternalCalendarFacade } from "../common/native/common/generatedipc/ExternalCalendarFacade.js"
import m from "mithril"
import { assertSystemFolderOfType, isMailInSpamOrTrash } from "./mail/model/MailUtils.js"
import { AppType } from "../common/misc/ClientConstants.js"
import { ParsedEvent } from "../common/calendar/import/CalendarImporter.js"
assertMainOrNode()
@ -545,12 +548,12 @@ class MailLocator {
}
async ownMailAddressNameChanger(): Promise<MailAddressNameChanger> {
const { OwnMailAddressNameChanger } = await import("../mail-app/settings/mailaddress/OwnMailAddressNameChanger.js")
const { OwnMailAddressNameChanger } = await import("../common/settings/mailaddress/OwnMailAddressNameChanger.js")
return new OwnMailAddressNameChanger(this.mailboxModel, this.entityClient)
}
async adminNameChanger(mailGroupId: Id, userId: Id): Promise<MailAddressNameChanger> {
const { AnotherUserMailAddressNameChanger } = await import("../mail-app/settings/mailaddress/AnotherUserMailAddressNameChanger.js")
const { AnotherUserMailAddressNameChanger } = await import("../common/settings/mailaddress/AnotherUserMailAddressNameChanger.js")
return new AnotherUserMailAddressNameChanger(this.mailAddressFacade, mailGroupId, userId)
}
@ -570,7 +573,12 @@ class MailLocator {
const { NoopCredentialRemovalHandler, AppsCredentialRemovalHandler } = await import("../common/login/CredentialRemovalHandler.js")
return isBrowser()
? new NoopCredentialRemovalHandler()
: new AppsCredentialRemovalHandler(this.indexerFacade, this.pushService, this.configFacade, isApp() ? this.nativeContactsSyncManager() : null)
: new AppsCredentialRemovalHandler(this.pushService, this.configFacade, async (login, userId) => {
if (isApp()) {
await mailLocator.nativeContactsSyncManager().disableSync(userId, login)
}
await mailLocator.indexerFacade.deleteIndex(userId)
})
}
async loginViewModelFactory(): Promise<lazy<LoginViewModel>> {
@ -660,7 +668,7 @@ class MailLocator {
workerFacade,
sqlCipherFacade,
contactFacade,
} = this.worker.getWorkerInterface()
} = this.worker.getWorkerInterface() as WorkerInterface
this.loginFacade = loginFacade
this.customerFacade = customerFacade
this.giftCardFacade = giftCardFacade
@ -867,7 +875,20 @@ class MailLocator {
: new FileControllerNative(blobFacade, guiDownload, this.nativeInterfaces.fileApp)
const { ContactModel } = await import("../common/contactsFunctionality/ContactModel.js")
this.contactModel = new ContactModel(this.searchFacade, this.entityClient, this.logins, this.eventController)
this.contactModel = new ContactModel(
this.entityClient,
this.logins,
this.eventController,
async (query: string, field: string, minSuggestionCount: number, maxResults?: number) => {
const { createRestriction } = await import("./search/model/SearchUtils.js")
return mailLocator.searchFacade.search(
query,
createRestriction(SearchCategoryTypes.contact, null, null, field, [], null),
minSuggestionCount,
maxResults,
)
},
)
this.minimizedMailModel = new MinimizedMailEditorViewModel()
this.appStorePaymentPicker = new AppStorePaymentPicker()

View file

@ -23,6 +23,7 @@ import { formatEventDuration } from "../../calendar-app/calendar/gui/CalendarGui
import { getContactListName } from "../../common/contactsFunctionality/ContactUtils.js"
import { getSenderOrRecipientHeading } from "../mail/view/MailViewerUtils.js"
import { mailLocator } from "../mailLocator.js"
type SearchBarOverlayAttrs = {
state: SearchBarState
@ -107,7 +108,7 @@ export class SearchBarOverlay implements Component<SearchBarOverlayAttrs> {
},
m(Button, {
label: "cancel_action",
click: () => locator.indexerFacade.cancelMailIndexing(),
click: () => mailLocator.indexerFacade.cancelMailIndexing(),
//icon: () => Icons.Cancel
type: ButtonType.Secondary,
}),
@ -148,7 +149,7 @@ export class SearchBarOverlay implements Component<SearchBarOverlayAttrs> {
},
m(Button, {
label: "retry_action",
click: () => locator.indexerFacade.extendMailIndex(failedIndexingUpTo),
click: () => mailLocator.indexerFacade.extendMailIndex(failedIndexingUpTo),
type: ButtonType.Secondary,
}),
),

View file

@ -5,12 +5,13 @@ import { NOTHING_INDEXED_TIMESTAMP } from "../../../common/api/common/TutanotaCo
import { DbError } from "../../../common/api/common/error/DbError"
import type { SearchIndexStateInfo, SearchRestriction, SearchResult } from "../../../common/api/worker/search/SearchTypes"
import { arrayEquals, assertNonNull, assertNotNull, incrementMonth, isSameTypeRef, lazyAsync, ofClass, tokenize } from "@tutao/tutanota-utils"
import type { SearchFacade } from "../../../common/api/worker/search/SearchFacade"
import type { SearchFacade } from "../../workerUtils/index/SearchFacade.js"
import { assertMainOrNode } from "../../../common/api/common/Env"
import { listIdPart } from "../../../common/api/common/utils/EntityUtils.js"
import { IProgressMonitor } from "../../../common/api/common/utils/ProgressMonitor.js"
import { ProgressTracker } from "../../../common/api/main/ProgressTracker.js"
import { CalendarEventsRepository } from "../../../common/calendar/date/CalendarEventsRepository.js"
import { CommonSearchModel } from "../../../common/search/CommonSearchModel.js"
assertMainOrNode()
export type SearchQuery = {
@ -28,8 +29,8 @@ export class SearchModel {
lastQueryString: Stream<string | null>
indexingSupported: boolean
_searchFacade: SearchFacade
private _lastQuery: SearchQuery | null
_lastSearchPromise: Promise<SearchResult | void>
private lastQuery: SearchQuery | null
private lastSearchPromise: Promise<SearchResult | void>
cancelSignal: Stream<boolean>
constructor(searchFacade: SearchFacade, private readonly calendarModel: lazyAsync<CalendarEventsRepository>) {
@ -46,17 +47,17 @@ export class SearchModel {
indexedMailCount: 0,
failedIndexingUpTo: null,
})
this._lastQuery = null
this._lastSearchPromise = Promise.resolve()
this.lastQuery = null
this.lastSearchPromise = Promise.resolve()
this.cancelSignal = stream(false)
}
async search(searchQuery: SearchQuery, progressTracker: ProgressTracker): Promise<SearchResult | void> {
if (this._lastQuery && searchQueryEquals(searchQuery, this._lastQuery)) {
return this._lastSearchPromise
if (this.lastQuery && searchQueryEquals(searchQuery, this.lastQuery)) {
return this.lastSearchPromise
}
this._lastQuery = searchQuery
this.lastQuery = searchQuery
const { query, restriction, minSuggestionCount, maxResults } = searchQuery
this.lastQueryString(query)
let result = this.result()
@ -83,7 +84,7 @@ export class SearchModel {
moreResultsEntries: [],
}
this.result(result)
this._lastSearchPromise = Promise.resolve(result)
this.lastSearchPromise = Promise.resolve(result)
} else if (isSameTypeRef(CalendarEventTypeRef, restriction.type)) {
// we interpret restriction.start as the start of the first day of the first month we want to search
// restriction.end is the end of the last day of the last month we want to search
@ -114,8 +115,8 @@ export class SearchModel {
if (this.cancelSignal()) {
this.result(calendarResult)
this._lastSearchPromise = Promise.resolve(calendarResult)
return this._lastSearchPromise
this.lastSearchPromise = Promise.resolve(calendarResult)
return this.lastSearchPromise
}
await calendarModel.loadMonthsIfNeeded(daysInMonths, monitor, this.cancelSignal)
@ -133,8 +134,8 @@ export class SearchModel {
if (this.cancelSignal()) {
this.result(calendarResult)
this._lastSearchPromise = Promise.resolve(calendarResult)
return this._lastSearchPromise
this.lastSearchPromise = Promise.resolve(calendarResult)
return this.lastSearchPromise
}
if (tokens.length > 0) {
@ -183,17 +184,17 @@ export class SearchModel {
if (this.cancelSignal()) {
this.result(calendarResult)
this._lastSearchPromise = Promise.resolve(calendarResult)
return this._lastSearchPromise
this.lastSearchPromise = Promise.resolve(calendarResult)
return this.lastSearchPromise
}
}
}
}
this.result(calendarResult)
this._lastSearchPromise = Promise.resolve(calendarResult)
this.lastSearchPromise = Promise.resolve(calendarResult)
} else {
this._lastSearchPromise = this._searchFacade
this.lastSearchPromise = this._searchFacade
.search(query, restriction, minSuggestionCount, maxResults ?? undefined)
.then((result) => {
this.result(result)
@ -207,12 +208,12 @@ export class SearchModel {
)
}
return this._lastSearchPromise
return this.lastSearchPromise
}
isNewSearch(query: string, restriction: SearchRestriction): boolean {
let isNew = false
let lastQuery = this._lastQuery
let lastQuery = this.lastQuery
if (lastQuery == null) {
isNew = true
} else if (lastQuery.query !== query) {

View file

@ -48,9 +48,9 @@ import {
} from "../model/SearchUtils.js"
import Stream from "mithril/stream"
import { MailboxDetail, MailboxModel } from "../../../common/mailFunctionality/MailboxModel.js"
import { SearchFacade } from "../../../common/api/worker/search/SearchFacade.js"
import { SearchFacade } from "../../workerUtils/index/SearchFacade.js"
import { LoginController } from "../../../common/api/main/LoginController.js"
import { Indexer } from "../../../common/api/worker/search/Indexer.js"
import { Indexer } from "../../workerUtils/index/Indexer.js"
import { EntityClient, loadMultipleFromLists } from "../../../common/api/common/EntityClient.js"
import { SearchRouter } from "../../../common/search/view/SearchRouter.js"
import { MailOpenedListener } from "../../mail/view/MailViewModel.js"

View file

@ -12,7 +12,6 @@ import { DropDownSelector } from "../../common/gui/base/DropDownSelector.js"
import { Dialog } from "../../common/gui/base/Dialog"
import type { UpdateHelpLabelAttrs } from "./DesktopUpdateHelpLabel"
import { DesktopUpdateHelpLabel } from "./DesktopUpdateHelpLabel"
import type { MailExportMode } from "../mail/export/Exporter"
import { DesktopConfigKey } from "../../common/desktop/config/ConfigKeys"
import { getCurrentSpellcheckLanguageLabel, showSpellcheckLanguageDialog } from "../../common/gui/dialogs/SpellcheckLanguageDialog"
import { ifAllowedTutaLinks } from "../../common/gui/base/GuiUtils"
@ -22,6 +21,7 @@ import { IconButton, IconButtonAttrs } from "../../common/gui/base/IconButton.js
import { ButtonSize } from "../../common/gui/base/ButtonSize.js"
import { MoreInfoLink } from "../../common/misc/news/MoreInfoLink.js"
import { UpdatableSettingsViewer } from "../../common/settings/Interfaces.js"
import { MailExportMode } from "../../common/mailFunctionality/SharedMailUtils.js"
assertMainOrNode()

View file

@ -4,13 +4,13 @@ import { lang } from "../../common/misc/LanguageViewModel"
import { EmailSignatureType, FeatureType } from "../../common/api/common/TutanotaConstants"
import { HtmlEditor } from "../../common/gui/editor/HtmlEditor"
import type { TutanotaProperties } from "../../common/api/entities/tutanota/TypeRefs.js"
import { insertInlineImageB64ClickHandler } from "../mail/view/MailViewerUtils"
import { PayloadTooLargeError } from "../../common/api/common/error/RestError"
import { showProgressDialog } from "../../common/gui/dialogs/ProgressDialog"
import { neverNull, ofClass } from "@tutao/tutanota-utils"
import { locator } from "../../common/api/main/CommonLocator"
import { assertMainOrNode } from "../../common/api/common/Env"
import { DropDownSelector } from "../../common/gui/base/DropDownSelector.js"
import { insertInlineImageB64ClickHandler } from "../../common/mailFunctionality/SharedMailUtils.js"
assertMainOrNode()
// signatures can become large, for example if they include a base64 embedded image. we ask for confirmation in such cases

View file

@ -16,7 +16,7 @@ import { IconButton, IconButtonAttrs } from "../../common/gui/base/IconButton.js
import { ButtonSize } from "../../common/gui/base/ButtonSize.js"
import { MailAddressAvailability } from "../../common/api/entities/sys/TypeRefs.js"
import { SearchDropDown } from "../../common/gui/SearchDropDown.js"
import { isTutanotaMailAddress } from "../../common/mailFunctionality/SharedMailUtils.js"
import { isTutaMailAddress } from "../../common/mailFunctionality/SharedMailUtils.js"
assertMainOrNode()
@ -274,7 +274,7 @@ export class SelectMailAddressFormWithSuggestions implements Component<SelectMai
this.onBusyStateChanged(false, onBusyStateChanged)
return { valid: false }
} else if (!isMailAddress(cleanMailAddress, true) || (isTutanotaMailAddress(cleanMailAddress) && cleanUsername.length < 3)) {
} else if (!isMailAddress(cleanMailAddress, true) || (isTutaMailAddress(cleanMailAddress) && cleanUsername.length < 3)) {
this.onValidationFinished(
cleanMailAddress,
{

View file

@ -1,17 +1,17 @@
import { NotAuthorizedError, NotFoundError } from "../../common/error/RestError"
import type { Contact, ContactList } from "../../entities/tutanota/TypeRefs.js"
import { ContactTypeRef } from "../../entities/tutanota/TypeRefs.js"
import { typeModels as tutanotaModels } from "../../entities/tutanota/TypeModels"
import type { Db, GroupData, IndexUpdate, SearchIndexEntry } from "./SearchTypes"
import { _createNewIndexUpdate, typeRefToTypeInfo } from "./IndexUtils"
import { NotAuthorizedError, NotFoundError } from "../../../common/api/common/error/RestError.js"
import type { Contact, ContactList } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { ContactTypeRef } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { typeModels as tutanotaModels } from "../../../common/api/entities/tutanota/TypeModels.js"
import type { Db, GroupData, IndexUpdate, SearchIndexEntry } from "../../../common/api/worker/search/SearchTypes.js"
import { _createNewIndexUpdate, typeRefToTypeInfo } from "../../../common/api/worker/search/IndexUtils.js"
import { neverNull, noOp, ofClass, promiseMap } from "@tutao/tutanota-utils"
import { FULL_INDEXED_TIMESTAMP, OperationType } from "../../common/TutanotaConstants"
import { IndexerCore } from "./IndexerCore"
import { SuggestionFacade } from "./SuggestionFacade"
import { FULL_INDEXED_TIMESTAMP, OperationType } from "../../../common/api/common/TutanotaConstants.js"
import { IndexerCore } from "./IndexerCore.js"
import { SuggestionFacade } from "./SuggestionFacade.js"
import { tokenize } from "@tutao/tutanota-utils"
import type { EntityUpdate } from "../../entities/sys/TypeRefs.js"
import { EntityClient } from "../../common/EntityClient"
import { GroupDataOS, MetaDataOS } from "./IndexTables.js"
import type { EntityUpdate } from "../../../common/api/entities/sys/TypeRefs.js"
import { EntityClient } from "../../../common/api/common/EntityClient.js"
import { GroupDataOS, MetaDataOS } from "../../../common/api/worker/search/IndexTables.js"
export class ContactIndexer {
_core: IndexerCore

View file

@ -1,9 +1,15 @@
import { ENTITY_EVENT_BATCH_TTL_DAYS, getMembershipGroupType, GroupType, NOTHING_INDEXED_TIMESTAMP, OperationType } from "../../common/TutanotaConstants"
import { ConnectionError, NotAuthorizedError, NotFoundError } from "../../common/error/RestError"
import type { EntityUpdate, GroupMembership, User } from "../../entities/sys/TypeRefs.js"
import { EntityEventBatch, EntityEventBatchTypeRef, UserTypeRef } from "../../entities/sys/TypeRefs.js"
import type { DatabaseEntry, DbKey, DbTransaction } from "./DbFacade"
import { b64UserIdHash, DbFacade } from "./DbFacade"
import {
ENTITY_EVENT_BATCH_TTL_DAYS,
getMembershipGroupType,
GroupType,
NOTHING_INDEXED_TIMESTAMP,
OperationType,
} from "../../../common/api/common/TutanotaConstants.js"
import { ConnectionError, NotAuthorizedError, NotFoundError } from "../../../common/api/common/error/RestError.js"
import type { EntityUpdate, GroupMembership, User } from "../../../common/api/entities/sys/TypeRefs.js"
import { EntityEventBatch, EntityEventBatchTypeRef, UserTypeRef } from "../../../common/api/entities/sys/TypeRefs.js"
import type { DatabaseEntry, DbKey, DbTransaction } from "../../../common/api/worker/search/DbFacade.js"
import { b64UserIdHash, DbFacade } from "../../../common/api/worker/search/DbFacade.js"
import {
assertNotNull,
contains,
@ -22,27 +28,34 @@ import {
promiseMap,
TypeRef,
} from "@tutao/tutanota-utils"
import { firstBiggerThanSecond, GENERATED_MAX_ID, generatedIdToTimestamp, getElementId, isSameId, timestampToGeneratedId } from "../../common/utils/EntityUtils"
import { _createNewIndexUpdate, filterIndexMemberships, markEnd, markStart, typeRefToTypeInfo } from "./IndexUtils"
import type { Db, GroupData } from "./SearchTypes"
import { IndexingErrorReason } from "./SearchTypes"
import { ContactIndexer } from "./ContactIndexer"
import { ContactList, ContactListTypeRef, ContactTypeRef, MailTypeRef } from "../../entities/tutanota/TypeRefs.js"
import { MailIndexer } from "./MailIndexer"
import { IndexerCore } from "./IndexerCore"
import type { EntityRestClient } from "../rest/EntityRestClient"
import { OutOfSyncError } from "../../common/error/OutOfSyncError"
import { SuggestionFacade } from "./SuggestionFacade"
import { DbError } from "../../common/error/DbError"
import type { QueuedBatch } from "../EventQueue.js"
import { EventQueue } from "../EventQueue.js"
import { CancelledError } from "../../common/error/CancelledError"
import { MembershipRemovedError } from "../../common/error/MembershipRemovedError"
import type { BrowserData } from "../../../misc/ClientConstants"
import { InvalidDatabaseStateError } from "../../common/error/InvalidDatabaseStateError"
import { LocalTimeDateProvider } from "../DateProvider"
import { EntityClient } from "../../common/EntityClient"
import { deleteObjectStores } from "../utils/DbUtils"
import {
firstBiggerThanSecond,
GENERATED_MAX_ID,
generatedIdToTimestamp,
getElementId,
isSameId,
timestampToGeneratedId,
} from "../../../common/api/common/utils/EntityUtils.js"
import { _createNewIndexUpdate, filterIndexMemberships, markEnd, markStart, typeRefToTypeInfo } from "../../../common/api/worker/search/IndexUtils.js"
import type { Db, GroupData } from "../../../common/api/worker/search/SearchTypes.js"
import { IndexingErrorReason } from "../../../common/api/worker/search/SearchTypes.js"
import { ContactIndexer } from "./ContactIndexer.js"
import { ContactList, ContactListTypeRef, ContactTypeRef, MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { MailIndexer } from "./MailIndexer.js"
import { IndexerCore } from "./IndexerCore.js"
import type { EntityRestClient } from "../../../common/api/worker/rest/EntityRestClient.js"
import { OutOfSyncError } from "../../../common/api/common/error/OutOfSyncError.js"
import { SuggestionFacade } from "./SuggestionFacade.js"
import { DbError } from "../../../common/api/common/error/DbError.js"
import type { QueuedBatch } from "../../../common/api/worker/EventQueue.js"
import { EventQueue } from "../../../common/api/worker/EventQueue.js"
import { CancelledError } from "../../../common/api/common/error/CancelledError.js"
import { MembershipRemovedError } from "../../../common/api/common/error/MembershipRemovedError.js"
import type { BrowserData } from "../../../common/misc/ClientConstants.js"
import { InvalidDatabaseStateError } from "../../../common/api/common/error/InvalidDatabaseStateError.js"
import { LocalTimeDateProvider } from "../../../common/api/worker/DateProvider.js"
import { EntityClient } from "../../../common/api/common/EntityClient.js"
import { deleteObjectStores } from "../../../common/api/worker/utils/DbUtils.js"
import {
aes256EncryptSearchIndexEntry,
aes256RandomKey,
@ -53,9 +66,9 @@ import {
unauthenticatedAesDecrypt,
BitArray,
} from "@tutao/tutanota-crypto"
import { DefaultEntityRestCache } from "../rest/DefaultEntityRestCache.js"
import { CacheInfo } from "../facades/LoginFacade.js"
import { InfoMessageHandler } from "../../../gui/InfoMessageHandler.js"
import { DefaultEntityRestCache } from "../../../common/api/worker/rest/DefaultEntityRestCache.js"
import { CacheInfo } from "../../../common/api/worker/facades/LoginFacade.js"
import { InfoMessageHandler } from "../../../common/gui/InfoMessageHandler.js"
import {
ElementDataOS,
EncryptedIndexerMetaData,
@ -66,11 +79,11 @@ import {
SearchIndexOS,
SearchIndexWordsIndex,
SearchTermSuggestionsOS,
} from "./IndexTables.js"
import { MailFacade } from "../facades/lazy/MailFacade.js"
import { encryptKeyWithVersionedKey, VersionedKey } from "../crypto/CryptoFacade.js"
import { KeyLoaderFacade } from "../facades/KeyLoaderFacade.js"
import { getIndexerMetaData, updateEncryptionMetadata } from "../facades/lazy/ConfigurationDatabase.js"
} from "../../../common/api/worker/search/IndexTables.js"
import { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade.js"
import { encryptKeyWithVersionedKey, VersionedKey } from "../../../common/api/worker/crypto/CryptoFacade.js"
import { KeyLoaderFacade } from "../../../common/api/worker/facades/KeyLoaderFacade.js"
import { getIndexerMetaData, updateEncryptionMetadata } from "../../../common/api/worker/facades/lazy/ConfigurationDatabase.js"
export type InitParams = {
user: User

View file

@ -1,4 +1,4 @@
import type { DbTransaction } from "./DbFacade"
import type { DbTransaction } from "../../../common/api/worker/search/DbFacade.js"
import type { $Promisable, DeferredObject, PromiseMapFn } from "@tutao/tutanota-utils"
import {
arrayHash,
@ -17,7 +17,7 @@ import {
TypeRef,
uint8ArrayToBase64,
} from "@tutao/tutanota-utils"
import { elementIdPart, firstBiggerThanSecond, generatedIdToTimestamp, listIdPart } from "../../common/utils/EntityUtils"
import { elementIdPart, firstBiggerThanSecond, generatedIdToTimestamp, listIdPart } from "../../../common/api/common/utils/EntityUtils.js"
import {
compareMetaEntriesOldest,
decryptIndexKey,
@ -29,7 +29,7 @@ import {
getIdFromEncSearchIndexEntry,
getPerformanceTimestamp,
typeRefToTypeInfo,
} from "./IndexUtils"
} from "../../../common/api/worker/search/IndexUtils.js"
import type {
AttributeHandler,
B64EncIndexKey,
@ -45,13 +45,13 @@ import type {
SearchIndexMetaDataDbRow,
SearchIndexMetadataEntry,
SearchIndexMetaDataRow,
} from "./SearchTypes"
import type { QueuedBatch } from "../EventQueue.js"
import { EventQueue } from "../EventQueue.js"
import { CancelledError } from "../../common/error/CancelledError"
import { ProgrammingError } from "../../common/error/ProgrammingError"
import type { BrowserData } from "../../../misc/ClientConstants"
import { InvalidDatabaseStateError } from "../../common/error/InvalidDatabaseStateError"
} from "../../../common/api/worker/search/SearchTypes.js"
import type { QueuedBatch } from "../../../common/api/worker/EventQueue.js"
import { EventQueue } from "../../../common/api/worker/EventQueue.js"
import { CancelledError } from "../../../common/api/common/error/CancelledError.js"
import { ProgrammingError } from "../../../common/api/common/error/ProgrammingError.js"
import type { BrowserData } from "../../../common/misc/ClientConstants.js"
import { InvalidDatabaseStateError } from "../../../common/api/common/error/InvalidDatabaseStateError.js"
import {
appendBinaryBlocks,
calculateNeededSpaceForNumbers,
@ -59,10 +59,17 @@ import {
encodeNumbers,
iterateBinaryBlocks,
removeBinaryBlockRanges,
} from "./SearchIndexEncoding"
import type { EntityUpdate } from "../../entities/sys/TypeRefs.js"
} from "../../../common/api/worker/search/SearchIndexEncoding.js"
import type { EntityUpdate } from "../../../common/api/entities/sys/TypeRefs.js"
import { aes256EncryptSearchIndexEntry, aesDecrypt, unauthenticatedAesDecrypt } from "@tutao/tutanota-crypto"
import { ElementDataOS, GroupDataOS, MetaDataOS, SearchIndexMetaDataOS, SearchIndexOS, SearchIndexWordsIndex } from "./IndexTables.js"
import {
ElementDataOS,
GroupDataOS,
MetaDataOS,
SearchIndexMetaDataOS,
SearchIndexOS,
SearchIndexWordsIndex,
} from "../../../common/api/worker/search/IndexTables.js"
const SEARCH_INDEX_ROW_LENGTH = 1000

View file

@ -1,5 +1,5 @@
import { FULL_INDEXED_TIMESTAMP, MailSetKind, MailState, NOTHING_INDEXED_TIMESTAMP, OperationType } from "../../common/TutanotaConstants"
import type { File as TutanotaFile, Mail, MailBox, MailDetails, MailFolder } from "../../entities/tutanota/TypeRefs.js"
import { FULL_INDEXED_TIMESTAMP, MailSetKind, MailState, NOTHING_INDEXED_TIMESTAMP, OperationType } from "../../../common/api/common/TutanotaConstants"
import type { File as TutanotaFile, Mail, MailBox, MailDetails, MailFolder } from "../../../common/api/entities/tutanota/TypeRefs.js"
import {
FileTypeRef,
MailboxGroupRootTypeRef,
@ -8,9 +8,9 @@ import {
MailDetailsDraftTypeRef,
MailFolderTypeRef,
MailTypeRef,
} from "../../entities/tutanota/TypeRefs.js"
import { ConnectionError, NotAuthorizedError, NotFoundError } from "../../common/error/RestError"
import { typeModels } from "../../entities/tutanota/TypeModels"
} from "../../../common/api/entities/tutanota/TypeRefs.js"
import { ConnectionError, NotAuthorizedError, NotFoundError } from "../../../common/api/common/error/RestError.js"
import { typeModels } from "../../../common/api/entities/tutanota/TypeModels.js"
import { assertNotNull, first, groupBy, groupByAndMap, isNotNull, neverNull, noOp, ofClass, promiseMap, splitInChunks, TypeRef } from "@tutao/tutanota-utils"
import {
elementIdPart,
@ -21,29 +21,36 @@ import {
LEGACY_TO_RECIPIENTS_ID,
listIdPart,
timestampToGeneratedId,
} from "../../common/utils/EntityUtils"
import { _createNewIndexUpdate, encryptIndexKeyBase64, filterMailMemberships, getPerformanceTimestamp, htmlToText, typeRefToTypeInfo } from "./IndexUtils"
import { Db, GroupData, IndexingErrorReason, IndexUpdate, SearchIndexEntry } from "./SearchTypes"
import { CancelledError } from "../../common/error/CancelledError"
import { IndexerCore } from "./IndexerCore"
import { DbError } from "../../common/error/DbError"
import { DefaultEntityRestCache } from "../rest/DefaultEntityRestCache.js"
import type { DateProvider } from "../DateProvider"
import type { EntityUpdate, GroupMembership, User } from "../../entities/sys/TypeRefs.js"
import { EntityRestClient, OwnerEncSessionKeyProvider } from "../rest/EntityRestClient"
import { EntityClient } from "../../common/EntityClient"
import { ProgressMonitor } from "../../common/utils/ProgressMonitor"
import type { SomeEntity } from "../../common/EntityTypes"
import { EphemeralCacheStorage } from "../rest/EphemeralCacheStorage"
import { InfoMessageHandler } from "../../../gui/InfoMessageHandler.js"
import { ElementDataOS, GroupDataOS, Metadata, MetaDataOS } from "./IndexTables.js"
import { MailFacade } from "../facades/lazy/MailFacade.js"
import { containsEventOfType, EntityUpdateData } from "../../common/utils/EntityUpdateUtils.js"
import { b64UserIdHash } from "./DbFacade.js"
import { hasError } from "../../common/utils/ErrorUtils.js"
import { getDisplayedSender, getMailBodyText, MailAddressAndName } from "../../common/CommonMailUtils.js"
} from "../../../common/api/common/utils/EntityUtils.js"
import {
_createNewIndexUpdate,
encryptIndexKeyBase64,
filterMailMemberships,
getPerformanceTimestamp,
htmlToText,
typeRefToTypeInfo,
} from "../../../common/api/worker/search/IndexUtils.js"
import { Db, GroupData, IndexingErrorReason, IndexUpdate, SearchIndexEntry } from "../../../common/api/worker/search/SearchTypes.js"
import { CancelledError } from "../../../common/api/common/error/CancelledError.js"
import { IndexerCore } from "./IndexerCore.js"
import { DbError } from "../../../common/api/common/error/DbError.js"
import { DefaultEntityRestCache } from "../../../common/api/worker/rest/DefaultEntityRestCache.js"
import type { DateProvider } from "../../../common/api/worker/DateProvider.js"
import type { EntityUpdate, GroupMembership, User } from "../../../common/api/entities/sys/TypeRefs.js"
import { EntityRestClient, OwnerEncSessionKeyProvider } from "../../../common/api/worker/rest/EntityRestClient.js"
import { EntityClient } from "../../../common/api/common/EntityClient.js"
import { ProgressMonitor } from "../../../common/api/common/utils/ProgressMonitor.js"
import type { SomeEntity } from "../../../common/api/common/EntityTypes.js"
import { EphemeralCacheStorage } from "../../../common/api/worker/rest/EphemeralCacheStorage.js"
import { InfoMessageHandler } from "../../../common/gui/InfoMessageHandler.js"
import { ElementDataOS, GroupDataOS, Metadata, MetaDataOS } from "../../../common/api/worker/search/IndexTables.js"
import { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade.js"
import { containsEventOfType, EntityUpdateData } from "../../../common/api/common/utils/EntityUpdateUtils.js"
import { b64UserIdHash } from "../../../common/api/worker/search/DbFacade.js"
import { hasError } from "../../../common/api/common/utils/ErrorUtils.js"
import { getDisplayedSender, getMailBodyText, MailAddressAndName } from "../../../common/api/common/CommonMailUtils.js"
import { isDraft } from "../../../../mail-app/mail/model/MailUtils.js"
import { isDraft } from "../../mail/model/MailChecks.js"
export const INITIAL_MAIL_INDEX_INTERVAL_DAYS = 28
const ENTITY_INDEXER_CHUNK = 20

View file

@ -1,6 +1,6 @@
import { MailTypeRef } from "../../entities/tutanota/TypeRefs.js"
import { DbTransaction } from "./DbFacade"
import { resolveTypeReference } from "../../common/EntityFunctions"
import { MailTypeRef } from "../../../common/api/entities/tutanota/TypeRefs.js"
import { DbTransaction } from "../../../common/api/worker/search/DbFacade.js"
import { resolveTypeReference } from "../../../common/api/common/EntityFunctions.js"
import {
arrayHash,
asyncFind,
@ -36,8 +36,8 @@ import type {
SearchIndexMetaDataRow,
SearchRestriction,
SearchResult,
} from "./SearchTypes"
import type { TypeInfo } from "./IndexUtils"
} from "../../../common/api/worker/search/SearchTypes.js"
import type { TypeInfo } from "../../../common/api/worker/search/IndexUtils.js"
import {
decryptMetaData,
decryptSearchIndexEntry,
@ -48,19 +48,19 @@ import {
markStart,
printMeasure,
typeRefToTypeInfo,
} from "./IndexUtils"
import { FULL_INDEXED_TIMESTAMP, NOTHING_INDEXED_TIMESTAMP } from "../../common/TutanotaConstants"
import { compareNewestFirst, elementIdPart, firstBiggerThanSecond, getListId, timestampToGeneratedId } from "../../common/utils/EntityUtils"
import { INITIAL_MAIL_INDEX_INTERVAL_DAYS, MailIndexer } from "./MailIndexer"
import { SuggestionFacade } from "./SuggestionFacade"
import { AssociationType, Cardinality, ValueType } from "../../common/EntityConstants.js"
import { NotAuthorizedError, NotFoundError } from "../../common/error/RestError"
import { iterateBinaryBlocks } from "./SearchIndexEncoding"
import type { BrowserData } from "../../../misc/ClientConstants"
import type { TypeModel } from "../../common/EntityTypes"
import { EntityClient } from "../../common/EntityClient"
import { UserFacade } from "../facades/UserFacade"
import { ElementDataOS, SearchIndexMetaDataOS, SearchIndexOS, SearchIndexWordsIndex } from "./IndexTables.js"
} from "../../../common/api/worker/search/IndexUtils.js"
import { FULL_INDEXED_TIMESTAMP, NOTHING_INDEXED_TIMESTAMP } from "../../../common/api/common/TutanotaConstants.js"
import { compareNewestFirst, elementIdPart, firstBiggerThanSecond, getListId, timestampToGeneratedId } from "../../../common/api/common/utils/EntityUtils.js"
import { INITIAL_MAIL_INDEX_INTERVAL_DAYS, MailIndexer } from "./MailIndexer.js"
import { SuggestionFacade } from "./SuggestionFacade.js"
import { AssociationType, Cardinality, ValueType } from "../../../common/api/common/EntityConstants.js"
import { NotAuthorizedError, NotFoundError } from "../../../common/api/common/error/RestError.js"
import { iterateBinaryBlocks } from "../../../common/api/worker/search/SearchIndexEncoding.js"
import type { BrowserData } from "../../../common/misc/ClientConstants.js"
import type { TypeModel } from "../../../common/api/common/EntityTypes.js"
import { EntityClient } from "../../../common/api/common/EntityClient.js"
import { UserFacade } from "../../../common/api/worker/facades/UserFacade.js"
import { ElementDataOS, SearchIndexMetaDataOS, SearchIndexOS, SearchIndexWordsIndex } from "../../../common/api/worker/search/IndexTables.js"
type RowsToReadForIndexKey = {
indexKey: string

View file

@ -1,7 +1,7 @@
import type { Db } from "./SearchTypes"
import type { Db } from "../../../common/api/worker/search/SearchTypes.js"
import { stringToUtf8Uint8Array, TypeRef, utf8Uint8ArrayToString } from "@tutao/tutanota-utils"
import { aesDecrypt, aes256EncryptSearchIndexEntry, unauthenticatedAesDecrypt } from "@tutao/tutanota-crypto"
import { SearchTermSuggestionsOS } from "./IndexTables.js"
import { SearchTermSuggestionsOS } from "../../../common/api/worker/search/IndexTables.js"
export type SuggestionsType = Record<string, string[]>

View file

@ -1,5 +1,5 @@
import { UserTypeRef } from "../../common/api/entities/sys/TypeRefs.js"
import { AccountType, OFFLINE_STORAGE_DEFAULT_TIME_RANGE_DAYS } from "../../common/api/common/TutanotaConstants.js"
import { UserTypeRef } from "../../../common/api/entities/sys/TypeRefs.js"
import { AccountType, OFFLINE_STORAGE_DEFAULT_TIME_RANGE_DAYS } from "../../../common/api/common/TutanotaConstants.js"
import { assertNotNull, DAY_IN_MILLIS, groupByAndMap } from "@tutao/tutanota-utils"
import {
constructMailSetEntryId,
@ -10,7 +10,7 @@ import {
getElementId,
listIdPart,
timestampToGeneratedId,
} from "../../common/api/common/utils/EntityUtils.js"
} from "../../../common/api/common/utils/EntityUtils.js"
import {
FileTypeRef,
MailBoxTypeRef,
@ -19,10 +19,10 @@ import {
MailFolderTypeRef,
MailSetEntryTypeRef,
MailTypeRef,
} from "../../common/api/entities/tutanota/TypeRefs.js"
import { FolderSystem } from "../../common/api/common/mail/FolderSystem.js"
import { isDraft, isSpamOrTrashFolder } from "../mail/model/MailUtils.js"
import { OfflineStorage, OfflineStorageCleaner } from "../../common/api/worker/offline/OfflineStorage.js"
} from "../../../common/api/entities/tutanota/TypeRefs.js"
import { FolderSystem } from "../../../common/api/common/mail/FolderSystem.js"
import { OfflineStorage, OfflineStorageCleaner } from "../../../common/api/worker/offline/OfflineStorage.js"
import { isDraft, isSpamOrTrashFolder } from "../../mail/model/MailChecks.js"
export class MailOfflineCleaner implements OfflineStorageCleaner {
async cleanOfflineDb(offlineStorage: OfflineStorage, timeRangeDays: number | null, userId: Id, now: number): Promise<void> {

View file

@ -1,61 +1,46 @@
import type { Commands } from "../common/threading/MessageDispatcher.js"
import { errorToObj, MessageDispatcher, Request } from "../common/threading/MessageDispatcher.js"
import { BookingFacade } from "./facades/lazy/BookingFacade.js"
import { NotAuthenticatedError } from "../common/error/RestError"
import { ProgrammingError } from "../common/error/ProgrammingError"
import { initLocator, locator, resetLocator } from "./WorkerLocator"
import { assertWorkerOrNode, isMainOrNode } from "../common/Env"
import type { BrowserData } from "../../misc/ClientConstants"
import { CryptoFacade } from "./crypto/CryptoFacade"
import type { GiftCardFacade } from "./facades/lazy/GiftCardFacade.js"
import type { LoginFacade, LoginListener } from "./facades/LoginFacade"
import type { CustomerFacade } from "./facades/lazy/CustomerFacade.js"
import type { GroupManagementFacade } from "./facades/lazy/GroupManagementFacade.js"
import { ConfigurationDatabase } from "./facades/lazy/ConfigurationDatabase.js"
import { CalendarFacade } from "./facades/lazy/CalendarFacade.js"
import { MailFacade } from "./facades/lazy/MailFacade.js"
import { ShareFacade } from "./facades/lazy/ShareFacade.js"
import { CounterFacade } from "./facades/lazy/CounterFacade.js"
import type { Indexer } from "./search/Indexer"
import { SearchFacade } from "./search/SearchFacade"
import { MailAddressFacade } from "./facades/lazy/MailAddressFacade.js"
import { UserManagementFacade } from "./facades/lazy/UserManagementFacade.js"
import { DelayedImpls, exposeLocalDelayed, exposeRemote } from "../common/WorkerProxy"
import type { Commands } from "../../../common/api/common/threading/MessageDispatcher.js"
import { errorToObj, MessageDispatcher, Request } from "../../../common/api/common/threading/MessageDispatcher.js"
import { BookingFacade } from "../../../common/api/worker/facades/lazy/BookingFacade.js"
import { NotAuthenticatedError } from "../../../common/api/common/error/RestError.js"
import { ProgrammingError } from "../../../common/api/common/error/ProgrammingError.js"
import { initLocator, locator, resetLocator } from "./WorkerLocator.js"
import { assertWorkerOrNode, isMainOrNode } from "../../../common/api/common/Env.js"
import type { BrowserData } from "../../../common/misc/ClientConstants.js"
import { CryptoFacade } from "../../../common/api/worker/crypto/CryptoFacade.js"
import type { GiftCardFacade } from "../../../common/api/worker/facades/lazy/GiftCardFacade.js"
import type { LoginFacade } from "../../../common/api/worker/facades/LoginFacade.js"
import type { CustomerFacade } from "../../../common/api/worker/facades/lazy/CustomerFacade.js"
import type { GroupManagementFacade } from "../../../common/api/worker/facades/lazy/GroupManagementFacade.js"
import { ConfigurationDatabase } from "../../../common/api/worker/facades/lazy/ConfigurationDatabase.js"
import { CalendarFacade } from "../../../common/api/worker/facades/lazy/CalendarFacade.js"
import { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade.js"
import { ShareFacade } from "../../../common/api/worker/facades/lazy/ShareFacade.js"
import { CounterFacade } from "../../../common/api/worker/facades/lazy/CounterFacade.js"
import type { Indexer } from "../index/Indexer.js"
import { SearchFacade } from "../index/SearchFacade.js"
import { MailAddressFacade } from "../../../common/api/worker/facades/lazy/MailAddressFacade.js"
import { UserManagementFacade } from "../../../common/api/worker/facades/lazy/UserManagementFacade.js"
import { DelayedImpls, exposeLocalDelayed, exposeRemote } from "../../../common/api/common/WorkerProxy.js"
import { random } from "@tutao/tutanota-crypto"
import type { NativeInterface } from "../../native/common/NativeInterface"
import type { EntityRestInterface } from "./rest/EntityRestClient"
import { RestClient } from "./rest/RestClient"
import { IServiceExecutor } from "../common/ServiceRequest.js"
import { BlobFacade } from "./facades/lazy/BlobFacade.js"
import { ExposedCacheStorage } from "./rest/DefaultEntityRestCache.js"
import { BlobAccessTokenFacade } from "./facades/BlobAccessTokenFacade.js"
import { WebsocketConnectivityListener } from "../../misc/WebsocketConnectivityModel.js"
import { EventBusClient } from "./EventBusClient.js"
import { EntropyFacade } from "./facades/EntropyFacade.js"
import { ExposedProgressTracker } from "../main/ProgressTracker.js"
import { ExposedEventController } from "../main/EventController.js"
import { ExposedOperationProgressTracker } from "../main/OperationProgressTracker.js"
import { WorkerFacade } from "./facades/WorkerFacade.js"
import { InfoMessageHandler } from "../../gui/InfoMessageHandler.js"
import { SqlCipherFacade } from "../../native/common/generatedipc/SqlCipherFacade.js"
import { WebWorkerTransport } from "../common/threading/Transport.js"
import type { NativeInterface } from "../../../common/native/common/NativeInterface.js"
import type { EntityRestInterface } from "../../../common/api/worker/rest/EntityRestClient.js"
import { RestClient } from "../../../common/api/worker/rest/RestClient.js"
import { IServiceExecutor } from "../../../common/api/common/ServiceRequest.js"
import { BlobFacade } from "../../../common/api/worker/facades/lazy/BlobFacade.js"
import { ExposedCacheStorage } from "../../../common/api/worker/rest/DefaultEntityRestCache.js"
import { BlobAccessTokenFacade } from "../../../common/api/worker/facades/BlobAccessTokenFacade.js"
import { EntropyFacade } from "../../../common/api/worker/facades/EntropyFacade.js"
import { WorkerFacade } from "../../../common/api/worker/facades/WorkerFacade.js"
import { SqlCipherFacade } from "../../../common/native/common/generatedipc/SqlCipherFacade.js"
import { WebWorkerTransport } from "../../../common/api/common/threading/Transport.js"
import { ContactFacade } from "../../../common/api/worker/facades/lazy/ContactFacade.js"
import { RecoverCodeFacade } from "../../../common/api/worker/facades/lazy/RecoverCodeFacade.js"
import { CacheManagementFacade } from "../../../common/api/worker/facades/lazy/CacheManagementFacade.js"
import { ExposedEventBus, MainInterface, WorkerRandomizer } from "../../../common/api/worker/workerInterfaces.js"
import { CryptoError } from "@tutao/tutanota-crypto/error.js"
import { ContactFacade } from "./facades/lazy/ContactFacade.js"
import { RecoverCodeFacade } from "./facades/lazy/RecoverCodeFacade.js"
import { CacheManagementFacade } from "./facades/lazy/CacheManagementFacade.js"
import { OfflineStorageCleaner } from "./offline/OfflineStorage.js"
assertWorkerOrNode()
export interface WorkerRandomizer {
generateRandomNumber(numBytes: number): Promise<number>
}
export interface ExposedEventBus {
tryReconnect: EventBusClient["tryReconnect"]
close: EventBusClient["close"]
}
/** Interface of the facades exposed by the worker, basically interface for the worker itself */
export interface WorkerInterface {
readonly loginFacade: LoginFacade
@ -88,16 +73,6 @@ export interface WorkerInterface {
readonly contactFacade: ContactFacade
}
/** Interface for the "main"/webpage context of the app, interface for the worker client. */
export interface MainInterface {
readonly loginListener: LoginListener
readonly wsConnectivityListener: WebsocketConnectivityListener
readonly progressTracker: ExposedProgressTracker
readonly eventController: ExposedEventController
readonly operationProgressTracker: ExposedOperationProgressTracker
readonly infoMessageHandler: InfoMessageHandler
}
type WorkerRequest = Request<WorkerRequestType>
export class WorkerImpl implements NativeInterface {
@ -109,7 +84,7 @@ export class WorkerImpl implements NativeInterface {
this._dispatcher = new MessageDispatcher(new WebWorkerTransport(this._scope), this.queueCommands(this.exposedInterface), "worker-main")
}
async init(browserData: BrowserData, offlineStorageCleaner: OfflineStorageCleaner): Promise<void> {
async init(browserData: BrowserData): Promise<void> {
// import("tuta-sdk").then(async (module) => {
// // await module.default("wasm/tutasdk.wasm")
// const entityClient = new module.EntityClient()
@ -119,7 +94,7 @@ export class WorkerImpl implements NativeInterface {
// entityClient.free()
// })
await initLocator(this, browserData, offlineStorageCleaner)
await initLocator(this, browserData)
const workerScope = this._scope
// only register oncaught error handler if we are in the *real* worker scope

View file

@ -1,79 +1,90 @@
import { CacheInfo, LoginFacade, LoginListener } from "./facades/LoginFacade"
import type { WorkerImpl } from "./WorkerImpl"
import type { Indexer } from "./search/Indexer"
import type { EntityRestInterface } from "./rest/EntityRestClient"
import { EntityRestClient } from "./rest/EntityRestClient"
import type { UserManagementFacade } from "./facades/lazy/UserManagementFacade.js"
import { CacheStorage, DefaultEntityRestCache } from "./rest/DefaultEntityRestCache.js"
import type { GroupManagementFacade } from "./facades/lazy/GroupManagementFacade.js"
import type { MailFacade } from "./facades/lazy/MailFacade.js"
import type { MailAddressFacade } from "./facades/lazy/MailAddressFacade.js"
import type { CustomerFacade } from "./facades/lazy/CustomerFacade.js"
import type { CounterFacade } from "./facades/lazy/CounterFacade.js"
import { EventBusClient } from "./EventBusClient"
import { assertWorkerOrNode, getWebsocketBaseUrl, isAdminClient, isAndroidApp, isBrowser, isIOSApp, isOfflineStorageAvailable, isTest } from "../common/Env"
import { Const } from "../common/TutanotaConstants"
import type { BrowserData } from "../../misc/ClientConstants"
import type { CalendarFacade } from "./facades/lazy/CalendarFacade.js"
import type { ShareFacade } from "./facades/lazy/ShareFacade.js"
import { RestClient } from "./rest/RestClient"
import { SuspensionHandler } from "./SuspensionHandler"
import { EntityClient } from "../common/EntityClient"
import type { GiftCardFacade } from "./facades/lazy/GiftCardFacade.js"
import type { ConfigurationDatabase } from "./facades/lazy/ConfigurationDatabase.js"
import { DeviceEncryptionFacade } from "./facades/DeviceEncryptionFacade"
import type { NativeInterface } from "../../native/common/NativeInterface"
import { NativeFileApp } from "../../native/common/FileApp"
import { AesApp } from "../../native/worker/AesApp"
import type { RsaImplementation } from "./crypto/RsaImplementation"
import { createRsaImplementation } from "./crypto/RsaImplementation"
import { CryptoFacade } from "./crypto/CryptoFacade"
import { InstanceMapper } from "./crypto/InstanceMapper"
import { AdminClientDummyEntityRestCache } from "./rest/AdminClientDummyEntityRestCache.js"
import { SleepDetector } from "./utils/SleepDetector.js"
import { SchedulerImpl } from "../common/utils/Scheduler.js"
import { NoZoneDateProvider } from "../common/utils/NoZoneDateProvider.js"
import { LateInitializedCacheStorageImpl } from "./rest/CacheStorageProxy"
import { IServiceExecutor } from "../common/ServiceRequest"
import { ServiceExecutor } from "./rest/ServiceExecutor"
import type { BookingFacade } from "./facades/lazy/BookingFacade.js"
import type { BlobFacade } from "./facades/lazy/BlobFacade.js"
import { UserFacade } from "./facades/UserFacade"
import { OfflineStorage, OfflineStorageCleaner } from "./offline/OfflineStorage.js"
import { OFFLINE_STORAGE_MIGRATIONS, OfflineStorageMigrator } from "./offline/OfflineStorageMigrator.js"
import { modelInfos } from "../common/EntityFunctions.js"
import { FileFacadeSendDispatcher } from "../../native/common/generatedipc/FileFacadeSendDispatcher.js"
import { NativePushFacadeSendDispatcher } from "../../native/common/generatedipc/NativePushFacadeSendDispatcher.js"
import { NativeCryptoFacadeSendDispatcher } from "../../native/common/generatedipc/NativeCryptoFacadeSendDispatcher"
import { CacheInfo, LoginFacade, LoginListener } from "../../../common/api/worker/facades/LoginFacade.js"
import type { WorkerImpl } from "./WorkerImpl.js"
import type { Indexer } from "../index/Indexer.js"
import type { EntityRestInterface } from "../../../common/api/worker/rest/EntityRestClient.js"
import { EntityRestClient } from "../../../common/api/worker/rest/EntityRestClient.js"
import type { UserManagementFacade } from "../../../common/api/worker/facades/lazy/UserManagementFacade.js"
import { CacheStorage, DefaultEntityRestCache } from "../../../common/api/worker/rest/DefaultEntityRestCache.js"
import type { GroupManagementFacade } from "../../../common/api/worker/facades/lazy/GroupManagementFacade.js"
import type { MailFacade } from "../../../common/api/worker/facades/lazy/MailFacade.js"
import type { MailAddressFacade } from "../../../common/api/worker/facades/lazy/MailAddressFacade.js"
import type { CustomerFacade } from "../../../common/api/worker/facades/lazy/CustomerFacade.js"
import type { CounterFacade } from "../../../common/api/worker/facades/lazy/CounterFacade.js"
import { EventBusClient } from "../../../common/api/worker/EventBusClient.js"
import {
assertWorkerOrNode,
getWebsocketBaseUrl,
isAdminClient,
isAndroidApp,
isBrowser,
isIOSApp,
isOfflineStorageAvailable,
isTest,
} from "../../../common/api/common/Env.js"
import { Const } from "../../../common/api/common/TutanotaConstants.js"
import type { BrowserData } from "../../../common/misc/ClientConstants.js"
import type { CalendarFacade } from "../../../common/api/worker/facades/lazy/CalendarFacade.js"
import type { ShareFacade } from "../../../common/api/worker/facades/lazy/ShareFacade.js"
import { RestClient } from "../../../common/api/worker/rest/RestClient.js"
import { SuspensionHandler } from "../../../common/api/worker/SuspensionHandler.js"
import { EntityClient } from "../../../common/api/common/EntityClient.js"
import type { GiftCardFacade } from "../../../common/api/worker/facades/lazy/GiftCardFacade.js"
import type { ConfigurationDatabase } from "../../../common/api/worker/facades/lazy/ConfigurationDatabase.js"
import { DeviceEncryptionFacade } from "../../../common/api/worker/facades/DeviceEncryptionFacade.js"
import type { NativeInterface } from "../../../common/native/common/NativeInterface.js"
import { NativeFileApp } from "../../../common/native/common/FileApp.js"
import { AesApp } from "../../../common/native/worker/AesApp.js"
import type { RsaImplementation } from "../../../common/api/worker/crypto/RsaImplementation.js"
import { createRsaImplementation } from "../../../common/api/worker/crypto/RsaImplementation.js"
import { CryptoFacade } from "../../../common/api/worker/crypto/CryptoFacade.js"
import { InstanceMapper } from "../../../common/api/worker/crypto/InstanceMapper.js"
import { AdminClientDummyEntityRestCache } from "../../../common/api/worker/rest/AdminClientDummyEntityRestCache.js"
import { SleepDetector } from "../../../common/api/worker/utils/SleepDetector.js"
import { SchedulerImpl } from "../../../common/api/common/utils/Scheduler.js"
import { NoZoneDateProvider } from "../../../common/api/common/utils/NoZoneDateProvider.js"
import { LateInitializedCacheStorageImpl } from "../../../common/api/worker/rest/CacheStorageProxy.js"
import { IServiceExecutor } from "../../../common/api/common/ServiceRequest.js"
import { ServiceExecutor } from "../../../common/api/worker/rest/ServiceExecutor.js"
import type { BookingFacade } from "../../../common/api/worker/facades/lazy/BookingFacade.js"
import type { BlobFacade } from "../../../common/api/worker/facades/lazy/BlobFacade.js"
import { UserFacade } from "../../../common/api/worker/facades/UserFacade.js"
import { OfflineStorage, OfflineStorageCleaner } from "../../../common/api/worker/offline/OfflineStorage.js"
import { OFFLINE_STORAGE_MIGRATIONS, OfflineStorageMigrator } from "../../../common/api/worker/offline/OfflineStorageMigrator.js"
import { modelInfos } from "../../../common/api/common/EntityFunctions.js"
import { FileFacadeSendDispatcher } from "../../../common/native/common/generatedipc/FileFacadeSendDispatcher.js"
import { NativePushFacadeSendDispatcher } from "../../../common/native/common/generatedipc/NativePushFacadeSendDispatcher.js"
import { NativeCryptoFacadeSendDispatcher } from "../../../common/native/common/generatedipc/NativeCryptoFacadeSendDispatcher.js"
import { random } from "@tutao/tutanota-crypto"
import { ExportFacadeSendDispatcher } from "../../native/common/generatedipc/ExportFacadeSendDispatcher.js"
import { ExportFacadeSendDispatcher } from "../../../common/native/common/generatedipc/ExportFacadeSendDispatcher.js"
import { assertNotNull, delay, lazyAsync, lazyMemoized } from "@tutao/tutanota-utils"
import { InterWindowEventFacadeSendDispatcher } from "../../native/common/generatedipc/InterWindowEventFacadeSendDispatcher.js"
import { SqlCipherFacadeSendDispatcher } from "../../native/common/generatedipc/SqlCipherFacadeSendDispatcher.js"
import { EntropyFacade } from "./facades/EntropyFacade.js"
import { BlobAccessTokenFacade } from "./facades/BlobAccessTokenFacade.js"
import { OwnerEncSessionKeysUpdateQueue } from "./crypto/OwnerEncSessionKeysUpdateQueue.js"
import { EventBusEventCoordinator } from "./EventBusEventCoordinator.js"
import { WorkerFacade } from "./facades/WorkerFacade.js"
import { SqlCipherFacade } from "../../native/common/generatedipc/SqlCipherFacade.js"
import type { SearchFacade } from "./search/SearchFacade.js"
import { Challenge } from "../entities/sys/TypeRefs.js"
import { LoginFailReason } from "../main/PageContextLoginListener.js"
import { ConnectionError, ServiceUnavailableError } from "../common/error/RestError.js"
import { SessionType } from "../common/SessionType.js"
import { Argon2idFacade, NativeArgon2idFacade, WASMArgon2idFacade } from "./facades/Argon2idFacade.js"
import { DomainConfigProvider } from "../common/DomainConfigProvider.js"
import { KyberFacade, NativeKyberFacade, WASMKyberFacade } from "./facades/KyberFacade.js"
import { PQFacade } from "./facades/PQFacade.js"
import { PdfWriter } from "./pdf/PdfWriter.js"
import { ContactFacade } from "./facades/lazy/ContactFacade.js"
import { KeyLoaderFacade } from "./facades/KeyLoaderFacade.js"
import { KeyRotationFacade } from "./facades/KeyRotationFacade.js"
import { KeyCache } from "./facades/KeyCache.js"
import { cryptoWrapper } from "./crypto/CryptoWrapper.js"
import { RecoverCodeFacade } from "./facades/lazy/RecoverCodeFacade.js"
import { CacheManagementFacade } from "./facades/lazy/CacheManagementFacade.js"
import type { Credentials } from "../../misc/credentials/Credentials.js"
import { InterWindowEventFacadeSendDispatcher } from "../../../common/native/common/generatedipc/InterWindowEventFacadeSendDispatcher.js"
import { SqlCipherFacadeSendDispatcher } from "../../../common/native/common/generatedipc/SqlCipherFacadeSendDispatcher.js"
import { EntropyFacade } from "../../../common/api/worker/facades/EntropyFacade.js"
import { BlobAccessTokenFacade } from "../../../common/api/worker/facades/BlobAccessTokenFacade.js"
import { OwnerEncSessionKeysUpdateQueue } from "../../../common/api/worker/crypto/OwnerEncSessionKeysUpdateQueue.js"
import { EventBusEventCoordinator } from "../../../common/api/worker/EventBusEventCoordinator.js"
import { WorkerFacade } from "../../../common/api/worker/facades/WorkerFacade.js"
import { SqlCipherFacade } from "../../../common/native/common/generatedipc/SqlCipherFacade.js"
import type { SearchFacade } from "../index/SearchFacade.js"
import { Challenge } from "../../../common/api/entities/sys/TypeRefs.js"
import { LoginFailReason } from "../../../common/api/main/PageContextLoginListener.js"
import { ConnectionError, ServiceUnavailableError } from "../../../common/api/common/error/RestError.js"
import { SessionType } from "../../../common/api/common/SessionType.js"
import { Argon2idFacade, NativeArgon2idFacade, WASMArgon2idFacade } from "../../../common/api/worker/facades/Argon2idFacade.js"
import { DomainConfigProvider } from "../../../common/api/common/DomainConfigProvider.js"
import { KyberFacade, NativeKyberFacade, WASMKyberFacade } from "../../../common/api/worker/facades/KyberFacade.js"
import { PQFacade } from "../../../common/api/worker/facades/PQFacade.js"
import { PdfWriter } from "../../../common/api/worker/pdf/PdfWriter.js"
import { ContactFacade } from "../../../common/api/worker/facades/lazy/ContactFacade.js"
import { KeyLoaderFacade } from "../../../common/api/worker/facades/KeyLoaderFacade.js"
import { KeyRotationFacade } from "../../../common/api/worker/facades/KeyRotationFacade.js"
import { KeyCache } from "../../../common/api/worker/facades/KeyCache.js"
import { cryptoWrapper } from "../../../common/api/worker/crypto/CryptoWrapper.js"
import { RecoverCodeFacade } from "../../../common/api/worker/facades/lazy/RecoverCodeFacade.js"
import { CacheManagementFacade } from "../../../common/api/worker/facades/lazy/CacheManagementFacade.js"
import { MailOfflineCleaner } from "../offline/MailOfflineCleaner.js"
import type { QueuedBatch } from "../../../common/api/worker/EventQueue.js"
import { Credentials } from "../../../common/misc/credentials/Credentials.js"
assertWorkerOrNode()
@ -131,15 +142,17 @@ export type WorkerLocatorType = {
pdfWriter: lazyAsync<PdfWriter>
// used to cache between resets
_worker: WorkerImpl
_browserData: BrowserData
_offlineStorageCleaner: OfflineStorageCleaner
//contact
contactFacade: lazyAsync<ContactFacade>
}
export const locator: WorkerLocatorType = {} as any
export async function initLocator(worker: WorkerImpl, browserData: BrowserData, offlineStorageCleaner: OfflineStorageCleaner) {
export async function initLocator(worker: WorkerImpl, browserData: BrowserData) {
locator._worker = worker
locator._browserData = browserData
locator.keyCache = new KeyCache()
locator.user = new UserFacade(locator.keyCache)
locator.workerFacade = new WorkerFacade()
@ -158,12 +171,10 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
locator.entropyFacade = new EntropyFacade(locator.user, locator.serviceExecutor, random, () => locator.keyLoader)
locator.blobAccessToken = new BlobAccessTokenFacade(locator.serviceExecutor, dateProvider, locator.user)
const entityRestClient = new EntityRestClient(locator.user, locator.restClient, () => locator.crypto, locator.instanceMapper, locator.blobAccessToken)
locator._browserData = browserData
locator._offlineStorageCleaner = offlineStorageCleaner
locator.native = worker
locator.booking = lazyMemoized(async () => {
const { BookingFacade } = await import("./facades/lazy/BookingFacade.js")
const { BookingFacade } = await import("../../../common/api/worker/facades/lazy/BookingFacade.js")
return new BookingFacade(locator.serviceExecutor)
})
@ -176,18 +187,20 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
new InterWindowEventFacadeSendDispatcher(worker),
dateProvider,
new OfflineStorageMigrator(OFFLINE_STORAGE_MIGRATIONS, modelInfos),
offlineStorageCleaner,
new MailOfflineCleaner(),
)
}
} else {
offlineStorageProvider = async () => null
}
locator.pdfWriter = async () => {
const { PdfWriter } = await import("./pdf/PdfWriter.js")
const { PdfWriter } = await import("../../../common/api/worker/pdf/PdfWriter.js")
return new PdfWriter(new TextEncoder(), undefined)
}
const maybeUninitializedStorage = new LateInitializedCacheStorageImpl(worker, offlineStorageProvider)
const maybeUninitializedStorage = new LateInitializedCacheStorageImpl(async (error: Error) => {
await worker.sendError(error)
}, offlineStorageProvider)
locator.cacheStorage = maybeUninitializedStorage
@ -205,12 +218,12 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
const nonCachingEntityClient = new EntityClient(entityRestClient)
locator.cacheManagement = lazyMemoized(async () => {
const { CacheManagementFacade } = await import("./facades/lazy/CacheManagementFacade.js")
const { CacheManagementFacade } = await import("../../../common/api/worker/facades/lazy/CacheManagementFacade.js")
return new CacheManagementFacade(locator.user, locator.cachingEntityClient, assertNotNull(cache))
})
locator.indexer = lazyMemoized(async () => {
const { Indexer } = await import("./search/Indexer.js")
const { Indexer } = await import("../index/Indexer.js")
return new Indexer(entityRestClient, mainInterface.infoMessageHandler, browserData, locator.cache as DefaultEntityRestCache, await locator.mail())
})
@ -238,19 +251,19 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
)
locator.recoverCode = lazyMemoized(async () => {
const { RecoverCodeFacade } = await import("./facades/lazy/RecoverCodeFacade.js")
const { RecoverCodeFacade } = await import("../../../common/api/worker/facades/lazy/RecoverCodeFacade.js")
return new RecoverCodeFacade(locator.user, locator.cachingEntityClient, locator.login, locator.keyLoader)
})
locator.share = lazyMemoized(async () => {
const { ShareFacade } = await import("./facades/lazy/ShareFacade.js")
const { ShareFacade } = await import("../../../common/api/worker/facades/lazy/ShareFacade.js")
return new ShareFacade(locator.user, locator.crypto, locator.serviceExecutor, locator.cachingEntityClient, locator.keyLoader)
})
locator.counters = lazyMemoized(async () => {
const { CounterFacade } = await import("./facades/lazy/CounterFacade.js")
const { CounterFacade } = await import("../../../common/api/worker/facades/lazy/CounterFacade.js")
return new CounterFacade(locator.serviceExecutor)
})
locator.groupManagement = lazyMemoized(async () => {
const { GroupManagementFacade } = await import("./facades/lazy/GroupManagementFacade.js")
const { GroupManagementFacade } = await import("../../../common/api/worker/facades/lazy/GroupManagementFacade.js")
return new GroupManagementFacade(
locator.user,
await locator.counters(),
@ -303,10 +316,9 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
}
locator.deviceEncryptionFacade = new DeviceEncryptionFacade()
const { DatabaseKeyFactory } = await import("../../misc/credentials/DatabaseKeyFactory.js")
const { DatabaseKeyFactory } = await import("../../../common/misc/credentials/DatabaseKeyFactory.js")
locator.login = new LoginFacade(
worker,
locator.restClient,
/**
* we don't want to try to use the cache in the login facade, because it may not be available (when no user is logged in)
@ -324,16 +336,19 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
new DatabaseKeyFactory(locator.deviceEncryptionFacade),
argon2idFacade,
nonCachingEntityClient,
async (error: Error) => {
await worker.sendError(error)
},
)
locator.search = lazyMemoized(async () => {
const { SearchFacade } = await import("./search/SearchFacade.js")
const { SearchFacade } = await import("../index/SearchFacade.js")
const indexer = await locator.indexer()
const suggestionFacades = [indexer._contact.suggestionFacade]
return new SearchFacade(locator.user, indexer.db, indexer._mail, suggestionFacades, browserData, locator.cachingEntityClient)
})
locator.userManagement = lazyMemoized(async () => {
const { UserManagementFacade } = await import("./facades/lazy/UserManagementFacade.js")
const { UserManagementFacade } = await import("../../../common/api/worker/facades/lazy/UserManagementFacade.js")
return new UserManagementFacade(
locator.user,
await locator.groupManagement(),
@ -348,7 +363,7 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
)
})
locator.customer = lazyMemoized(async () => {
const { CustomerFacade } = await import("./facades/lazy/CustomerFacade.js")
const { CustomerFacade } = await import("../../../common/api/worker/facades/lazy/CustomerFacade.js")
return new CustomerFacade(
locator.user,
await locator.groupManagement(),
@ -368,7 +383,7 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
})
const aesApp = new AesApp(new NativeCryptoFacadeSendDispatcher(worker), random)
locator.blob = lazyMemoized(async () => {
const { BlobFacade } = await import("./facades/lazy/BlobFacade.js")
const { BlobFacade } = await import("../../../common/api/worker/facades/lazy/BlobFacade.js")
return new BlobFacade(
locator.user,
locator.serviceExecutor,
@ -383,7 +398,7 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
)
})
locator.mail = lazyMemoized(async () => {
const { MailFacade } = await import("./facades/lazy/MailFacade.js")
const { MailFacade } = await import("../../../common/api/worker/facades/lazy/MailFacade.js")
return new MailFacade(
locator.user,
locator.cachingEntityClient,
@ -397,7 +412,7 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
})
const nativePushFacade = new NativePushFacadeSendDispatcher(worker)
locator.calendar = lazyMemoized(async () => {
const { CalendarFacade } = await import("./facades/lazy/CalendarFacade.js")
const { CalendarFacade } = await import("../../../common/api/worker/facades/lazy/CalendarFacade.js")
return new CalendarFacade(
locator.user,
await locator.groupManagement(),
@ -413,7 +428,7 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
})
locator.mailAddress = lazyMemoized(async () => {
const { MailAddressFacade } = await import("./facades/lazy/MailAddressFacade.js")
const { MailAddressFacade } = await import("../../../common/api/worker/facades/lazy/MailAddressFacade.js")
return new MailAddressFacade(
locator.user,
await locator.groupManagement(),
@ -424,21 +439,27 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
const scheduler = new SchedulerImpl(dateProvider, self, self)
locator.configFacade = lazyMemoized(async () => {
const { ConfigurationDatabase } = await import("./facades/lazy/ConfigurationDatabase.js")
const { ConfigurationDatabase } = await import("../../../common/api/worker/facades/lazy/ConfigurationDatabase.js")
return new ConfigurationDatabase(locator.keyLoader, locator.user)
})
const eventBusCoordinator = new EventBusEventCoordinator(
worker,
mainInterface.wsConnectivityListener,
locator.mail,
locator.indexer,
locator.user,
locator.cachingEntityClient,
mainInterface.eventController,
locator.configFacade,
locator.keyRotation,
locator.cacheManagement,
async (error: Error) => {
await worker.sendError(error)
},
async (queuedBatch: QueuedBatch[]) => {
const indexer = await locator.indexer()
indexer.addBatchesToQueue(queuedBatch)
indexer.startProcessing()
},
)
locator.eventBusClient = new EventBusClient(
@ -454,11 +475,11 @@ export async function initLocator(worker: WorkerImpl, browserData: BrowserData,
locator.login.init(locator.eventBusClient)
locator.Const = Const
locator.giftCards = lazyMemoized(async () => {
const { GiftCardFacade } = await import("./facades/lazy/GiftCardFacade.js")
const { GiftCardFacade } = await import("../../../common/api/worker/facades/lazy/GiftCardFacade.js")
return new GiftCardFacade(locator.user, await locator.customer(), locator.serviceExecutor, locator.crypto, locator.keyLoader)
})
locator.contactFacade = lazyMemoized(async () => {
const { ContactFacade } = await import("./facades/lazy/ContactFacade.js")
const { ContactFacade } = await import("../../../common/api/worker/facades/lazy/ContactFacade.js")
return new ContactFacade(new EntityClient(locator.cache))
})
}
@ -498,7 +519,7 @@ async function initIndexer(worker: WorkerImpl, cacheInfo: CacheInfo, keyLoaderFa
export async function resetLocator(): Promise<void> {
await locator.login.resetSession()
await initLocator(locator.login.worker, locator._browserData, locator._offlineStorageCleaner)
await initLocator(locator._worker, locator._browserData)
}
if (typeof self !== "undefined") {

View file

@ -1,6 +1,6 @@
import { WorkerImpl } from "../common/api/worker/WorkerImpl.js"
import { Logger, replaceNativeLogger } from "../common/api/common/Logger.js"
import { MailOfflineCleaner } from "./offline/MailOfflineCleaner.js"
import { WorkerImpl } from "./WorkerImpl.js"
import { Logger, replaceNativeLogger } from "../../../common/api/common/Logger.js"
import { MailOfflineCleaner } from "../offline/MailOfflineCleaner.js"
/**
* Receives the first message from the client and initializes the WorkerImpl to receive all future messages. Sends a response to the client on this first message.
@ -22,7 +22,7 @@ self.onmessage = function (msg) {
// @ts-ignore
const workerImpl = new WorkerImpl(typeof self !== "undefined" ? self : null)
await workerImpl.init(browserData, new MailOfflineCleaner())
await workerImpl.init(browserData)
workerImpl.exposedInterface.entropyFacade().then((entropyFacade) => entropyFacade.addEntropy(initialRandomizerEntropy))
self.postMessage({
id: data.id,

View file

@ -12,7 +12,7 @@ import {
MailTypeRef,
} from "../../src/common/api/entities/tutanota/TypeRefs.js"
import { neverNull } from "@tutao/tutanota-utils"
import { initLocator, locator } from "../../src/common/api/worker/WorkerLocator.js"
import { initLocator, locator } from "../../src/mail-app/workerUtils/worker/WorkerLocator.js"
import { browserDataStub, createTestEntity } from "./TestUtils.js"
import { SessionType } from "../../src/common/api/common/SessionType.js"

View file

@ -144,7 +144,7 @@ export async function run({ integration, filter }: { integration?: boolean; filt
}
async function setupSuite({ integration }: { integration?: boolean }) {
const { WorkerImpl } = await import("../../src/common/api/worker/WorkerImpl.js")
const { WorkerImpl } = await import("../../src/mail-app/workerUtils/worker/WorkerImpl.js")
globalThis.testWorker = WorkerImpl
if (typeof process != "undefined") {

View file

@ -1,6 +1,6 @@
import type { BrowserData } from "../../src/common/misc/ClientConstants.js"
import type { Db } from "../../src/common/api/worker/search/SearchTypes.js"
import { IndexerCore } from "../../src/common/api/worker/search/IndexerCore.js"
import { IndexerCore } from "../../src/mail-app/workerUtils/index/IndexerCore.js"
import { EventQueue } from "../../src/common/api/worker/EventQueue.js"
import { DbFacade, DbTransaction } from "../../src/common/api/worker/search/DbFacade.js"
import { assertNotNull, deepEqual, defer, Thunk, TypeRef } from "@tutao/tutanota-utils"

View file

@ -13,7 +13,6 @@ import {
} from "../../../../src/common/api/entities/sys/TypeRefs.js"
import { createTestEntity } from "../../TestUtils.js"
import { AccountType, OperationType } from "../../../../src/common/api/common/TutanotaConstants.js"
import { UserFacade } from "../../../../src/common/api/worker/facades/UserFacade.js"
import { EntityClient } from "../../../../src/common/api/common/EntityClient.js"
import { lazyAsync, lazyMemoized } from "@tutao/tutanota-utils"
@ -21,6 +20,7 @@ import { MailFacade } from "../../../../src/common/api/worker/facades/lazy/MailF
import { EventController } from "../../../../src/common/api/main/EventController.js"
import { KeyRotationFacade } from "../../../../src/common/api/worker/facades/KeyRotationFacade.js"
import { CacheManagementFacade } from "../../../../src/common/api/worker/facades/lazy/CacheManagementFacade.js"
import { QueuedBatch } from "../../../../src/common/api/worker/EventQueue.js"
o.spec("EventBusEventCoordinatorTest", () => {
let eventBusEventCoordinator: EventBusEventCoordinator
@ -49,16 +49,16 @@ o.spec("EventBusEventCoordinatorTest", () => {
keyRotationFacadeMock = object()
cacheManagementFacade = object()
eventBusEventCoordinator = new EventBusEventCoordinator(
object(),
object(),
lazyMailFacade,
object(),
userFacade,
entityClient,
eventController,
object(),
keyRotationFacadeMock,
async () => cacheManagementFacade,
async (error: Error) => {},
(queuedBatch: QueuedBatch[]) => {},
)
})

View file

@ -15,7 +15,6 @@ import { LoginFacade, LoginListener, ResumeSessionErrorReason } from "../../../.
import { IServiceExecutor } from "../../../../../src/common/api/common/ServiceRequest"
import { EntityClient } from "../../../../../src/common/api/common/EntityClient"
import { RestClient } from "../../../../../src/common/api/worker/rest/RestClient"
import { WorkerImpl } from "../../../../../src/common/api/worker/WorkerImpl"
import { InstanceMapper } from "../../../../../src/common/api/worker/crypto/InstanceMapper"
import { CryptoFacade, encryptString } from "../../../../../src/common/api/worker/crypto/CryptoFacade"
import { CacheStorageLateInitializer } from "../../../../../src/common/api/worker/rest/CacheStorageProxy"
@ -83,7 +82,6 @@ async function makeUser(userId: Id, kdfVersion: KdfType = DEFAULT_KDF_TYPE, user
o.spec("LoginFacadeTest", function () {
let facade: LoginFacade
let workerMock: WorkerImpl
let serviceExecutor: IServiceExecutor
let restClientMock: RestClient
let entityClientMock: EntityClient
@ -103,7 +101,6 @@ o.spec("LoginFacadeTest", function () {
const login = "born.slippy@tuta.io"
o.beforeEach(function () {
workerMock = instance(WorkerImpl)
serviceExecutor = object()
when(serviceExecutor.get(SaltService, anything()), { ignoreExtraArgs: true }).thenResolve(
createTestEntity(SaltReturnTypeRef, { salt: SALT, kdfVersion: DEFAULT_KDF_TYPE }),
@ -143,7 +140,6 @@ o.spec("LoginFacadeTest", function () {
when(argon2idFacade.generateKeyFromPassphrase(anything(), anything())).thenResolve(PASSWORD_KEY)
facade = new LoginFacade(
workerMock,
restClientMock,
entityClientMock,
loginListener,
@ -158,6 +154,7 @@ o.spec("LoginFacadeTest", function () {
databaseKeyFactoryMock,
argon2idFacade,
entityClientMock,
async (error: Error) => {},
)
eventBusClientMock = instance(EventBusClient)

View file

@ -1,5 +1,5 @@
import o from "@tutao/otest"
import { WorkerImpl } from "../../../../../src/common/api/worker/WorkerImpl.js"
import { WorkerImpl } from "../../../../../src/mail-app/workerUtils/worker/WorkerImpl.js"
import { UserFacade } from "../../../../../src/common/api/worker/facades/UserFacade.js"
import { GroupManagementFacade } from "../../../../../src/common/api/worker/facades/lazy/GroupManagementFacade.js"
import { CounterFacade } from "../../../../../src/common/api/worker/facades/lazy/CounterFacade.js"

View file

@ -1,6 +1,6 @@
import o from "@tutao/otest"
import { verify } from "@tutao/tutanota-test-utils"
import { customTypeEncoders, ensureBase64Ext, OfflineStorage } from "../../../../../src/common/api/worker/offline/OfflineStorage.js"
import { customTypeEncoders, ensureBase64Ext, OfflineStorage, OfflineStorageCleaner } from "../../../../../src/common/api/worker/offline/OfflineStorage.js"
import { instance, object, when } from "testdouble"
import * as cborg from "cborg"
import {
@ -38,11 +38,11 @@ import { BlobElementEntity, ElementEntity, ListElementEntity, SomeEntity } from
import { resolveTypeReference } from "../../../../../src/common/api/common/EntityFunctions.js"
import { Type as TypeId } from "../../../../../src/common/api/common/EntityConstants.js"
import { expandId } from "../../../../../src/common/api/worker/rest/DefaultEntityRestCache.js"
import { WorkerImpl } from "../../../../../src/common/api/worker/WorkerImpl.js"
import { UserTypeRef } from "../../../../../src/common/api/entities/sys/TypeRefs.js"
import { DesktopSqlCipher } from "../../../../../src/common/desktop/db/DesktopSqlCipher.js"
import { createTestEntity } from "../../../TestUtils.js"
import { sql } from "../../../../../src/common/api/worker/offline/Sql.js"
import { MailOfflineCleaner } from "../../../../../src/mail-app/workerUtils/offline/MailOfflineCleaner.js"
function incrementId(id: Id, ms: number) {
const timestamp = generatedIdToTimestamp(id)
@ -80,8 +80,8 @@ o.spec("OfflineStorageDb", function () {
let dateProviderMock: DateProvider
let storage: OfflineStorage
let migratorMock: OfflineStorageMigrator
let offlineStorageCleanerMock: OfflineStorageCleaner
let interWindowEventSenderMock: InterWindowEventFacadeSendDispatcher
let worker: WorkerImpl
o.beforeEach(async function () {
dbFacade = new DesktopSqlCipher(nativePath, database, false)
@ -89,8 +89,9 @@ o.spec("OfflineStorageDb", function () {
dateProviderMock = object<DateProvider>()
migratorMock = instance(OfflineStorageMigrator)
interWindowEventSenderMock = instance(InterWindowEventFacadeSendDispatcher)
offlineStorageCleanerMock = new MailOfflineCleaner()
when(dateProviderMock.now()).thenReturn(now.getTime())
storage = new OfflineStorage(dbFacade, interWindowEventSenderMock, dateProviderMock, migratorMock)
storage = new OfflineStorage(dbFacade, interWindowEventSenderMock, dateProviderMock, migratorMock, offlineStorageCleanerMock)
})
o.afterEach(async function () {

View file

@ -3,7 +3,7 @@ import { func, instance, when } from "testdouble"
import { verify } from "@tutao/tutanota-test-utils"
import { LateInitializedCacheStorageImpl, OfflineStorageArgs } from "../../../../../src/common/api/worker/rest/CacheStorageProxy.js"
import { OfflineStorage } from "../../../../../src/common/api/worker/offline/OfflineStorage.js"
import { WorkerImpl } from "../../../../../src/common/api/worker/WorkerImpl.js"
import { WorkerImpl } from "../../../../../src/mail-app/workerUtils/worker/WorkerImpl.js"
o.spec("CacheStorageProxy", function () {
const userId = "userId"
@ -20,7 +20,9 @@ o.spec("CacheStorageProxy", function () {
offlineStorageMock = instance(OfflineStorage)
offlineStorageProviderMock = func() as () => Promise<null | OfflineStorage>
proxy = new LateInitializedCacheStorageImpl(workerMock, offlineStorageProviderMock)
proxy = new LateInitializedCacheStorageImpl(async (error: Error) => {
await workerMock.sendError(error)
}, offlineStorageProviderMock)
})
o.spec("initialization", function () {

View file

@ -46,7 +46,7 @@ import {
MailDetailsTypeRef,
MailTypeRef,
} from "../../../../../src/common/api/entities/tutanota/TypeRefs.js"
import { OfflineStorage } from "../../../../../src/common/api/worker/offline/OfflineStorage.js"
import { OfflineStorage, OfflineStorageCleaner } from "../../../../../src/common/api/worker/offline/OfflineStorage.js"
import { assertThrows, mockAttribute, spy, unmockAttribute, verify } from "@tutao/tutanota-test-utils"
import { NoZoneDateProvider } from "../../../../../src/common/api/common/utils/NoZoneDateProvider.js"
import { RestClient } from "../../../../../src/common/api/worker/rest/RestClient.js"
@ -86,7 +86,8 @@ async function getOfflineStorage(userId: Id): Promise<CacheStorage> {
const sqlCipherFacade = new PerWindowSqlCipherFacade(odbRefCounter)
await sqlCipherFacade.openDb(userId, offlineDatabaseTestKey)
const interWindowEventSender = instance(InterWindowEventFacadeSendDispatcher)
const offlineStorage = new OfflineStorage(sqlCipherFacade, interWindowEventSender, new NoZoneDateProvider(), migratorMock)
const offlineStorageCleanerMock = object<OfflineStorageCleaner>()
const offlineStorage = new OfflineStorage(sqlCipherFacade, interWindowEventSender, new NoZoneDateProvider(), migratorMock, offlineStorageCleanerMock)
await offlineStorage.init({ userId, databaseKey: offlineDatabaseTestKey, timeRangeDays: 42, forceNewDatabase: false })
return offlineStorage
}

View file

@ -7,7 +7,7 @@ import {
ContactSocialIdTypeRef,
ContactTypeRef,
} from "../../../../../src/common/api/entities/tutanota/TypeRefs.js"
import { ContactIndexer } from "../../../../../src/common/api/worker/search/ContactIndexer.js"
import { ContactIndexer } from "../../../../../src/mail-app/workerUtils/index/ContactIndexer.js"
import { NotAuthorizedError, NotFoundError } from "../../../../../src/common/api/common/error/RestError.js"
import { DbTransaction } from "../../../../../src/common/api/worker/search/DbFacade.js"
import { FULL_INDEXED_TIMESTAMP, NOTHING_INDEXED_TIMESTAMP, OperationType } from "../../../../../src/common/api/common/TutanotaConstants.js"

View file

@ -30,7 +30,7 @@ import { EntityUpdateTypeRef } from "../../../../../src/common/api/entities/sys/
import { EventQueue } from "../../../../../src/common/api/worker/EventQueue.js"
import { CancelledError } from "../../../../../src/common/api/common/error/CancelledError.js"
import { createSearchIndexDbStub, DbStub, DbStubTransaction } from "./DbStub.js"
import { IndexerCore } from "../../../../../src/common/api/worker/search/IndexerCore.js"
import { IndexerCore } from "../../../../../src/mail-app/workerUtils/index/IndexerCore.js"
import { elementIdPart, generatedIdToTimestamp, listIdPart, timestampToGeneratedId } from "../../../../../src/common/api/common/utils/EntityUtils.js"
import { createTestEntity, makeCore } from "../../../TestUtils.js"
import { Aes256Key, aes256RandomKey, aesEncrypt, fixedIv, IV_BYTE_LENGTH, random, unauthenticatedAesDecrypt } from "@tutao/tutanota-crypto"

View file

@ -7,7 +7,7 @@ import {
NOTHING_INDEXED_TIMESTAMP,
OperationType,
} from "../../../../../src/common/api/common/TutanotaConstants.js"
import { Indexer } from "../../../../../src/common/api/worker/search/Indexer.js"
import { Indexer } from "../../../../../src/mail-app/workerUtils/index/Indexer.js"
import { NotAuthorizedError } from "../../../../../src/common/api/common/error/RestError.js"
import { ContactListTypeRef, ContactTypeRef, MailTypeRef } from "../../../../../src/common/api/entities/tutanota/TypeRefs.js"
import { OutOfSyncError } from "../../../../../src/common/api/common/error/OutOfSyncError.js"
@ -25,11 +25,11 @@ import { func, instance, matchers, object, replace, reset, verify, when } from "
import { CacheInfo } from "../../../../../src/common/api/worker/facades/LoginFacade.js"
import { RestClient } from "../../../../../src/common/api/worker/rest/RestClient.js"
import { EntityClient } from "../../../../../src/common/api/common/EntityClient.js"
import { ContactIndexer } from "../../../../../src/common/api/worker/search/ContactIndexer.js"
import { ContactIndexer } from "../../../../../src/mail-app/workerUtils/index/ContactIndexer.js"
import { InfoMessageHandler } from "../../../../../src/common/gui/InfoMessageHandler.js"
import { GroupDataOS, Metadata, MetaDataOS } from "../../../../../src/common/api/worker/search/IndexTables.js"
import { MailFacade } from "../../../../../src/common/api/worker/facades/lazy/MailFacade.js"
import { MailIndexer } from "../../../../../src/common/api/worker/search/MailIndexer.js"
import { MailIndexer } from "../../../../../src/mail-app/workerUtils/index/MailIndexer.js"
import { KeyLoaderFacade } from "../../../../../src/common/api/worker/facades/KeyLoaderFacade.js"
const SERVER_TIME = new Date("1994-06-08").getTime()

View file

@ -9,10 +9,10 @@ import {
NOTHING_INDEXED_TIMESTAMP,
OperationType,
} from "../../../../../src/common/api/common/TutanotaConstants.js"
import { IndexerCore } from "../../../../../src/common/api/worker/search/IndexerCore.js"
import { IndexerCore } from "../../../../../src/mail-app/workerUtils/index/IndexerCore.js"
import type { EntityUpdate } from "../../../../../src/common/api/entities/sys/TypeRefs.js"
import { EntityUpdateTypeRef, GroupMembershipTypeRef, UserTypeRef } from "../../../../../src/common/api/entities/sys/TypeRefs.js"
import { _getCurrentIndexTimestamp, INITIAL_MAIL_INDEX_INTERVAL_DAYS, MailIndexer } from "../../../../../src/common/api/worker/search/MailIndexer.js"
import { _getCurrentIndexTimestamp, INITIAL_MAIL_INDEX_INTERVAL_DAYS, MailIndexer } from "../../../../../src/mail-app/workerUtils/index/MailIndexer.js"
import {
BodyTypeRef,
EncryptedMailAddressTypeRef,

View file

@ -1,5 +1,5 @@
import o from "@tutao/otest"
import { SearchFacade } from "../../../../../src/common/api/worker/search/SearchFacade.js"
import { SearchFacade } from "../../../../../src/mail-app/workerUtils/index/SearchFacade.js"
import { ContactTypeRef, MailTypeRef } from "../../../../../src/common/api/entities/tutanota/TypeRefs.js"
import { UserTypeRef } from "../../../../../src/common/api/entities/sys/TypeRefs.js"
import type { TypeInfo } from "../../../../../src/common/api/worker/search/IndexUtils.js"

View file

@ -3,7 +3,7 @@
*/
import o from "@tutao/otest"
import { ContactTypeRef } from "../../../../../src/common/api/entities/tutanota/TypeRefs.js"
import { SuggestionFacade } from "../../../../../src/common/api/worker/search/SuggestionFacade.js"
import { SuggestionFacade } from "../../../../../src/mail-app/workerUtils/index/SuggestionFacade.js"
import { downcast } from "@tutao/tutanota-utils"
import { aes256RandomKey, fixedIv } from "@tutao/tutanota-crypto"
import { SearchTermSuggestionsOS } from "../../../../../src/common/api/worker/search/IndexTables.js"

View file

@ -8,18 +8,21 @@ import { EntityClient } from "../../../src/common/api/common/EntityClient.js"
import { EntityRestClientMock } from "../api/worker/rest/EntityRestClientMock.js"
import { downcast } from "@tutao/tutanota-utils"
import { LoginController } from "../../../src/common/api/main/LoginController.js"
import { matchers, object, when } from "testdouble"
import { instance, matchers, object, when } from "testdouble"
import { UserController } from "../../../src/common/api/main/UserController.js"
import { createTestEntity } from "../TestUtils.js"
import { EntityUpdateData } from "../../../src/common/api/common/utils/EntityUpdateUtils.js"
import { MailboxDetail, MailboxModel } from "../../../src/common/mailFunctionality/MailboxModel.js"
import { InboxRuleHandler } from "../../../src/mail-app/mail/model/InboxRuleHandler.js"
import { getElementId, getListId } from "../../../src/common/api/common/utils/EntityUtils.js"
import { MailModel } from "../../../src/mail-app/mail/model/MailModel.js"
import { EventController } from "../../../src/common/api/main/EventController.js"
import { MailFacade } from "../../../src/common/api/worker/facades/lazy/MailFacade.js"
o.spec("MailModelTest", function () {
let notifications: Partial<Notifications>
let showSpy: Spy
let model: MailboxModel
let model: MailModel
const inboxFolder = createTestEntity(MailFolderTypeRef, { _id: ["folderListId", "inboxId"], isMailSet: false })
inboxFolder.mails = "instanceListId"
inboxFolder.folderType = MailSetKind.INBOX
@ -33,6 +36,9 @@ o.spec("MailModelTest", function () {
o.beforeEach(function () {
notifications = {}
const mailboxModel = instance(MailboxModel)
const eventController = instance(EventController)
const mailFacade = instance(MailFacade)
showSpy = notifications.showNotification = spy()
logins = object()
let userController = object<UserController>()
@ -40,43 +46,37 @@ o.spec("MailModelTest", function () {
when(logins.getUserController()).thenReturn(userController)
inboxRuleHandler = object()
model = new MailboxModel(downcast({}), new EntityClient(restClient), logins)
model = new MailModel(downcast({}), mailboxModel, eventController, new EntityClient(restClient), logins, mailFacade, null, null)
// not pretty, but works
model.mailboxDetails(mailboxDetails as MailboxDetail[])
// model.mailboxDetails(mailboxDetails as MailboxDetail[])
})
o("doesn't send notification for another folder", async function () {
const mail = createTestEntity(MailTypeRef, { _id: [anotherFolder.mails, "mailId"], sets: [] })
restClient.addListInstances(mail)
await model.entityEventsReceived(
[
makeUpdate({
instanceListId: getListId(mail),
instanceId: getElementId(mail),
operation: OperationType.CREATE,
}),
],
"userGroupId",
)
await model.entityEventsReceived([
makeUpdate({
instanceListId: getListId(mail),
instanceId: getElementId(mail),
operation: OperationType.CREATE,
}),
])
o(showSpy.invocations.length).equals(0)
})
o("doesn't send notification for move operation", async function () {
const mail = createTestEntity(MailTypeRef, { _id: [inboxFolder.mails, "mailId"], sets: [] })
restClient.addListInstances(mail)
await model.entityEventsReceived(
[
makeUpdate({
instanceListId: getListId(mail),
instanceId: getElementId(mail),
operation: OperationType.DELETE,
}),
makeUpdate({
instanceListId: getListId(mail),
instanceId: getElementId(mail),
operation: OperationType.CREATE,
}),
],
"userGroupId",
)
await model.entityEventsReceived([
makeUpdate({
instanceListId: getListId(mail),
instanceId: getElementId(mail),
operation: OperationType.DELETE,
}),
makeUpdate({
instanceListId: getListId(mail),
instanceId: getElementId(mail),
operation: OperationType.CREATE,
}),
])
o(showSpy.invocations.length).equals(0)
})

View file

@ -3,7 +3,7 @@ import { Recipient, RecipientType } from "../../../src/common/api/common/recipie
import { LazyLoaded } from "@tutao/tutanota-utils"
import { Contact } from "../../../src/common/api/entities/tutanota/TypeRefs.js"
import { User } from "../../../src/common/api/entities/sys/TypeRefs.js"
import { createNewContact, isTutanotaMailAddress } from "../../../src/common/mailFunctionality/SharedMailUtils.js"
import { createNewContact, isTutaMailAddress } from "../../../src/common/mailFunctionality/SharedMailUtils.js"
/**
* Creating actual ResolvableRecipients is annoying because you have to mock a bunch of stuff in other model classes
@ -43,7 +43,7 @@ export class ResolvableRecipientMock implements ResolvableRecipient {
private user: User,
) {
this.name = name ?? ""
this.type = type ?? (isTutanotaMailAddress(address) ? RecipientType.INTERNAL : RecipientType.UNKNOWN)
this.type = type ?? (isTutaMailAddress(address) ? RecipientType.INTERNAL : RecipientType.UNKNOWN)
if (resolveMode === ResolveMode.Eager) {
this.lazyResolve.getAsync()

View file

@ -1,8 +1,8 @@
import o from "@tutao/otest"
import type { MailBundle } from "../../../../src/mail-app/mail/export/Bundler.js"
import { _formatSmtpDateTime, mailToEml } from "../../../../src/mail-app/mail/export/Exporter.js"
import { base64ToUint8Array, stringToUtf8Uint8Array } from "@tutao/tutanota-utils"
import { createDataFile } from "../../../../src/common/api/common/DataFile.js"
import { MailBundle } from "../../../../src/common/mailFunctionality/SharedMailUtils.js"
o.spec("Exporter", function () {
o.spec("mail to eml", function () {