-
Notifications
You must be signed in to change notification settings - Fork 530
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP: Add MailExportFacade and range request options
Oh, dear. Co-authored-by: BijinDev <[email protected]>
- Loading branch information
Showing
7 changed files
with
244 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { MailExportTokenService } from "../../../common/api/entities/tutanota/Services" | ||
import { createMailExportTokenServicePostIn } from "../../../common/api/entities/tutanota/TypeRefs" | ||
import { assertWorkerOrNode } from "../../../common/api/common/Env" | ||
import { IServiceExecutor } from "../../../common/api/common/ServiceRequest" | ||
import { EntityClient } from "../../../common/api/common/EntityClient" | ||
import { CacheMode, EntityRestClientLoadOptions } from "../../../common/api/worker/rest/EntityRestClient" | ||
import type { ListElementEntity, SomeEntity } from "../../../common/api/common/EntityTypes" | ||
import { TypeRef } from "@tutao/tutanota-utils" | ||
import { AccessExpiredError } from "../../../common/api/common/error/RestError" | ||
|
||
assertWorkerOrNode() | ||
|
||
const TAG = "MailExportFacade" | ||
|
||
type ExportToken = string & { _exportToken: undefined } | ||
|
||
/** | ||
* Mail exporter functions | ||
* | ||
* This implements loadForMailGroup and loadRangeForMailGroup which uses mail export tokens retrieved from the server | ||
* and does not write to cache. Note that no loadAll method is implemented since tokens expire after a short period of | ||
* time, and it is better to process in batches. | ||
*/ | ||
export class MailExportFacade { | ||
// Mail group ID -> export token | ||
private readonly tokenCache: Map<Id, ExportToken> = new Map() | ||
|
||
constructor(private readonly serviceExecutor: IServiceExecutor, private readonly entityClient: EntityClient) {} | ||
|
||
/** | ||
* Load a single element for export, (re-)generating a mail export token if needed | ||
*/ | ||
async loadForMailGroup<T extends SomeEntity>(mailGroup: Id, typeRef: TypeRef<T>, id: PropertyType<T, "_id">): Promise<T> { | ||
return await this.handleRequest(mailGroup, async (options) => { | ||
return await this.entityClient.load(typeRef, id, options) | ||
}) | ||
} | ||
|
||
/** | ||
* Load a range of elements for export, (re-)generating a mail export token if needed | ||
*/ | ||
async loadRangeForMailGroup<T extends ListElementEntity>( | ||
mailGroup: Id, | ||
typeRef: TypeRef<T>, | ||
listId: Id, | ||
firstId: Id, | ||
reverse: boolean, | ||
count: number, | ||
): Promise<T[]> { | ||
return await this.handleRequest(mailGroup, async (options) => { | ||
return await this.entityClient.loadRange<T>(typeRef, listId, firstId, count, reverse, options) | ||
}) | ||
} | ||
|
||
/** | ||
* Runs `request`. | ||
* | ||
* If `AccessExpiredError` is thrown, delete the cached token and re-run it again. | ||
* @param mailGroup mail group to request a token | ||
* @param request function to run | ||
* @private | ||
*/ | ||
private async handleRequest<T>(mailGroup: Id, request: (options: EntityRestClientLoadOptions) => Promise<T>): Promise<T> { | ||
try { | ||
const options = await this.applyExportOptions(mailGroup) | ||
return await request(options) | ||
} catch (e) { | ||
if (e instanceof AccessExpiredError) { | ||
console.log(TAG, `token expired for exporting of mail group ${mailGroup} and will be renewed`) | ||
this.tokenCache.delete(mailGroup) | ||
const options = await this.applyExportOptions(mailGroup) | ||
return await request(options) | ||
} else { | ||
throw e | ||
} | ||
} | ||
} | ||
|
||
private async applyExportOptions(mailGroup: Id): Promise<EntityRestClientLoadOptions> { | ||
const options: EntityRestClientLoadOptions = {} | ||
options.cacheMode = CacheMode.ReadOnly | ||
options.extraHeaders = { | ||
mailExportToken: await this.getToken(mailGroup), | ||
} | ||
return options | ||
} | ||
|
||
private async getToken(mailGroup: Id): Promise<ExportToken> { | ||
return this.tokenCache.get(mailGroup) ?? (await this.requestNewToken(mailGroup)) | ||
} | ||
|
||
/** | ||
* Request a new token and write it to the tokenCache. | ||
* | ||
* This token will be valid for the mail group and current user for a short amount of time, after which you will get | ||
* an `AccessExpiredError` when using the token (or `NotAuthorizedError` if the user lost access to the group in the | ||
* meantime). | ||
* @param mailGroup mail group the token is valid for | ||
* @throws TooManyRequestsError the user cannot request any more tokens right now | ||
* @throws NotAuthorizedError the user has no access to `mailGroup` | ||
* @return the token | ||
*/ | ||
private async requestNewToken(mailGroup: Id): Promise<ExportToken> { | ||
console.log(TAG, `generating new token for ${mailGroup}`) | ||
const requestData = createMailExportTokenServicePostIn({ | ||
mailGroup, | ||
}) | ||
const result = await this.serviceExecutor.post(MailExportTokenService, requestData) | ||
const token = result.mailExportToken as ExportToken | ||
this.tokenCache.set(mailGroup, token) | ||
return token | ||
} | ||
} |
Oops, something went wrong.