clipper: handle clipped note sync in background (#2367)
This commit is contained in:
@@ -33,6 +33,7 @@ export function environmentToString(environment: Environment) {
|
||||
[Environment.Web]: 'web',
|
||||
[Environment.Desktop]: 'desktop',
|
||||
[Environment.Mobile]: 'native-mobile-web',
|
||||
[Environment.Clipper]: 'clipper',
|
||||
}
|
||||
return map[environment]
|
||||
}
|
||||
|
||||
@@ -374,15 +374,8 @@ export class LegacyApiService
|
||||
if (preprocessingError) {
|
||||
return preprocessingError
|
||||
}
|
||||
const path = Paths.v1.sync
|
||||
const params = this.params({
|
||||
[ApiEndpointParam.SyncPayloads]: payloads,
|
||||
[ApiEndpointParam.LastSyncToken]: lastSyncToken,
|
||||
[ApiEndpointParam.PaginationToken]: paginationToken,
|
||||
[ApiEndpointParam.SyncDlLimit]: limit,
|
||||
[ApiEndpointParam.SharedVaultUuids]: sharedVaultUuids,
|
||||
})
|
||||
const response = await this.httpService.post<RawSyncResponse>(path, params, this.getSessionAccessToken())
|
||||
const request = this.getSyncHttpRequest(payloads, lastSyncToken, paginationToken, limit, sharedVaultUuids)
|
||||
const response = await this.httpService.runHttp<RawSyncResponse>(request)
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
this.preprocessAuthenticatedErrorResponse(response)
|
||||
@@ -394,6 +387,29 @@ export class LegacyApiService
|
||||
return response
|
||||
}
|
||||
|
||||
getSyncHttpRequest(
|
||||
payloads: ServerSyncPushContextualPayload[],
|
||||
lastSyncToken: string | undefined,
|
||||
paginationToken: string | undefined,
|
||||
limit: number,
|
||||
sharedVaultUuids?: string[] | undefined,
|
||||
): HttpRequest {
|
||||
const path = Paths.v1.sync
|
||||
const params = this.params({
|
||||
[ApiEndpointParam.SyncPayloads]: payloads,
|
||||
[ApiEndpointParam.LastSyncToken]: lastSyncToken,
|
||||
[ApiEndpointParam.PaginationToken]: paginationToken,
|
||||
[ApiEndpointParam.SyncDlLimit]: limit,
|
||||
[ApiEndpointParam.SharedVaultUuids]: sharedVaultUuids,
|
||||
})
|
||||
return {
|
||||
url: joinPaths(this.host, path),
|
||||
params,
|
||||
verb: HttpVerb.Post,
|
||||
authentication: this.getSessionAccessToken(),
|
||||
}
|
||||
}
|
||||
|
||||
async refreshSession(): Promise<HttpResponse<SessionRenewalResponse>> {
|
||||
const preprocessingError = this.preprocessingError()
|
||||
if (preprocessingError) {
|
||||
|
||||
@@ -71,6 +71,7 @@ import {
|
||||
|
||||
export const MINIMUM_PASSWORD_LENGTH = 8
|
||||
export const MissingAccountParams = 'missing-params'
|
||||
const ThirtyMinutes = 30 * 60 * 1000
|
||||
|
||||
const cleanedEmailString = (email: string) => {
|
||||
return email.trim().toLowerCase()
|
||||
@@ -837,4 +838,27 @@ export class SessionManager
|
||||
|
||||
return Result.ok(sessionOrError.getValue())
|
||||
}
|
||||
|
||||
async refreshSessionIfExpiringSoon(): Promise<boolean> {
|
||||
const session = this.getSession()
|
||||
|
||||
if (!session) {
|
||||
return false
|
||||
}
|
||||
if (session instanceof LegacySession) {
|
||||
return false
|
||||
}
|
||||
|
||||
const accessTokenExpiration = new Date(session.accessToken.expiresAt)
|
||||
const refreshTokenExpiration = new Date(session.refreshToken.expiresAt)
|
||||
|
||||
const willAccessTokenExpireSoon = accessTokenExpiration.getTime() - Date.now() < ThirtyMinutes
|
||||
const willRefreshTokenExpireSoon = refreshTokenExpiration.getTime() - Date.now() < ThirtyMinutes
|
||||
|
||||
if (willAccessTokenExpireSoon || willRefreshTokenExpireSoon) {
|
||||
return this.httpService.refreshSession()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConflictParams, ConflictType } from '@standardnotes/responses'
|
||||
import { ConflictParams, ConflictType, HttpRequest } from '@standardnotes/responses'
|
||||
import { AccountSyncOperation } from '@Lib/Services/Sync/Account/Operation'
|
||||
import {
|
||||
LoggerInterface,
|
||||
@@ -922,6 +922,26 @@ export class SyncService
|
||||
return undefined
|
||||
}
|
||||
|
||||
async getRawSyncRequestForExternalUse(
|
||||
items: (DecryptedItemInterface | DeletedItemInterface)[],
|
||||
): Promise<HttpRequest | undefined> {
|
||||
if (this.dealloced) {
|
||||
return
|
||||
}
|
||||
|
||||
const online = this.sessionManager.online()
|
||||
|
||||
if (!online) {
|
||||
return
|
||||
}
|
||||
|
||||
const payloads = await this.payloadsByPreparingForServer(items.map((i) => i.payloadRepresentation()))
|
||||
const syncToken = await this.getLastSyncToken()
|
||||
const paginationToken = await this.getPaginationToken()
|
||||
|
||||
return this.apiService.getSyncHttpRequest(payloads, syncToken, paginationToken, 150)
|
||||
}
|
||||
|
||||
private async handleOfflineResponse(response: OfflineSyncResponse) {
|
||||
this.logger.debug('Offline Sync Response', response)
|
||||
|
||||
|
||||
@@ -1047,4 +1047,25 @@ describe('online syncing', function () {
|
||||
|
||||
await contextB.deinit()
|
||||
})
|
||||
|
||||
it('should sync note when running raw sync request for external use', async function () {
|
||||
const contextA = this.context
|
||||
const contextB = await Factory.createAppContextWithFakeCrypto('AppB', contextA.email, contextA.password)
|
||||
|
||||
await contextB.launch()
|
||||
await contextB.signIn()
|
||||
|
||||
const notePayload = Factory.createNote()
|
||||
|
||||
const rawSyncRequest = await this.application.sync.getRawSyncRequestForExternalUse([notePayload])
|
||||
expect(rawSyncRequest).to.be.ok
|
||||
|
||||
const response = await this.application.http.runHttp(rawSyncRequest)
|
||||
expect(response.status).to.equal(200)
|
||||
|
||||
await contextB.sync()
|
||||
|
||||
const note = contextB.application.items.findItem(notePayload.uuid)
|
||||
expect(note).to.be.ok
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user